moved js files in public into js and css into css folder

This commit is contained in:
2026-09-22 21:55:02 +02:00
parent 99a3277e64
commit 08f5f3f8d9
40 changed files with 1772 additions and 1555 deletions
+81 -62
View File
@@ -1,69 +1,88 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sign in &middot; Elsewhere</title>
<link rel="icon" href="/favicon.svg">
<script src="/app/theme-bootstrap.js?v=20260909-account-controls">
</script>
<link rel="stylesheet" href="/style.css?v=20260909-account-controls">
<link rel="stylesheet" href="/pages.css?v=20260909-account-controls">
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sign in &middot; Elsewhere</title>
<link rel="icon" href="img/favicon.svg">
<script src="/js/theme-bootstrap.js?v=20260909-account-controls">
</script>
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls">
<link rel="stylesheet" href="/css/pages.css?v=20260909-account-controls">
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet">
</head>
<body class="site-page account-page">
<!-- site-header -->
<main id="main" class="account-layout page-content">
<section class="account-intro">
<p class="eyebrow">Welcome to elsewhere</p>
<h1 id="accountHeadline">Pick up where<br>curiosity left off.</h1>
<p>A space for your ideas, your characters, and the conversations worth coming back to.</p>
<a href="/">&larr; Back to Elsewhere</a>
</section>
<section class="account-box">
<h2 id="accountTitle">Sign in</h2>
<p id="accountDescription" class="muted">Welcome back. Your conversations are waiting.</p>
<form id="accountForm">
<label class="field">
<span>Username</span>
<input name="username" autocomplete="username" required minlength="3">
</label>
<div class="field">
<label for="accountPassword">Password</label>
<div class="password-input">
<input id="accountPassword" name="password" type="password" autocomplete="current-password" required minlength="6">
<button type="button" id="showPassword" class="icon-button password-toggle" aria-label="Show password" title="Show password" aria-controls="accountPassword" aria-pressed="false">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/><path class="eye-slash" d="m3 3 18 18"/></svg>
</button>
</div>
</div>
<label id="inviteField" class="field hidden">
<span>Invite code</span>
<input name="inviteCode" autocomplete="off">
</label>
<label id="consentField" class="consent-container hidden">
<input name="consent" type="checkbox">
<span>I agree to the <a href="/tos.html" target="_blank" rel="noopener">Terms</a> and <a href="/privacy.html" target="_blank" rel="noopener">Privacy Policy</a>.</span>
</label>
<div id="accountStatus" class="account-status hidden" role="status" aria-live="polite" aria-atomic="true" tabindex="-1">
<svg class="account-error-icon" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v6m0 3v1"/></svg>
<span id="accountStatusText"></span>
</div>
<button id="accountSubmit" class="primary-action" type="submit">Sign in</button>
</form>
<p id="accountSwitch">
</p>
</section>
</main>
<footer class="site-footer">
<a class="site-brand" href="/">elsewhere</a>
<span>A space for every conversation.</span>
<a href="/privacy.html">Privacy</a>
<a href="/tos.html">Terms</a>
</footer>
<script type="module" src="/pages.js?v=20260909-account-controls">
</script>
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
<!-- site-header -->
<main id="main" class="account-layout page-content">
<section class="account-intro">
<p class="eyebrow">Welcome to elsewhere</p>
<h1 id="accountHeadline">Pick up where<br>curiosity left off.</h1>
<p>A space for your ideas, your characters, and the conversations worth coming back to.</p>
<a href="/">&larr; Back to Elsewhere</a>
</section>
<section class="account-box">
<h2 id="accountTitle">Sign in</h2>
<p id="accountDescription" class="muted">Welcome back. Your conversations are waiting.</p>
<form id="accountForm">
<label class="field">
<span>Username</span>
<input name="username" autocomplete="username" required minlength="3">
</label>
<div class="field">
<label for="accountPassword">Password</label>
<div class="password-input">
<input id="accountPassword" name="password" type="password" autocomplete="current-password"
required minlength="6">
<button type="button" id="showPassword" class="icon-button password-toggle"
aria-label="Show password" title="Show password" aria-controls="accountPassword"
aria-pressed="false">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z" />
<circle cx="12" cy="12" r="3" />
<path class="eye-slash" d="m3 3 18 18" />
</svg>
</button>
</div>
</div>
<label id="inviteField" class="field hidden">
<span>Invite code</span>
<input name="inviteCode" autocomplete="off">
</label>
<label id="consentField" class="consent-container hidden">
<input name="consent" type="checkbox">
<span>I agree to the <a href="/tos.html" target="_blank" rel="noopener">Terms</a> and <a
href="/privacy.html" target="_blank" rel="noopener">Privacy Policy</a>.</span>
</label>
<div id="accountStatus" class="account-status hidden" role="status" aria-live="polite"
aria-atomic="true" tabindex="-1">
<svg class="account-error-icon" viewBox="0 0 24 24" width="20" height="20" fill="none"
stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="12" cy="12" r="9" />
<path d="M12 7v6m0 3v1" />
</svg>
<span id="accountStatusText"></span>
</div>
<button id="accountSubmit" class="primary-action" type="submit">Sign in</button>
</form>
<p id="accountSwitch">
</p>
</section>
</main>
<footer class="site-footer">
<a class="site-brand" href="/">elsewhere</a>
<span>A space for every conversation.</span>
<a href="/privacy.html">Privacy</a>
<a href="/tos.html">Terms</a>
</footer>
<script type="module" src="js/pages.js?v=20260909-account-controls">
</script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
</html>
+418 -320
View File
@@ -1,369 +1,467 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="img/favicon.svg">
<title>Elsewhere</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script src="/app/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/style.css?v=20260920-web-search-composer"/>
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet">
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/css/style.css?v=20260920-web-search-composer" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/pages.css?v=20260909-account-controls"><link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
<link rel="stylesheet" href="/css/pages.css?v=20260909-account-controls">
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
</head>
<body class="site-page chat-page" data-theme="fakegpt" data-theme-mode="light">
<!-- site-header -->
<div class="app-shell">
<section id="chat" class="card chat-card chat-focus-card hidden" aria-label="AI chat workspace">
<div class="chat-layout chat-layout-focus">
<aside id="chatSidebar" class="chat-sidebar" aria-label="Conversation history">
<div class="sidebar-top">
<div class="sidebar-workspace-card"><h2>Conversations</h2><button id="newChat" class="primary-action" type="button">New chat</button></div>
<label class="field compact-field">
<span>Search</span>
<input id="chatSearch" type="search" placeholder="Search chats"/>
</label>
</div>
<div class="sidebar-middle">
<div id="chatList" class="chat-list chat-list-grouped"></div>
<div id="chatListLoading" class="chat-loading hidden" role="status" aria-live="polite">
<span class="chat-loading-dot" aria-hidden="true"></span>
<span id="chatListLoadingText">Loading chats...</span>
</div>
</div>
</aside>
<section id="main" class="chat-main chat-main-focus" aria-label="Active conversation">
<div class="chat-heading"><p id="workspaceNotice" class="status error hidden" role="status" aria-live="polite"></p><header class="chat-header chat-header-focus">
<button id="chatSidebarToggle" class="icon-button sidebar-toggle" type="button" aria-controls="chatSidebar" aria-expanded="false" aria-label="Open conversation history">History</button>
<div class="chat-title-block">
<div class="chat-title">
<h2 id="activeChatTitle">New chat</h2>
<body class="site-page chat-page" data-theme="fakegpt" data-theme-mode="light">
<!-- site-header -->
<div class="app-shell">
<section id="chat" class="card chat-card chat-focus-card hidden" aria-label="AI chat workspace">
<div class="chat-layout chat-layout-focus">
<aside id="chatSidebar" class="chat-sidebar" aria-label="Conversation history">
<div class="sidebar-top">
<div class="sidebar-workspace-card">
<h2>Conversations</h2><button id="newChat" class="primary-action" type="button">New
chat</button>
</div>
<div class="chat-header-context" aria-label="Current chat setup">
<button id="headerModelButton" class="chat-context-item chat-context-button secondary-action" type="button" aria-controls="chatDrawer" aria-label="Change AI model"><span>Model</span><strong id="headerModelName">Loading...</strong></button>
<span class="chat-context-item"><span>Persona</span><strong id="headerPersonaName">None</strong></span>
<label class="field compact-field">
<span>Search</span>
<input id="chatSearch" type="search" placeholder="Search chats" />
</label>
</div>
<div class="sidebar-middle">
<div id="chatList" class="chat-list chat-list-grouped"></div>
<div id="chatListLoading" class="chat-loading hidden" role="status" aria-live="polite">
<span class="chat-loading-dot" aria-hidden="true"></span>
<span id="chatListLoadingText">Loading chats...</span>
</div>
</div>
<div class="chat-header-actions">
<div class="chat-actions-menu">
<button id="chatDrawerButton" class="secondary-action overflow-trigger" type="button" aria-expanded="false" aria-controls="chatDrawer">
<span class="overflow-trigger-label">Chat settings</span>
</button>
<div id="chatDrawer" class="chat-drawer hidden" role="dialog" aria-modal="true" aria-labelledby="chatDrawerTitle">
<div class="chat-drawer-panel">
<div class="chat-drawer-header">
<div>
<p class="eyebrow">Conversation</p>
<h2 id="chatDrawerTitle">Settings</h2>
</div>
<button id="chatDrawerClose" class="icon-button" type="button" aria-label="Close chat settings">×</button>
</div>
<div class="chat-drawer-body">
<div class="chat-drawer-summary">
<div class="chat-drawer-pills">
<span id="chatCharacterPill" class="context-pill hidden">Character: none</span>
<span id="chatUserPersonaPill" class="context-pill hidden">User persona: none</span>
</aside>
<section id="main" class="chat-main chat-main-focus" aria-label="Active conversation">
<div class="chat-heading">
<p id="workspaceNotice" class="status error hidden" role="status" aria-live="polite"></p>
<header class="chat-header chat-header-focus">
<button id="chatSidebarToggle" class="icon-button sidebar-toggle" type="button"
aria-controls="chatSidebar" aria-expanded="false"
aria-label="Open conversation history">History</button>
<div class="chat-title-block">
<div class="chat-title">
<h2 id="activeChatTitle">New chat</h2>
</div>
<div class="chat-header-context" aria-label="Current chat setup">
<button id="headerModelButton"
class="chat-context-item chat-context-button secondary-action" type="button"
aria-controls="chatDrawer"
aria-label="Change AI model"><span>Model</span><strong
id="headerModelName">Loading...</strong></button>
<span class="chat-context-item"><span>Persona</span><strong
id="headerPersonaName">None</strong></span>
</div>
</div>
<div class="chat-header-actions">
<div class="chat-actions-menu">
<button id="chatDrawerButton" class="secondary-action overflow-trigger"
type="button" aria-expanded="false" aria-controls="chatDrawer">
<span class="overflow-trigger-label">Chat settings</span>
</button>
<div id="chatDrawer" class="chat-drawer hidden" role="dialog" aria-modal="true"
aria-labelledby="chatDrawerTitle">
<div class="chat-drawer-panel">
<div class="chat-drawer-header">
<div>
<p class="eyebrow">Conversation</p>
<h2 id="chatDrawerTitle">Settings</h2>
</div>
<button id="chatDrawerClose" class="icon-button" type="button"
aria-label="Close chat settings">×</button>
</div>
</div>
<details class="chat-drawer-section chat-drawer-disclosure chat-drawer-section--model" open>
<summary class="chat-drawer-row">
<span class="chat-drawer-row-label">
<span class="eyebrow">AI model</span>
<strong>Model</strong>
</span>
<span class="chat-drawer-row-meta">
<strong id="modelBadgeName">Loading...</strong>
<span class="chat-drawer-row-hint">Change</span>
</span>
</summary>
<div id="modelPanel" class="model-panel chat-drawer-disclosure-panel" data-static-panel="true">
<div class="model-select">
<div class="model-select-header">
<label for="model">Model</label>
<span id="modelCount" class="model-count">Loading</span>
</div>
<div class="model-select-control">
<select id="model">
<option disabled selected>Loading models...</option>
</select>
<span class="model-select-chevron" aria-hidden="true"></span>
<div class="chat-drawer-body">
<div class="chat-drawer-summary">
<div class="chat-drawer-pills">
<span id="chatCharacterPill"
class="context-pill hidden">Character: none</span>
<span id="chatUserPersonaPill" class="context-pill hidden">User
persona: none</span>
</div>
</div>
<div id="modelHelp" class="model-help" aria-live="polite">
<div class="model-help-header">
<strong id="modelHelpTitle">Choose a model</strong>
<span id="modelHelpBadge" class="model-help-badge">Waiting</span>
</div>
<p id="modelHelpSummary" class="model-help-summary">Pick a model to see what it is best suited for.</p>
</div>
<div class="generation-settings-grid">
<label class="field"><span>Temperature</span><input id="chatTemperature" type="number" min="0" max="2" step="0.1" placeholder="Default"/></label>
<label class="field"><span>Context length</span><input id="chatContextLength" type="number" min="256" max="131072" placeholder="Default"/></label>
<label class="field"><span>Response length</span><input id="chatResponseLength" type="number" min="1" max="32768" placeholder="Default"/></label>
<label class="field field-wide"><span>System instruction</span><textarea id="chatSystemInstruction" rows="4" maxlength="8000" placeholder="Optional instructions for this chat"></textarea></label>
<button id="saveGenerationSettings" type="button">Save generation settings</button>
</div>
</div>
</details>
<details class="chat-drawer-section chat-drawer-disclosure chat-drawer-section--persona">
<summary class="chat-drawer-row">
<span class="chat-drawer-row-label">
<span class="eyebrow">Your identity</span>
<strong>Persona</strong>
</span>
<span class="chat-drawer-row-meta">
<strong id="chatDrawerPersonaValue">None</strong>
<span class="chat-drawer-row-hint">Change</span>
</span>
</summary>
<div id="personaPanel" class="persona-panel chat-drawer-disclosure-panel" data-static-panel="true">
<div class="persona-section">
<div class="persona-section-header">
<div>
<h2>Your persona</h2>
<details
class="chat-drawer-section chat-drawer-disclosure chat-drawer-section--model"
open>
<summary class="chat-drawer-row">
<span class="chat-drawer-row-label">
<span class="eyebrow">AI model</span>
<strong>Model</strong>
</span>
<span class="chat-drawer-row-meta">
<strong id="modelBadgeName">Loading...</strong>
<span class="chat-drawer-row-hint">Change</span>
</span>
</summary>
<div id="modelPanel"
class="model-panel chat-drawer-disclosure-panel"
data-static-panel="true">
<div class="model-select">
<div class="model-select-header">
<label for="model">Model</label>
<span id="modelCount" class="model-count">Loading</span>
</div>
<div class="model-select-control">
<select id="model">
<option disabled selected>Loading models...</option>
</select>
<span class="model-select-chevron"
aria-hidden="true"></span>
</div>
</div>
<div class="persona-section-actions">
<button id="clearUserPersona" class="secondary-action" type="button">Clear</button>
<a href="/app/personas" class="secondary-action">Manage</a>
<div id="modelHelp" class="model-help" aria-live="polite">
<div class="model-help-header">
<strong id="modelHelpTitle">Choose a model</strong>
<span id="modelHelpBadge"
class="model-help-badge">Waiting</span>
</div>
<p id="modelHelpSummary" class="model-help-summary">Pick a
model to see what it is best suited for.</p>
</div>
<div class="generation-settings-grid">
<label class="field"><span>Temperature</span><input
id="chatTemperature" type="number" min="0" max="2"
step="0.1" placeholder="Default" /></label>
<label class="field"><span>Context length</span><input
id="chatContextLength" type="number" min="256"
max="131072" placeholder="Default" /></label>
<label class="field"><span>Response length</span><input
id="chatResponseLength" type="number" min="1"
max="32768" placeholder="Default" /></label>
<label class="field field-wide"><span>System
instruction</span><textarea
id="chatSystemInstruction" rows="4" maxlength="8000"
placeholder="Optional instructions for this chat"></textarea></label>
<button id="saveGenerationSettings" type="button">Save
generation settings</button>
</div>
</div>
<p id="activeUserPersonaStatus" class="status persona-status">No default user persona set.</p>
<div id="userPersonaList" class="persona-list"></div>
</details>
<details
class="chat-drawer-section chat-drawer-disclosure chat-drawer-section--persona">
<summary class="chat-drawer-row">
<span class="chat-drawer-row-label">
<span class="eyebrow">Your identity</span>
<strong>Persona</strong>
</span>
<span class="chat-drawer-row-meta">
<strong id="chatDrawerPersonaValue">None</strong>
<span class="chat-drawer-row-hint">Change</span>
</span>
</summary>
<div id="personaPanel"
class="persona-panel chat-drawer-disclosure-panel"
data-static-panel="true">
<div class="persona-section">
<div class="persona-section-header">
<div>
<h2>Your persona</h2>
</div>
<div class="persona-section-actions">
<button id="clearUserPersona"
class="secondary-action"
type="button">Clear</button>
<a href="/app/personas"
class="secondary-action">Manage</a>
</div>
</div>
<p id="activeUserPersonaStatus"
class="status persona-status">No default user persona
set.</p>
<div id="userPersonaList" class="persona-list"></div>
</div>
</div>
</details>
<div class="chat-drawer-section">
<p class="dropdown-section-label">Chat</p>
<div class="chat-drawer-action-list">
<button id="renameChat"
class="secondary-action chat-drawer-action" type="button"
data-chat-action="rename">Rename chat</button>
<button id="pinChat" class="secondary-action chat-drawer-action"
type="button" data-chat-action="pin">Pin</button>
<button id="archiveChat"
class="secondary-action chat-drawer-action" type="button"
data-chat-action="archive">Archive</button>
<button id="exportChat"
class="secondary-action chat-drawer-action" type="button"
data-chat-action="export">Export</button>
</div>
</div>
</div>
</details>
<div class="chat-drawer-section">
<p class="dropdown-section-label">Chat</p>
<div class="chat-drawer-action-list">
<button id="renameChat" class="secondary-action chat-drawer-action" type="button" data-chat-action="rename">Rename chat</button>
<button id="pinChat" class="secondary-action chat-drawer-action" type="button" data-chat-action="pin">Pin</button>
<button id="archiveChat" class="secondary-action chat-drawer-action" type="button" data-chat-action="archive">Archive</button>
<button id="exportChat" class="secondary-action chat-drawer-action" type="button" data-chat-action="export">Export</button>
<details class="chat-drawer-section chat-drawer-more">
<summary class="chat-drawer-row chat-drawer-row--compact">
<span class="chat-drawer-row-label">
<strong>Tools &amp; workspace</strong>
</span>
<span class="chat-drawer-row-hint">Show</span>
</summary>
<div class="chat-drawer-action-list chat-drawer-disclosure-panel">
<button id="moveChatFolder"
class="secondary-action chat-drawer-action" type="button"
data-chat-action="folder">Move to folder</button>
<button id="manageChatMemory"
class="secondary-action chat-drawer-action" type="button"
data-chat-action="memory">Memory facts</button>
<button id="managePersonaState"
class="secondary-action chat-drawer-action" type="button"
data-chat-action="personaState">Roleplay state</button>
<button id="backupWorkspace"
class="secondary-action chat-drawer-action"
type="button">Backup workspace</button>
<button id="importWorkspace"
class="secondary-action chat-drawer-action"
type="button">Import workspace</button>
<input id="importWorkspaceFile" class="hidden" type="file"
accept="application/json,.json" />
</div>
</details>
<details class="chat-drawer-section chat-drawer-danger">
<summary class="chat-drawer-row chat-drawer-row--compact">
<span class="chat-drawer-row-label">
<strong>Clear conversation</strong>
</span>
<span class="chat-drawer-row-hint">Show</span>
</summary>
<div class="chat-drawer-action-list chat-drawer-disclosure-panel">
<button id="clearChat"
class="secondary-action chat-drawer-action chat-drawer-action-danger"
type="button" data-chat-action="clear">Clear chat</button>
</div>
</details>
</div>
</div>
<details class="chat-drawer-section chat-drawer-more">
<summary class="chat-drawer-row chat-drawer-row--compact">
<span class="chat-drawer-row-label">
<strong>Tools &amp; workspace</strong>
</span>
<span class="chat-drawer-row-hint">Show</span>
</summary>
<div class="chat-drawer-action-list chat-drawer-disclosure-panel">
<button id="moveChatFolder" class="secondary-action chat-drawer-action" type="button" data-chat-action="folder">Move to folder</button>
<button id="manageChatMemory" class="secondary-action chat-drawer-action" type="button" data-chat-action="memory">Memory facts</button>
<button id="managePersonaState" class="secondary-action chat-drawer-action" type="button" data-chat-action="personaState">Roleplay state</button>
<button id="backupWorkspace" class="secondary-action chat-drawer-action" type="button">Backup workspace</button>
<button id="importWorkspace" class="secondary-action chat-drawer-action" type="button">Import workspace</button>
<input id="importWorkspaceFile" class="hidden" type="file" accept="application/json,.json"/>
</div>
</details>
<details class="chat-drawer-section chat-drawer-danger">
<summary class="chat-drawer-row chat-drawer-row--compact">
<span class="chat-drawer-row-label">
<strong>Clear conversation</strong>
</span>
<span class="chat-drawer-row-hint">Show</span>
</summary>
<div class="chat-drawer-action-list chat-drawer-disclosure-panel">
<button id="clearChat" class="secondary-action chat-drawer-action chat-drawer-action-danger" type="button" data-chat-action="clear">Clear chat</button>
</div>
</details>
</div>
</div>
</div>
</header>
</div>
<div id="chatActivityOverlay" class="chat-activity-overlay hidden" role="status" aria-live="polite"
aria-atomic="true">
<div class="chat-activity-card">
<div class="chat-activity-mark" aria-hidden="true">
<span></span>
<span></span>
<span></span>
</div>
<p id="chatActivityEyebrow" class="eyebrow">Please wait</p>
<h3 id="chatActivityTitle">Preparing chat</h3>
<p id="chatActivityDetail" class="subtitle">The assistant is still working.</p>
</div>
</div>
</header></div>
<div id="chatActivityOverlay" class="chat-activity-overlay hidden" role="status" aria-live="polite" aria-atomic="true">
<div class="chat-activity-card">
<div class="chat-activity-mark" aria-hidden="true">
<span></span>
<span></span>
<span></span>
<div id="messages" class="messages-panel messages-panel-focus" role="log" aria-live="polite"
aria-relevant="additions text"></div>
<div class="input-shell input-shell-focus">
<div class="input-meta">
<p class="composer-hint">Enter to send · Shift+Enter for new line</p>
</div>
<div id="uploadPreviews" class="upload-previews" aria-live="polite" hidden></div>
<div class="input-container">
<input id="imagePicker" type="file" accept="image/png,image/jpeg,image/webp" multiple
hidden>
<button id="attachImage" class="icon-button" type="button" aria-label="Attach image"
title="Attach image">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.6" aria-hidden="true">
<rect x="3" y="3" width="18" height="18" rx="3" />
<circle cx="8" cy="8" r="1.5" />
<path d="m3 17 5-5 4 4 4-6 5 7" />
</svg>
</button>
<button id="webSearch" class="icon-button" type="button" aria-label="Search the web"
title="Search the web" aria-pressed="false">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.6" aria-hidden="true">
<circle cx="12" cy="12" r="9" />
<ellipse cx="12" cy="12" rx="4" ry="9" />
<path d="M3 12h18" />
</svg>
</button>
<button id="reasoning" class="icon-button" type="button" aria-label="Enable reasoning"
title="Enable reasoning" aria-pressed="false" disabled>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true">
<path
d="M12 18V5a3 3 0 0 0-5.8-1A4 4 0 0 0 3 10a4 4 0 0 0 1 7.8A4 4 0 0 0 12 18ZM12 5a3 3 0 0 1 5.8-1A4 4 0 0 1 21 10a4 4 0 0 1-1 7.8A4 4 0 0 1 12 18M8 8a4 4 0 0 1-5 2m13-2a4 4 0 0 0 5 2M8 14a4 4 0 0 0-4 4m12-4a4 4 0 0 1 4 4" />
</svg>
</button>
<label class="visually-hidden" for="msgInput">Message</label>
<textarea id="msgInput" rows="1" placeholder="Type your message..."></textarea>
<button id="send" type="button" aria-label="Send message"></button>
</div>
<p id="chatActivityEyebrow" class="eyebrow">Please wait</p>
<h3 id="chatActivityTitle">Preparing chat</h3>
<p id="chatActivityDetail" class="subtitle">The assistant is still working.</p>
</div>
</div>
<div id="messages" class="messages-panel messages-panel-focus" role="log" aria-live="polite" aria-relevant="additions text"></div>
<div class="input-shell input-shell-focus">
<div class="input-meta">
<p class="composer-hint">Enter to send · Shift+Enter for new line</p>
</div>
<div id="uploadPreviews" class="upload-previews" aria-live="polite" hidden></div>
<div class="input-container">
<input id="imagePicker" type="file" accept="image/png,image/jpeg,image/webp" multiple hidden>
<button id="attachImage" class="icon-button" type="button" aria-label="Attach image" title="Attach image">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="3"/><circle cx="8" cy="8" r="1.5"/><path d="m3 17 5-5 4 4 4-6 5 7"/></svg>
</button>
<button id="webSearch" class="icon-button" type="button" aria-label="Search the web" title="Search the web" aria-pressed="false">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><circle cx="12" cy="12" r="9"/><ellipse cx="12" cy="12" rx="4" ry="9"/><path d="M3 12h18"/></svg>
</button>
<button id="reasoning" class="icon-button" type="button" aria-label="Enable reasoning" title="Enable reasoning" aria-pressed="false" disabled>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 18V5a3 3 0 0 0-5.8-1A4 4 0 0 0 3 10a4 4 0 0 0 1 7.8A4 4 0 0 0 12 18ZM12 5a3 3 0 0 1 5.8-1A4 4 0 0 1 21 10a4 4 0 0 1-1 7.8A4 4 0 0 1 12 18M8 8a4 4 0 0 1-5 2m13-2a4 4 0 0 0 5 2M8 14a4 4 0 0 0-4 4m12-4a4 4 0 0 1 4 4"/></svg>
</button>
<label class="visually-hidden" for="msgInput">Message</label>
<textarea id="msgInput" rows="1" placeholder="Type your message..."></textarea>
<button id="send" type="button" aria-label="Send message"></button>
</div>
</div>
</section>
</div>
</section>
</div>
<div id="personaModal" class="modal-overlay side-panel-overlay hidden">
<div class="modal-card persona-modal-card side-panel-card">
<div class="modal-header">
<div class="persona-modal-heading">
<p class="eyebrow">Persona editor</p>
<h3 id="personaFormTitle">Create persona</h3>
</section>
</div>
<button id="personaClose" type="button" class="icon-button">Close</button>
</div>
<form id="personaForm" class="persona-form">
<div class="persona-form-intro">
<p class="persona-helper">AI Characters shape who you talk to. User personas add context for who you are in the scene.</p>
</section>
</div>
<div id="personaModal" class="modal-overlay side-panel-overlay hidden">
<div class="modal-card persona-modal-card side-panel-card">
<div class="modal-header">
<div class="persona-modal-heading">
<p class="eyebrow">Persona editor</p>
<h3 id="personaFormTitle">Create persona</h3>
</div>
<button id="personaClose" type="button" class="icon-button">Close</button>
</div>
<p id="personaFormNotice" class="status hidden"></p>
<div class="persona-form-grid">
<label class="field field-wide">
<span>Persona for</span>
<select id="personaType">
<option value="assistant">AI Character</option>
<option value="user">Your persona</option>
</select>
</label>
<label class="field">
<span>Persona name</span>
<input id="personaName" placeholder="Persona name" required/>
</label>
<label class="field">
<span>Persona pronouns</span>
<input id="personaPronouns" placeholder="e.g. she/her, they/them"/>
</label>
<label class="field field-wide">
<span>Appearance</span>
<textarea id="personaAppearance" rows="3" placeholder="Describe their look"></textarea>
</label>
<label class="field field-wide">
<span>Background</span>
<textarea id="personaBackground" rows="3" placeholder="Backstory, history, role, or origins"></textarea>
</label>
<label class="field field-wide">
<span>Details</span>
<textarea id="personaDetails" rows="4" placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
</label>
<label id="personaExamplesField" class="field field-wide hidden">
<span>Example dialogues</span>
<div class="dialogue-format-card" aria-hidden="true">
<p class="dialogue-format-label">Format</p>
<pre class="dialogue-format-example">{{character}}: Hello, my name is marc
<form id="personaForm" class="persona-form">
<div class="persona-form-intro">
<p class="persona-helper">AI Characters shape who you talk to. User personas add context for who you
are in the scene.</p>
</div>
<p id="personaFormNotice" class="status hidden"></p>
<div class="persona-form-grid">
<label class="field field-wide">
<span>Persona for</span>
<select id="personaType">
<option value="assistant">AI Character</option>
<option value="user">Your persona</option>
</select>
</label>
<label class="field">
<span>Persona name</span>
<input id="personaName" placeholder="Persona name" required />
</label>
<label class="field">
<span>Persona pronouns</span>
<input id="personaPronouns" placeholder="e.g. she/her, they/them" />
</label>
<label class="field field-wide">
<span>Appearance</span>
<textarea id="personaAppearance" rows="3" placeholder="Describe their look"></textarea>
</label>
<label class="field field-wide">
<span>Background</span>
<textarea id="personaBackground" rows="3"
placeholder="Backstory, history, role, or origins"></textarea>
</label>
<label class="field field-wide">
<span>Details</span>
<textarea id="personaDetails" rows="4"
placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
</label>
<label id="personaExamplesField" class="field field-wide hidden">
<span>Example dialogues</span>
<div class="dialogue-format-card" aria-hidden="true">
<p class="dialogue-format-label">Format</p>
<pre class="dialogue-format-example">{{character}}: Hello, my name is marc
{{user}}: Hi, Marc!
{{character}}: Nice to meet you</pre>
</div>
<textarea id="personaExampleDialogues" rows="5" placeholder="Write example exchanges that define the AI's tone, phrasing, pacing, and personality."></textarea>
</div>
<textarea id="personaExampleDialogues" rows="5"
placeholder="Write example exchanges that define the AI's tone, phrasing, pacing, and personality."></textarea>
</label>
</div>
<div class="persona-actions">
<button id="personaSave" type="submit" class="primary-action">Save persona</button>
</div>
</form>
</div>
</div>
<div id="roleplayStarterModal" class="modal-overlay side-panel-overlay hidden">
<div class="modal-card roleplay-starter-card side-panel-card">
<div class="modal-header">
<div class="persona-modal-heading">
<p class="eyebrow">Start roleplay</p>
<h3 id="roleplayStarterTitle">Choose your roleplay</h3>
</div>
<button id="roleplayStarterClose" type="button" class="icon-button">Close</button>
</div>
<div class="roleplay-starter-intro">
<p id="roleplayStarterIntroCopy" class="persona-helper">Pick a character and choose who you are before
moving on to the opening scene.</p>
</div>
<p id="roleplayStarterNotice" class="status hidden"></p>
<div class="roleplay-starter-actions">
<button id="roleplayNewPersona" class="secondary-action" type="button">Create AI Character</button>
</div>
<div id="roleplayStarterStepOne" class="persona-form-grid">
<label class="field field-wide">
<span>1. Character</span>
<select id="roleplayCharacterSelect"></select>
</label>
<label class="field field-wide">
<span>2. You in the scene</span>
<select id="roleplayUserPersonaSelect"></select>
</label>
</div>
<div class="persona-actions">
<button id="personaSave" type="submit" class="primary-action">Save persona</button>
<div id="roleplayStarterStepTwo" class="roleplay-starter-page hidden">
<label class="field field-wide">
<span>Optional opening direction</span>
<textarea id="roleplayScenarioInput" rows="4"
placeholder="Describe the kind of opening scene you want, such as a setting, conflict, relationship dynamic, or mood. Leave it empty and the AI will invent its own scenario."></textarea>
</label>
<p class="subtitle">Leave this empty if you want the AI Character to invent the scenario on its own. No
character limit.</p>
<div id="roleplayStarterSuggestions" class="quick-prompts" aria-label="Scenario suggestions"></div>
</div>
</form>
</div>
</div>
<div id="roleplayStarterModal" class="modal-overlay side-panel-overlay hidden">
<div class="modal-card roleplay-starter-card side-panel-card">
<div class="modal-header">
<div class="persona-modal-heading">
<p class="eyebrow">Start roleplay</p>
<h3 id="roleplayStarterTitle">Choose your roleplay</h3>
<div class="popup-actions">
<button id="roleplayStarterCancel" type="button" class="secondary-action">Cancel</button>
<button id="roleplayStarterBack" type="button" class="secondary-action hidden">Back</button>
<button id="roleplayStarterConfirm" type="button">Continue</button>
</div>
<button id="roleplayStarterClose" type="button" class="icon-button">Close</button>
</div>
<div class="roleplay-starter-intro">
<p id="roleplayStarterIntroCopy" class="persona-helper">Pick a character and choose who you are before moving on to the opening scene.</p>
</div>
<p id="roleplayStarterNotice" class="status hidden"></p>
<div class="roleplay-starter-actions">
<button id="roleplayNewPersona" class="secondary-action" type="button">Create AI Character</button>
</div>
<div id="roleplayStarterStepOne" class="persona-form-grid">
<label class="field field-wide">
<span>1. Character</span>
<select id="roleplayCharacterSelect"></select>
</label>
<label class="field field-wide">
<span>2. You in the scene</span>
<select id="roleplayUserPersonaSelect"></select>
</label>
</div>
<div id="roleplayStarterStepTwo" class="roleplay-starter-page hidden">
<label class="field field-wide">
<span>Optional opening direction</span>
<textarea id="roleplayScenarioInput" rows="4" placeholder="Describe the kind of opening scene you want, such as a setting, conflict, relationship dynamic, or mood. Leave it empty and the AI will invent its own scenario."></textarea>
</label>
<p class="subtitle">Leave this empty if you want the AI Character to invent the scenario on its own. No character limit.</p>
<div id="roleplayStarterSuggestions" class="quick-prompts" aria-label="Scenario suggestions"></div>
</div>
<div class="popup-actions">
<button id="roleplayStarterCancel" type="button" class="secondary-action">Cancel</button>
<button id="roleplayStarterBack" type="button" class="secondary-action hidden">Back</button>
<button id="roleplayStarterConfirm" type="button">Continue</button>
</div>
</div>
</div>
<div id="popupModal" class="modal-overlay hidden">
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="popupTitle" aria-describedby="popupDescription">
<div class="modal-header">
<div class="persona-modal-heading">
<p id="popupEyebrow" class="eyebrow">Action</p>
<h3 id="popupTitle">Confirm action</h3>
<div id="popupModal" class="modal-overlay hidden">
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="popupTitle"
aria-describedby="popupDescription">
<div class="modal-header">
<div class="persona-modal-heading">
<p id="popupEyebrow" class="eyebrow">Action</p>
<h3 id="popupTitle">Confirm action</h3>
</div>
<button id="popupClose" type="button" class="icon-button">Close</button>
</div>
<div class="popup-content">
<p id="popupDescription" class="subtitle">Please confirm this action.</p>
<label id="popupField" class="field hidden">
<span id="popupInputLabel">Value</span>
<input id="popupInput" type="text" autocomplete="off" />
</label>
</div>
<div class="popup-actions">
<button id="popupCancel" type="button" class="secondary-action">Cancel</button>
<button id="popupConfirm" type="button">Confirm</button>
</div>
<button id="popupClose" type="button" class="icon-button">Close</button>
</div>
<div class="popup-content">
<p id="popupDescription" class="subtitle">Please confirm this action.</p>
<label id="popupField" class="field hidden">
<span id="popupInputLabel">Value</span>
<input id="popupInput" type="text" autocomplete="off"/>
</label>
</div>
<div class="popup-actions">
<button id="popupCancel" type="button" class="secondary-action">Cancel</button>
<button id="popupConfirm" type="button">Confirm</button>
</div>
</div>
</div>
<div id="onboardingModal" class="modal-overlay hidden">
<div class="modal-card onboarding-card">
<div class="modal-header">
<div class="persona-modal-heading">
<p class="eyebrow">Get started</p>
<h3 id="onboardingTitle">Where shall we start?</h3>
<div id="onboardingModal" class="modal-overlay hidden">
<div class="modal-card onboarding-card">
<div class="modal-header">
<div class="persona-modal-heading">
<p class="eyebrow">Get started</p>
<h3 id="onboardingTitle">Where shall we start?</h3>
</div>
<button id="onboardingSkip" type="button" class="icon-button">Skip</button>
</div>
<p id="onboardingSubtitle" class="subtitle">Ask a question, explore an idea, or step into a story.</p>
<div id="onboardingChoices" class="onboarding-choices"></div>
<div class="popup-actions">
<button id="onboardingBack" type="button" class="secondary-action hidden">Back</button>
<button id="onboardingContinue" type="button">Continue</button>
</div>
<button id="onboardingSkip" type="button" class="icon-button">Skip</button>
</div>
<p id="onboardingSubtitle" class="subtitle">Ask a question, explore an idea, or step into a story.</p>
<div id="onboardingChoices" class="onboarding-choices"></div>
<div class="popup-actions">
<button id="onboardingBack" type="button" class="secondary-action hidden">Back</button>
<button id="onboardingContinue" type="button">Continue</button>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js" defer></script>
<script type="module" src="/script.js?v=20260921-reasoning-capability"></script>
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js" defer></script>
<script type="module" src="/js/script.js?v=20260921-reasoning-capability"></script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
</html>
</html>
+36 -30
View File
@@ -1,37 +1,43 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Discover personas &middot; Elsewhere</title>
<link rel="icon" href="/favicon.svg">
<script src="/app/theme-bootstrap.js?v=20260909-account-controls">
</script>
<link rel="stylesheet" href="/style.css?v=20260909-account-controls">
<link rel="stylesheet" href="/pages.css?v=20260909-account-controls">
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Discover personas &middot; Elsewhere</title>
<link rel="icon" href="/img/favicon.svg">
<script src="/js/theme-bootstrap.js?v=20260909-account-controls">
</script>
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls">
<link rel="stylesheet" href="/css/pages.css?v=20260909-account-controls">
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet">
</head>
<body class="site-page discover-page">
<!-- site-header -->
<main id="main" class="site-section page-content">
<p class="eyebrow">From the community</p>
<h1>Find your next conversation.</h1>
<p class="hero-copy">Discover characters and personas made by the community. Sign in when you're ready to bring one into your workspace.</p>
<label class="field">
<span>Search personas</span>
<input id="publicSearch" type="search" placeholder="Search by name or description">
</label>
<div id="featuredPersonas" class="feature-grid" aria-live="polite">Loading personas&hellip;</div>
</main>
<footer class="site-footer">
<a class="site-brand" href="/">elsewhere</a>
<span>A space for every conversation.</span>
<a href="/privacy.html">Privacy</a>
<a href="/tos.html">Terms</a>
</footer>
<script type="module" src="/pages.js?v=20260909-account-controls">
</script>
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
<!-- site-header -->
<main id="main" class="site-section page-content">
<p class="eyebrow">From the community</p>
<h1>Find your next conversation.</h1>
<p class="hero-copy">Discover characters and personas made by the community. Sign in when you're ready to bring
one into your workspace.</p>
<label class="field">
<span>Search personas</span>
<input id="publicSearch" type="search" placeholder="Search by name or description">
</label>
<div id="featuredPersonas" class="feature-grid" aria-live="polite">Loading personas&hellip;</div>
</main>
<footer class="site-footer">
<a class="site-brand" href="/">elsewhere</a>
<span>A space for every conversation.</span>
<a href="/privacy.html">Privacy</a>
<a href="/tos.html">Terms</a>
</footer>
<script type="module" src="/js/pages.js?v=20260909-account-controls">
</script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
</html>
+53 -48
View File
@@ -1,57 +1,62 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Home &middot; Elsewhere</title>
<link rel="icon" href="/favicon.svg">
<script src="/app/theme-bootstrap.js?v=20260909-account-controls">
</script>
<link rel="stylesheet" href="/style.css?v=20260909-account-controls">
<link rel="stylesheet" href="/pages.css?v=20260909-account-controls">
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Home &middot; Elsewhere</title>
<link rel="icon" href="/img/favicon.svg">
<script src="/js/theme-bootstrap.js?v=20260909-account-controls">
</script>
<link rel="stylesheet" href="/style.css?v=20260909-account-controls">
<link rel="stylesheet" href="/pages.css?v=20260909-account-controls">
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet">
</head>
<body class="site-page home-page">
<!-- site-header -->
<!-- site-header -->
<main id="main" class="home-main page-content">
<p id="welcomeName" class="eyebrow">Welcome home</p>
<h1>What would you like to do?</h1>
<p class="hero-copy">Start somewhere new, or pick up a conversation.</p>
<div class="feature-grid">
<a class="feature-card action-card" href="/app/chats/new">
<span class="feature-number">01 / ASK</span>
<h2>New conversation</h2>
<p>Make space for a question or an idea.</p>
<span>Start chatting &rarr;</span>
</a>
<a class="feature-card action-card" href="/market">
<span class="feature-number">02 / EXPLORE</span>
<h2>Chat with a character</h2>
<p>Find someone with a different perspective.</p>
<span>Discover personas &rarr;</span>
</a>
<a class="feature-card action-card" href="/app/personas?create=assistant">
<span class="feature-number">03 / CREATE</span>
<h2>Create a persona</h2>
<p>Give a new character a voice of their own.</p>
<span>Create persona &rarr;</span>
</a>
</div>
<section class="site-section">
<div class="section-heading">
<h2>Recent conversations</h2>
<a href="/app/chats">View all &rarr;</a>
</div>
<div id="recentChats" aria-live="polite">Loading conversations&hellip;</div>
</section>
<p id="homeStatus" role="status">
</p>
</main>
<main id="main" class="home-main page-content">
<p id="welcomeName" class="eyebrow">Welcome home</p>
<h1>What would you like to do?</h1>
<p class="hero-copy">Start somewhere new, or pick up a conversation.</p>
<div class="feature-grid">
<a class="feature-card action-card" href="/app/chats/new">
<span class="feature-number">01 / ASK</span>
<h2>New conversation</h2>
<p>Make space for a question or an idea.</p>
<span>Start chatting &rarr;</span>
</a>
<a class="feature-card action-card" href="/market">
<span class="feature-number">02 / EXPLORE</span>
<h2>Chat with a character</h2>
<p>Find someone with a different perspective.</p>
<span>Discover personas &rarr;</span>
</a>
<a class="feature-card action-card" href="/app/personas?create=assistant">
<span class="feature-number">03 / CREATE</span>
<h2>Create a persona</h2>
<p>Give a new character a voice of their own.</p>
<span>Create persona &rarr;</span>
</a>
</div>
<section class="site-section">
<div class="section-heading">
<h2>Recent conversations</h2>
<a href="/app/chats">View all &rarr;</a>
</div>
<div id="recentChats" aria-live="polite">Loading conversations&hellip;</div>
</section>
<p id="homeStatus" role="status">
</p>
</main>
<script type="module" src="/pages.js?v=20260909-account-controls">
</script>
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
<script type="module" src="/js/pages.js?v=20260909-account-controls">
</script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
</html>

Before

Width:  |  Height:  |  Size: 250 B

After

Width:  |  Height:  |  Size: 250 B

+16 -16
View File
@@ -1,7 +1,7 @@
import {get, post, put} from "./app/api.js?v=20260909-account-controls";
import {initializeAppearance} from "./app/themeController.js?v=20260909-account-controls";
import {clearStatus, setStatus} from "./app/statusNotice.js?v=20260909-account-controls";
import {populatePersonaForm, readPersonaForm} from "./app/personaForm.js?v=20260909-account-controls";
import { get, post, put } from "./js/api.js?v=20260909-account-controls";
import { initializeAppearance } from "./js/themeController.js?v=20260909-account-controls";
import { clearStatus, setStatus } from "./js/statusNotice.js?v=20260909-account-controls";
import { populatePersonaForm, readPersonaForm } from "./js/personaForm.js?v=20260909-account-controls";
const $ = (id) => document.getElementById(id);
const marketPersonaList = $("marketPersonaList");
@@ -70,14 +70,14 @@ const marketPersonaFields = {
};
const validMarketSorts = new Set(["best", "newest", "most_favorited", "most_popular", "top_rated", "alphabetical"]);
let marketPersonasByType = {assistant: [], user: []};
let marketPersonasByType = { assistant: [], user: [] };
let assistantPersonas = [];
let userPersonas = [];
let pendingMarketPersona = null;
let editingPersonaId = null;
let activeUserPersonaId = null;
let activeMarketView = "ai-characters";
let marketSorts = {assistant: "best", user: "best"};
let marketSorts = { assistant: "best", user: "best" };
let marketActivityDepth = 0;
let marketPreviewStep = 1;
let marketPopupResolver = null;
@@ -181,7 +181,7 @@ function syncMarketQuery() {
history.replaceState(null, "", `${window.location.pathname}?${params.toString()}`);
}
function setMarketSort(personaType, sort, {reload = true} = {}) {
function setMarketSort(personaType, sort, { reload = true } = {}) {
const normalizedType = personaType === "user" ? "user" : "assistant";
const normalizedSort = normalizeMarketSort(sort);
marketSorts[normalizedType] = normalizedSort;
@@ -474,7 +474,7 @@ async function ratePersona(marketId) {
if (raw === null) return;
const rating = Number(raw);
if (!rating) return;
const res = await post(`/personas/market/${marketId}/rate`, {rating});
const res = await post(`/personas/market/${marketId}/rate`, { rating });
if (res.error) return setMarketStatus(res.error, "error");
await refreshMarket();
setMarketStatus("Rating saved.", "success");
@@ -499,7 +499,7 @@ async function reportPersona(marketId) {
confirmLabel: "Submit report"
});
if (details === null) return;
const res = await post(`/personas/market/${marketId}/report`, {reason, details});
const res = await post(`/personas/market/${marketId}/report`, { reason, details });
if (res.error) return setMarketStatus(res.error, "error");
setMarketStatus("Report submitted.", "success");
}
@@ -525,7 +525,7 @@ async function loadMarketPersonas() {
async function collectMarketPersona(marketId) {
setMarketStatus("Collecting User Persona...");
const res = await post(`/personas/market/${marketId}/collect`, {equip: true});
const res = await post(`/personas/market/${marketId}/collect`, { equip: true });
if (res.error) {
setMarketStatus(res.error, "error");
return;
@@ -540,7 +540,7 @@ async function startMarketPersonaChat(marketId, userPersonaSelection, scenarioPr
return;
}
if (userPersonaSelection === "create_new") {
window.location.href = "/app/personas?view=my-roleplay-companions&create=user";
window.location.href = "/js/personas?view=my-roleplay-companions&create=user";
return;
}
@@ -623,7 +623,7 @@ if (marketCreateAiCharacterBtn) {
}
if (marketCreateUserPersonaBtn) {
marketCreateUserPersonaBtn.addEventListener("click", () => {
window.location.href = "/app/personas?view=my-roleplay-companions&create=user";
window.location.href = "/js/personas?view=my-roleplay-companions&create=user";
});
}
marketPreviewClose.addEventListener("click", closeMarketPreview);
@@ -633,7 +633,7 @@ marketPreviewModal.addEventListener("click", (event) => {
marketPreviewUserPersonaSelect.addEventListener("change", () => {
if (!pendingMarketPersona || pendingMarketPersona.persona_type !== "assistant") return;
if (marketPreviewUserPersonaSelect.value === "create_new") {
window.location.href = "/app/personas?view=my-roleplay-companions&create=user";
window.location.href = "/js/personas?view=my-roleplay-companions&create=user";
return;
}
marketPreviewConfirm.classList.toggle("hidden", !marketPreviewUserPersonaSelect.value);
@@ -673,10 +673,10 @@ marketPersonaForm.addEventListener("submit", (event) => {
});
window.addEventListener("load", async () => {
initializeAppearance({title: "Elsewhere Market", nameTargets: brandNameTargets});
initializeAppearance({ title: "Elsewhere Market", nameTargets: brandNameTargets });
const params = new URLSearchParams(window.location.search);
setMarketSort("assistant", params.get("assistantSort") || "best", {reload: false});
setMarketSort("user", params.get("userSort") || "best", {reload: false});
setMarketSort("assistant", params.get("assistantSort") || "best", { reload: false });
setMarketSort("user", params.get("userSort") || "best", { reload: false });
setMarketView(params.get("view") || "ai-characters");
await loadMarketPersonas();
const selectedPersona = [...marketPersonasByType.assistant, ...marketPersonasByType.user].find(persona => persona.id === Number(params.get("persona")));
+2 -2
View File
@@ -1,4 +1,4 @@
import {get, post} from "/app/api.js?v=20260909-account-controls";
import { get, post } from "/js/api.js?v=20260909-account-controls";
const params = new URLSearchParams(location.search);
function returnPath() {
@@ -65,7 +65,7 @@ if (form) {
showAccountStatus(register ? "Creating your account..." : "Signing in...");
const result = await post(register ? "/register" : "/login", {
username: form.elements.username.value.trim(), password: form.elements.password.value,
...(register ? {inviteCode: form.elements.inviteCode.value.trim()} : {})
...(register ? { inviteCode: form.elements.inviteCode.value.trim() } : {})
});
if (result.error) {
const duplicate = register && result.error === "User already exists";
+83 -83
View File
@@ -1,12 +1,12 @@
import {del, get, post, put, stream} from "./app/api.js?v=20260909-account-controls";
import {regenerateInline} from "./app/regeneration.js";
import {appendSources, createWebSearchToggle} from "./app/webSearch.js";
import {appendAttachments, createAttachmentComposer} from "./app/attachments.js";
import {createReasoningToggle, finishReasoning, updateReasoning} from "./app/reasoning.js?v=20260921-reasoning-capability";
import {closeChatDrawer, openChatDrawer} from "./app/chatDrawer.js?v=20260909-account-controls";
import {onboardingPrompts, requestedChatId} from "./app/constants.js?v=20260909-account-controls";
import {getModelProfile} from "./app/modelProfiles.js?v=20260909-account-controls";
import {applyTheme as applySharedTheme, applyThemeMode as applySharedThemeMode, readStoredAppearance} from "./app/themeController.js?v=20260909-account-controls";
import { del, get, post, put, stream } from "./js/api.js?v=20260909-account-controls";
import { regenerateInline } from "./js/regeneration.js";
import { appendSources, createWebSearchToggle } from "./js/webSearch.js";
import { appendAttachments, createAttachmentComposer } from "./js/attachments.js";
import { createReasoningToggle, finishReasoning, updateReasoning } from "./js/reasoning.js?v=20260921-reasoning-capability";
import { closeChatDrawer, openChatDrawer } from "./js/chatDrawer.js?v=20260909-account-controls";
import { onboardingPrompts, requestedChatId } from "./js/constants.js?v=20260909-account-controls";
import { getModelProfile } from "./js/modelProfiles.js?v=20260909-account-controls";
import { applyTheme as applySharedTheme, applyThemeMode as applySharedThemeMode, readStoredAppearance } from "./js/themeController.js?v=20260909-account-controls";
import {
activeChatTitle,
activeUserPersonaStatus,
@@ -103,7 +103,7 @@ import {
brandLogoTargets,
brandNameTargets,
userPersonaList
} from "./app/dom.js?v=20260909-account-controls";
} from "./js/dom.js?v=20260909-account-controls";
const headerModelButton = document.getElementById("headerModelButton");
const webSearchButton = document.getElementById("webSearch");
@@ -112,7 +112,7 @@ const reasoningToggle = createReasoningToggle(document.getElementById("reasoning
let isProcessing = false, activeChatId = null, editingPersonaId = null, editingPersonaType = "assistant", currentUsername = "";
let regenerationChatId = null;
const attachmentComposer = createAttachmentComposer({onChange: updateSendState, onError: message => setNotice(message, "error")});
const attachmentComposer = createAttachmentComposer({ onChange: updateSendState, onError: message => setNotice(message, "error") });
let chatSessions = [], currentMessages = [], assistantPersonas = [], userPersonas = [];
let activeUserPersonaId = null, currentSummary = null, publishedPersonaIds = new Set();
let popupResolver = null, popupMode = null, popupLastFocus = null;
@@ -129,7 +129,7 @@ let availableModelsById = new Map();
function setNotice(message = "", state = "") {
const notice = document.getElementById("workspaceNotice");
if (notice) { notice.textContent = state === "error" ? message : ""; notice.classList.toggle("hidden", state !== "error"); }
return {message, state};
return { message, state };
}
function setPersonaFormNotice(message = "", state = "") {
if (!personaFormNotice) return;
@@ -157,7 +157,7 @@ function setChatLoading(active, message = "Loading chats...") {
}
}
function setChatActivity(active, {eyebrow = "Please wait", title = "Preparing chat", detail = "The assistant is still working."} = {}) {
function setChatActivity(active, { eyebrow = "Please wait", title = "Preparing chat", detail = "The assistant is still working." } = {}) {
if (!chatActivityOverlay || !chatActivityEyebrow || !chatActivityTitle || !chatActivityDetail) return;
if (active) {
chatActivityDepth += 1;
@@ -274,7 +274,7 @@ function setMessageContent(el, content) {
const markdownParser = window.marked;
const purifier = window.DOMPurify;
if (markdownParser?.parse && purifier?.sanitize) {
el.innerHTML = purifier.sanitize(markdownParser.parse(content, {breaks: true}));
el.innerHTML = purifier.sanitize(markdownParser.parse(content, { breaks: true }));
} else {
el.textContent = content;
}
@@ -370,7 +370,7 @@ async function saveSelectedModel() {
if (!activeChatId || !modelSelect.value) return;
const chatId = activeChatId;
const selectedModel = modelSelect.value;
const res = await put(`/chats/${chatId}/model`, {model: selectedModel});
const res = await put(`/chats/${chatId}/model`, { model: selectedModel });
if (res.error || !res.chat) return setNotice(res.error || "Unable to switch models.", "error");
const chat = getChatById(chatId);
if (chat) Object.assign(chat, res.chat);
@@ -404,7 +404,7 @@ async function resolveMessageTarget(messageId, fallbackIndex = -1) {
if (messageId) {
const index = getMessageIndexById(messageId);
if (index >= 0) {
return {index, message: currentMessages[index]};
return { index, message: currentMessages[index] };
}
}
if (activeChatId) {
@@ -413,11 +413,11 @@ async function resolveMessageTarget(messageId, fallbackIndex = -1) {
if (messageId) {
const index = getMessageIndexById(messageId);
if (index >= 0) {
return {index, message: currentMessages[index]};
return { index, message: currentMessages[index] };
}
}
if (fallbackIndex >= 0 && fallbackIndex < currentMessages.length) {
return {index: fallbackIndex, message: currentMessages[fallbackIndex]};
return { index: fallbackIndex, message: currentMessages[fallbackIndex] };
}
return null;
}
@@ -488,7 +488,7 @@ async function openCreatedChat(chat) {
async function editChatMessage(messageId, fallbackIndex = -1) {
const target = await resolveMessageTarget(messageId, fallbackIndex);
if (!target) return setNotice("Message is not ready yet. Try again.", "error");
const {index, message} = target;
const { index, message } = target;
const nextContent = await promptPopup({
eyebrow: message.role === "user" ? "Your message" : "Assistant message",
title: "Edit message",
@@ -529,11 +529,11 @@ async function editChatMessage(messageId, fallbackIndex = -1) {
}
const endpoint = getMessageEndpoint(target);
if (!endpoint) return setNotice("Message is not ready yet. Try again.", "error");
const res = await put(endpoint, {content});
const res = await put(endpoint, { content });
if (res.error || !res.message) {
return setNotice(res.error || "Unable to edit message.", "error");
}
currentMessages[index] = {...currentMessages[index], ...res.message};
currentMessages[index] = { ...currentMessages[index], ...res.message };
syncRetryStateFromMessage(currentMessages[index]);
renderMessages();
setNotice("Message updated.", "success");
@@ -542,7 +542,7 @@ async function editChatMessage(messageId, fallbackIndex = -1) {
async function deleteChatMessage(messageId, fallbackIndex = -1) {
const target = await resolveMessageTarget(messageId, fallbackIndex);
if (!target) return setNotice("Message is not ready yet. Try again.", "error");
const {index, message} = target;
const { index, message } = target;
const confirmed = await confirmPopup({
eyebrow: message.role === "user" ? "Your message" : "Assistant message",
title: "Delete message",
@@ -582,7 +582,7 @@ async function retryLatestAssistantMessage(messageId, fallbackIndex = -1) {
const chatId = activeChatId;
const card = messagesDiv.querySelector(`[data-message-id="${target.message.id}"]`);
if (!card) return;
const payload = {model: modelSelect.value, webSearch: webSearchToggle.enabled, reasoning: reasoningToggle.enabled};
const payload = { model: modelSelect.value, webSearch: webSearchToggle.enabled, reasoning: reasoningToggle.enabled };
regenerationChatId = chatId;
setLoadingState(true, false);
try {
@@ -593,9 +593,9 @@ async function retryLatestAssistantMessage(messageId, fallbackIndex = -1) {
if (activeChatId !== chatId) return;
const index = getMessageIndexById(message.id);
if (index < 0) return;
currentMessages[index] = {...currentMessages[index], ...message, thinking: message.thinking, sources: message.sources};
currentMessages[index] = { ...currentMessages[index], ...message, thinking: message.thinking, sources: message.sources };
syncRetryStateFromMessage(currentMessages[index]);
const rendered = addMessage(currentMessages[index], false, false, {messageIndex: index, detached: true});
const rendered = addMessage(currentMessages[index], false, false, { messageIndex: index, detached: true });
card.replaceChildren(...rendered.childNodes);
}
});
@@ -626,9 +626,9 @@ async function switchResponseVersion(messageId, direction, fallbackIndex = -1) {
if (activeChatId !== chatId) return;
const index = getMessageIndexById(messageId);
if (index < 0) return;
currentMessages[index] = {...currentMessages[index], ...res.message, thinking: res.message.thinking, sources: res.message.sources};
currentMessages[index] = { ...currentMessages[index], ...res.message, thinking: res.message.thinking, sources: res.message.sources };
syncRetryStateFromMessage(currentMessages[index]);
const rendered = addMessage(currentMessages[index], false, false, {messageIndex: index, detached: true});
const rendered = addMessage(currentMessages[index], false, false, { messageIndex: index, detached: true });
card.replaceChildren(...rendered.childNodes);
} finally {
card.removeAttribute("aria-busy");
@@ -642,7 +642,7 @@ async function switchRetryVariant(messageId, direction, fallbackIndex = -1) {
if (!target) return setNotice("Message is not ready yet. Try again.", "error");
const siblingId = direction < 0 ? target.message.previousSiblingId : target.message.nextSiblingId;
if (!siblingId) return;
const res = await post(`/chats/${activeChatId}/branches/${siblingId}/activate`, {includeDescendants: true});
const res = await post(`/chats/${activeChatId}/branches/${siblingId}/activate`, { includeDescendants: true });
if (res.error) return setNotice(res.error, "error");
currentMessages = res.messages || [];
renderMessages();
@@ -658,7 +658,7 @@ async function branchFromMessage(messageId) {
});
if (!confirmed) return;
const sourceChatId = activeChatId;
const res = await post(`/chats/${sourceChatId}/messages/${messageId}/branch`, {confirmed: true});
const res = await post(`/chats/${sourceChatId}/messages/${messageId}/branch`, { confirmed: true });
if (res.error) return setNotice(res.error, "error");
await openCreatedChat(res.chat);
msgInput.focus();
@@ -689,7 +689,7 @@ function addMessage(contentOrMessage, isUser = false, isLoading = false, options
} else {
const message = typeof contentOrMessage === "object" && contentOrMessage
? contentOrMessage
: {id: null, role: isUser ? "user" : "bot", content: String(contentOrMessage || "")};
: { id: null, role: isUser ? "user" : "bot", content: String(contentOrMessage || "") };
const isMessageUser = message.role === "user";
msgDiv.dataset.messageId = message.id;
msgDiv.className = `msg ${isMessageUser ? "user" : "bot"}`;
@@ -746,32 +746,32 @@ function addMessage(contentOrMessage, isUser = false, isLoading = false, options
}
for (const versioned of [true, false]) {
if (Number((versioned ? message.versionCount : message.siblingCount) || 1) <= 1) continue;
const navigate = versioned ? switchResponseVersion : switchRetryVariant;
const prevBtn = document.createElement("button");
prevBtn.type = "button";
prevBtn.className = "msg-action-btn";
prevBtn.textContent = "Prev";
prevBtn.setAttribute("aria-label", versioned ? "Show previous response version" : "Show previous branch");
prevBtn.disabled = versioned ? message.versionIndex <= 0 : !message.previousSiblingId;
prevBtn.addEventListener("click", () => {
void navigate(message.id, -1, options.messageIndex ?? -1);
});
if (Number((versioned ? message.versionCount : message.siblingCount) || 1) <= 1) continue;
const navigate = versioned ? switchResponseVersion : switchRetryVariant;
const prevBtn = document.createElement("button");
prevBtn.type = "button";
prevBtn.className = "msg-action-btn";
prevBtn.textContent = "Prev";
prevBtn.setAttribute("aria-label", versioned ? "Show previous response version" : "Show previous branch");
prevBtn.disabled = versioned ? message.versionIndex <= 0 : !message.previousSiblingId;
prevBtn.addEventListener("click", () => {
void navigate(message.id, -1, options.messageIndex ?? -1);
});
const nextBtn = document.createElement("button");
nextBtn.type = "button";
nextBtn.className = "msg-action-btn";
nextBtn.textContent = "Next";
nextBtn.setAttribute("aria-label", versioned ? "Show next response version" : "Show next branch");
nextBtn.disabled = versioned ? message.versionIndex >= message.versionCount - 1 : !message.nextSiblingId;
nextBtn.addEventListener("click", () => {
void navigate(message.id, 1, options.messageIndex ?? -1);
});
const nextBtn = document.createElement("button");
nextBtn.type = "button";
nextBtn.className = "msg-action-btn";
nextBtn.textContent = "Next";
nextBtn.setAttribute("aria-label", versioned ? "Show next response version" : "Show next branch");
nextBtn.disabled = versioned ? message.versionIndex >= message.versionCount - 1 : !message.nextSiblingId;
nextBtn.addEventListener("click", () => {
void navigate(message.id, 1, options.messageIndex ?? -1);
});
const indexBadge = document.createElement("span");
indexBadge.className = "msg-variant-index";
indexBadge.textContent = versioned ? `${message.versionIndex + 1} / ${message.versionCount}` : `${Number(message.siblingIndex || 0) + 1}/${message.siblingCount}`;
actions.append(prevBtn, indexBadge, nextBtn);
const indexBadge = document.createElement("span");
indexBadge.className = "msg-variant-index";
indexBadge.textContent = versioned ? `${message.versionIndex + 1} / ${message.versionCount}` : `${Number(message.siblingIndex || 0) + 1}/${message.siblingCount}`;
actions.append(prevBtn, indexBadge, nextBtn);
}
msgDiv.append(header);
@@ -972,7 +972,7 @@ function updateModelHelp() {
}
const modelId = selectedOption.value;
const modelName = selectedOption.textContent || modelId;
const profile = getModelProfile(availableModelsById.get(modelId) || {model: modelId, name: modelName});
const profile = getModelProfile(availableModelsById.get(modelId) || { model: modelId, name: modelName });
if (modelBadgeName) modelBadgeName.textContent = modelName;
if (headerModelName) headerModelName.textContent = modelName;
modelHelpTitle.textContent = modelName;
@@ -993,7 +993,7 @@ function applyTheme(themeKey, persist = true) {
}
function applyThemeMode(modeKey, persist = true) {
applySharedThemeMode(modeKey, {persist});
applySharedThemeMode(modeKey, { persist });
}
function updateChatActionState() {
@@ -1067,10 +1067,10 @@ function renderChatList() {
return map;
}, new Map());
const groups = [
{label: "Pinned", items: filtered.filter((chat) => chat.is_pinned && !chat.archived_at)},
...Array.from(folderGroups.entries()).map(([label, items]) => ({label, items})),
{label: "Recent", items: filtered.filter((chat) => !chat.is_pinned && !chat.folder_name && !chat.archived_at)},
{label: "Archived", items: filtered.filter((chat) => chat.archived_at)}
{ label: "Pinned", items: filtered.filter((chat) => chat.is_pinned && !chat.archived_at) },
...Array.from(folderGroups.entries()).map(([label, items]) => ({ label, items })),
{ label: "Recent", items: filtered.filter((chat) => !chat.is_pinned && !chat.folder_name && !chat.archived_at) },
{ label: "Archived", items: filtered.filter((chat) => chat.archived_at) }
].filter((group) => group.items.length);
groups.forEach((group) => {
const section = document.createElement("section");
@@ -1276,10 +1276,10 @@ function openPopup(options) {
});
}
function confirmPopup(options) {
return openPopup({...options, mode: "confirm"}).then((result) => Boolean(result));
return openPopup({ ...options, mode: "confirm" }).then((result) => Boolean(result));
}
function promptPopup(options) {
return openPopup({...options, mode: "prompt"}).then((result) => typeof result === "string" ? result : null);
return openPopup({ ...options, mode: "prompt" }).then((result) => typeof result === "string" ? result : null);
}
function renderPersonaList(items, activeId, listElement, personaType) {
@@ -1303,9 +1303,9 @@ function renderPersonaList(items, activeId, listElement, personaType) {
tag.className = `persona-role-tag ${personaType === "user" ? "user" : "ai"}`; tag.textContent = personaType === "user" ? "You" : "AI";
title.textContent = persona.name; meta.textContent = persona.pronouns ? `Pronouns: ${persona.pronouns}` : "Pronouns: n/a";
[[personaType === "assistant" ? "Start roleplay" : "Set default", () => equipPersona(persona.id, personaType), personaType === "user" && persona.id === activeId],
["Edit", () => openPersonaForm(persona), false],
[publishedPersonaIds.has(persona.id) ? "Update listing" : "Publish", () => publishPersona(persona.id), false],
["Delete", () => deletePersona(persona.id), false]]
["Edit", () => openPersonaForm(persona), false],
[publishedPersonaIds.has(persona.id) ? "Update listing" : "Publish", () => publishPersona(persona.id), false],
["Delete", () => deletePersona(persona.id), false]]
.forEach(([label, handler, disabled]) => {
const btn = document.createElement("button");
btn.type = "button"; btn.textContent = label; btn.disabled = disabled; btn.addEventListener("click", handler); actions.appendChild(btn);
@@ -1486,7 +1486,7 @@ async function createNewChat() {
title: "Opening a new conversation",
detail: "Setting up a fresh chat so you can start sending messages."
});
const res = await post("/chats", {title: "New chat"});
const res = await post("/chats", { title: "New chat" });
if (res.error || !res.chat) {
setChatLoading(false);
setChatActivity(false);
@@ -1557,7 +1557,7 @@ async function renameChat(id) {
}) : "";
const title = (nextTitle || "").trim();
if (!chat || !title) return;
const res = await put(`/chats/${id}`, {title});
const res = await put(`/chats/${id}`, { title });
if (res.error) return setNotice(res.error, "error");
chat.title = title; updateWorkspaceCopy(); renderChatList(); setNotice("Chat renamed.", "success");
}
@@ -1629,7 +1629,7 @@ function exportCurrentChat() {
async function backupWorkspace() {
const response = await fetch("/exports/workspace");
const data = await response.json();
const blob = new Blob([JSON.stringify(data.workspace, null, 2)], {type: "application/json;charset=utf-8"});
const blob = new Blob([JSON.stringify(data.workspace, null, 2)], { type: "application/json;charset=utf-8" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
@@ -1663,7 +1663,7 @@ async function importWorkspaceFromFile(file) {
return setNotice("Import failed: choose a valid JSON workspace backup.", "error");
}
const preview = await post("/imports/workspace/preview", {workspace});
const preview = await post("/imports/workspace/preview", { workspace });
if (preview.error || preview.ok === false) {
setChatLoading(false);
const detail = (preview.errors || []).slice(0, 4).join("; ");
@@ -1722,7 +1722,7 @@ async function manageChatMemory() {
if (removed.error) return setNotice(removed.error, "error");
return setNotice("Memory fact deleted.", "success");
}
const saved = await post(`/chats/${activeChatId}/memories`, {fact: value});
const saved = await post(`/chats/${activeChatId}/memories`, { fact: value });
if (saved.error) return setNotice(saved.error, "error");
return setNotice("Memory fact saved.", "success");
}
@@ -1769,7 +1769,7 @@ async function managePersonaState() {
placeholder: label
});
if (value === null) return;
const nextState = {...state, [key]: String(value || "").trim() || null};
const nextState = { ...state, [key]: String(value || "").trim() || null };
const saved = await put(`/chats/${activeChatId}/persona-state`, nextState);
if (saved.error) return setNotice(saved.error, "error");
return setNotice("Roleplay state saved.", "success");
@@ -1859,22 +1859,22 @@ async function sendMessage() {
const currentChat = getChatById(activeChatId);
if (currentChat && !currentChat.assistant_persona_id && currentChat.title === "New chat") {
const autoTitle = message.length > 40 ? `${message.slice(0, 40)}...` : message;
currentChat.title = autoTitle; updateWorkspaceCopy(); renderChatList(); await put(`/chats/${activeChatId}`, {title: autoTitle});
currentChat.title = autoTitle; updateWorkspaceCopy(); renderChatList(); await put(`/chats/${activeChatId}`, { title: autoTitle });
}
const userMessage = {role: "user", content: message, attachments};
const userMessage = { role: "user", content: message, attachments };
currentMessages.push(userMessage); addMessage(userMessage, true); updateChatActionState();
msgInput.value = "";
resizeComposerInput();
setLoadingState(true, false); setNotice("Generating reply...");
const loadingMsg = addMessage({role: "bot", content: ""}, false, true, {isNewest: true});
const loadingMsg = addMessage({ role: "bot", content: "" }, false, true, { isNewest: true });
const loadingBody = loadingMsg.querySelector(".msg-content");
const loadingShell = loadingMsg.querySelector(".loading-shell");
const loadingLabel = loadingMsg.querySelector(".loading-copy strong");
loadingLabel.textContent = "Preparing response...";
try {
let streamError = null;
await stream("/chat/stream", {message, attachments: attachments.map(item => item.id), model: modelSelect.value, chatId: activeChatId, webSearch, reasoning}, {
onStatus: ({status, sourceCount, searchState}) => {
await stream("/chat/stream", { message, attachments: attachments.map(item => item.id), model: modelSelect.value, chatId: activeChatId, webSearch, reasoning }, {
onStatus: ({ status, sourceCount, searchState }) => {
if (status === "accepted") {
attachmentComposer.clear();
webSearchToggle.reset();
@@ -1883,16 +1883,16 @@ async function sendMessage() {
}
loadingLabel.textContent = status === "analyzing" ? "Analyzing images..." : status === "searching" ? "Searching the web..."
: searchState === "no_relevant_results" ? "No sufficiently relevant results. Generating response..."
: sourceCount === 0 ? "No web results found. Generating response..." : "Generating response...";
: sourceCount === 0 ? "No web results found. Generating response..." : "Generating response...";
},
onThinking: ({fullThinking}) => {
onThinking: ({ fullThinking }) => {
if (!reasoning) return;
const follow = messagesDiv.scrollHeight - messagesDiv.scrollTop - messagesDiv.clientHeight < 100;
updateReasoning(loadingMsg, fullThinking, true);
loadingShell?.classList.toggle("hidden", Boolean(fullThinking));
if (follow) scrollMessagesToBottom();
},
onChunk: ({fullReply}) => {
onChunk: ({ fullReply }) => {
const follow = messagesDiv.scrollHeight - messagesDiv.scrollTop - messagesDiv.clientHeight < 100;
if (fullReply) finishReasoning(loadingMsg);
if (loadingBody) {
@@ -1904,7 +1904,7 @@ async function sendMessage() {
if (follow) scrollMessagesToBottom();
},
onDone: () => { webSearchToggle.reset(); reasoningToggle.reset(); finishReasoning(loadingMsg); },
onError: ({error}) => {
onError: ({ error }) => {
streamError = error || "Failed to generate reply.";
}
});
@@ -1942,7 +1942,7 @@ async function sendMessage() {
const chatActions = {
archive: async () => {
const chat = getChatById(activeChatId);
if (chat) await updateChatOrganization(chat.id, {archived: !chat.archived_at});
if (chat) await updateChatOrganization(chat.id, { archived: !chat.archived_at });
},
clear: async () => {
if (activeChatId) await clearChat(activeChatId);
@@ -1962,7 +1962,7 @@ const chatActions = {
placeholder: "Research"
});
if (folderName === null) return;
await updateChatOrganization(chat.id, {folderName: String(folderName || "").trim() || null});
await updateChatOrganization(chat.id, { folderName: String(folderName || "").trim() || null });
},
memory: async () => {
await manageChatMemory();
@@ -1972,7 +1972,7 @@ const chatActions = {
},
pin: async () => {
const chat = getChatById(activeChatId);
if (chat) await updateChatOrganization(chat.id, {isPinned: !chat.is_pinned});
if (chat) await updateChatOrganization(chat.id, { isPinned: !chat.is_pinned });
},
rename: async () => {
if (activeChatId) await renameChat(activeChatId);
+12 -12
View File
@@ -1,11 +1,11 @@
import {del, get, post, put} from "./app/api.js?v=20260909-account-controls";
import { del, get, post, put } from "./js/api.js?v=20260909-account-controls";
import {
applyTheme as applySharedTheme,
applyThemeMode as applySharedThemeMode,
initializeAppearance
} from "./app/themeController.js?v=20260909-account-controls";
import {clearStatus, setStatus} from "./app/statusNotice.js?v=20260909-account-controls";
import {populatePersonaForm, readPersonaForm} from "./app/personaForm.js?v=20260909-account-controls";
} from "./js/themeController.js?v=20260909-account-controls";
import { clearStatus, setStatus } from "./js/statusNotice.js?v=20260909-account-controls";
import { populatePersonaForm, readPersonaForm } from "./js/personaForm.js?v=20260909-account-controls";
const $ = (id) => document.getElementById(id);
const settingsNotice = $("settingsNotice");
@@ -127,11 +127,11 @@ function promptSettingsPopup({
}
function applyTheme(themeKey, persist = true) {
return applySharedTheme(themeKey, {persist, title: "Elsewhere Settings", themeSelect: settingsThemeSelect});
return applySharedTheme(themeKey, { persist, title: "Elsewhere Settings", themeSelect: settingsThemeSelect });
}
function applyThemeMode(modeKey, persist = true) {
return applySharedThemeMode(modeKey, {persist, modeSelect: themeModeSelect});
return applySharedThemeMode(modeKey, { persist, modeSelect: themeModeSelect });
}
function setSettingsView(view) {
@@ -382,7 +382,7 @@ async function publishPersona(id) {
});
if (tagsInput === null) return;
const tags = String(tagsInput || "").split(",").map((tag) => tag.trim()).filter(Boolean);
const res = await post(`/personas/${id}/publish`, {tags});
const res = await post(`/personas/${id}/publish`, { tags });
if (res.error) {
setNotice(res.error, "error");
return;
@@ -435,7 +435,7 @@ async function unpublishPersona(id) {
}
function downloadJson(filename, payload) {
const blob = new Blob([JSON.stringify(payload, null, 2)], {type: "application/json;charset=utf-8"});
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json;charset=utf-8" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
@@ -479,7 +479,7 @@ async function deleteAccount() {
confirmLabel: "Delete account"
});
if (currentPassword === null) return;
const res = await request("/settings/account", {currentPassword}, "DELETE");
const res = await request("/settings/account", { currentPassword }, "DELETE");
if (res.error) return setNotice(res.error, "error");
window.location.href = "/";
}
@@ -492,7 +492,7 @@ usernameForm.addEventListener("submit", async (event) => {
setNotice("New username and current password are required.", "error");
return;
}
const res = await request("/settings/username", {username, password}, "PUT");
const res = await request("/settings/username", { username, password }, "PUT");
if (res.error) {
setNotice(res.error, "error");
return;
@@ -516,7 +516,7 @@ passwordForm.addEventListener("submit", async (event) => {
setNotice("New passwords do not match.", "error");
return;
}
const res = await request("/settings/password", {currentPassword, newPassword: nextPassword}, "PUT");
const res = await request("/settings/password", { currentPassword, newPassword: nextPassword }, "PUT");
if (res.error) {
setNotice(res.error, "error");
return;
@@ -575,7 +575,7 @@ settingsPopupInput.addEventListener("keydown", (event) => {
});
window.addEventListener("load", async () => {
initializeAppearance({title: "Elsewhere Settings", themeSelect: settingsThemeSelect, modeSelect: themeModeSelect});
initializeAppearance({ title: "Elsewhere Settings", themeSelect: settingsThemeSelect, modeSelect: themeModeSelect });
workspaceModeSelect.value = localStorage.getItem("krishd-workspace-mode") || "basic";
const params = new URLSearchParams(window.location.search);
const personaPage = location.pathname === "/app/personas";
+29 -22
View File
@@ -1,32 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Elsewhere</title>
<link rel="icon" href="/favicon.svg">
<script src="/app/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/style.css?v=20260909-account-controls">
<link rel="stylesheet" href="/pages.css?v=20260909-account-controls">
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="icon" href="/img/favicon.svg">
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls">
<link rel="stylesheet" href="/css/pages.css?v=20260909-account-controls">
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet">
</head>
<body class="site-page landing-page">
<!-- site-header -->
<main id="main" class="landing-entry">
<div class="entry-door" aria-hidden="true"><span></span></div>
<div class="entry-copy">
<p class="eyebrow">A space for every conversation.</p>
<h1>Take a thought<br>somewhere.</h1>
<p>Talk it through. Make something up.<br>Meet a character and see where it goes.</p>
<div class="site-actions">
<a data-start-link class="primary-action" href="/register">Step inside <span aria-hidden="true">&rarr;</span></a>
<a href="/market">Meet the personas</a>
<!-- site-header -->
<main id="main" class="landing-entry">
<div class="entry-door" aria-hidden="true"><span></span></div>
<div class="entry-copy">
<p class="eyebrow">A space for every conversation.</p>
<h1>Take a thought<br>somewhere.</h1>
<p>Talk it through. Make something up.<br>Meet a character and see where it goes.</p>
<div class="site-actions">
<a data-start-link class="primary-action" href="/register">Step inside <span
aria-hidden="true">&rarr;</span></a>
<a href="/market">Meet the personas</a>
</div>
</div>
</div>
</main>
<footer class="site-footer"><span>elsewhere / a little room for imagination</span><a href="/privacy.html">Privacy</a><a href="/tos.html">Terms</a></footer>
<script type="module" src="/pages.js?v=20260909-account-controls"></script>
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
</main>
<footer class="site-footer"><span>elsewhere / a little room for imagination</span><a
href="/privacy.html">Privacy</a><a href="/tos.html">Terms</a></footer>
<script type="module" src="/js/pages.js?v=20260909-account-controls"></script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
</html>
</html>
+234 -214
View File
@@ -1,242 +1,262 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
<title>Elsewhere Discover</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script src="/app/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/style.css?v=20260909-account-controls"/>
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet">
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
</head>
<body class="site-page market-page" data-theme="fakegpt" data-theme-mode="light">
<!-- site-header -->
<main id="main" class="market-shell page-content">
<header class="market-header market-header-simple">
<div>
<p class="eyebrow">Shared personas</p>
<h1>Discover</h1>
</div>
<div class="market-header-actions"><button id="refreshMarket" class="secondary-action" type="button">Refresh</button></div>
</header>
<div class="market-search">
<label for="marketSearch">Search</label>
<input id="marketSearch" type="search" placeholder="Search by name, creator, or description"/>
</div>
<p id="marketStatus" class="status persona-status">Loading market personas...</p>
<section class="settings-layout market-settings-layout">
<aside class="settings-nav" aria-label="Market sections" role="tablist">
<button class="settings-nav-item active" type="button" role="tab" aria-selected="true" data-market-view="ai-characters">Characters</button>
<button class="settings-nav-item" type="button" role="tab" aria-selected="false" data-market-view="user-personas">User personas</button>
</aside>
<div class="settings-content">
<section class="settings-view active" role="tabpanel" data-market-panel="ai-characters">
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Discover</p>
<h2>AI Characters</h2>
</div>
<div class="market-section-actions">
<label class="market-sort-control" for="marketAssistantSort">
<span>Sort</span>
<select id="marketAssistantSort">
<option value="best">Best</option>
<option value="newest">Newest</option>
<option value="most_favorited">Most Favorited</option>
<option value="most_popular">Most Popular</option>
<option value="top_rated">Top Rated</option>
<option value="alphabetical">A-Z</option>
</select>
</label>
<button id="marketCreateAiCharacter" type="button" class="secondary-action">Create AI Character</button>
</div>
</div>
<div id="marketPersonaList" class="persona-list"></div>
</div>
</section>
<section class="settings-view" role="tabpanel" aria-hidden="true" data-market-panel="user-personas">
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Discover</p>
<h2>User Personas</h2>
</div>
<div class="market-section-actions">
<label class="market-sort-control" for="marketUserSort">
<span>Sort</span>
<select id="marketUserSort">
<option value="best">Best</option>
<option value="newest">Newest</option>
<option value="most_favorited">Most Favorited</option>
<option value="most_popular">Most Popular</option>
<option value="top_rated">Top Rated</option>
<option value="alphabetical">A-Z</option>
</select>
</label>
<button id="marketCreateUserPersona" type="button" class="secondary-action">Create User Persona</button>
</div>
</div>
<div id="marketUserPersonaList" class="persona-list"></div>
</div>
</section>
</div>
</section>
</main>
<div id="marketPreviewModal" class="modal-overlay hidden">
<div class="modal-card market-preview-card">
<div class="modal-header">
<!-- site-header -->
<main id="main" class="market-shell page-content">
<header class="market-header market-header-simple">
<div>
<p class="eyebrow">Persona preview</p>
<h3 id="marketPreviewTitle">Persona details</h3>
<h4 id="marketPreviewName" class="market-preview-name">Persona details</h4>
<p id="marketPreviewIntro" class="subtitle">Review this persona and choose how you want to use it.</p>
<p class="eyebrow">Shared personas</p>
<h1>Discover</h1>
</div>
<button id="marketPreviewClose" type="button" class="icon-button">Close</button>
<div class="market-header-actions"><button id="refreshMarket" class="secondary-action"
type="button">Refresh</button></div>
</header>
<div class="market-search">
<label for="marketSearch">Search</label>
<input id="marketSearch" type="search" placeholder="Search by name, creator, or description" />
</div>
<div id="marketPreviewBody" class="market-preview-body">
<p id="marketPreviewMeta" class="persona-guidance"></p>
<div class="market-preview-details">
<p id="marketStatus" class="status persona-status">Loading market personas...</p>
<section class="settings-layout market-settings-layout">
<aside class="settings-nav" aria-label="Market sections" role="tablist">
<button class="settings-nav-item active" type="button" role="tab" aria-selected="true"
data-market-view="ai-characters">Characters</button>
<button class="settings-nav-item" type="button" role="tab" aria-selected="false"
data-market-view="user-personas">User personas</button>
</aside>
<div class="settings-content">
<section class="settings-view active" role="tabpanel" data-market-panel="ai-characters">
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Discover</p>
<h2>AI Characters</h2>
</div>
<div class="market-section-actions">
<label class="market-sort-control" for="marketAssistantSort">
<span>Sort</span>
<select id="marketAssistantSort">
<option value="best">Best</option>
<option value="newest">Newest</option>
<option value="most_favorited">Most Favorited</option>
<option value="most_popular">Most Popular</option>
<option value="top_rated">Top Rated</option>
<option value="alphabetical">A-Z</option>
</select>
</label>
<button id="marketCreateAiCharacter" type="button" class="secondary-action">Create AI
Character</button>
</div>
</div>
<div id="marketPersonaList" class="persona-list"></div>
</div>
</section>
<section class="settings-view" role="tabpanel" aria-hidden="true" data-market-panel="user-personas">
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Discover</p>
<h2>User Personas</h2>
</div>
<div class="market-section-actions">
<label class="market-sort-control" for="marketUserSort">
<span>Sort</span>
<select id="marketUserSort">
<option value="best">Best</option>
<option value="newest">Newest</option>
<option value="most_favorited">Most Favorited</option>
<option value="most_popular">Most Popular</option>
<option value="top_rated">Top Rated</option>
<option value="alphabetical">A-Z</option>
</select>
</label>
<button id="marketCreateUserPersona" type="button" class="secondary-action">Create User
Persona</button>
</div>
</div>
<div id="marketUserPersonaList" class="persona-list"></div>
</div>
</section>
</div>
</section>
</main>
<div id="marketPreviewModal" class="modal-overlay hidden">
<div class="modal-card market-preview-card">
<div class="modal-header">
<div>
<h4>Pronouns</h4>
<p id="marketPreviewPronouns"></p>
<p class="eyebrow">Persona preview</p>
<h3 id="marketPreviewTitle">Persona details</h3>
<h4 id="marketPreviewName" class="market-preview-name">Persona details</h4>
<p id="marketPreviewIntro" class="subtitle">Review this persona and choose how you want to use it.
</p>
</div>
<div>
<h4>Appearance</h4>
<p id="marketPreviewAppearance"></p>
</div>
<div>
<h4>Background</h4>
<p id="marketPreviewBackground"></p>
</div>
<div>
<h4>Details</h4>
<p id="marketPreviewDetails"></p>
</div>
<div>
<h4>Example dialogues</h4>
<p id="marketPreviewExampleDialogues"></p>
<button id="marketPreviewClose" type="button" class="icon-button">Close</button>
</div>
<div id="marketPreviewBody" class="market-preview-body">
<p id="marketPreviewMeta" class="persona-guidance"></p>
<div class="market-preview-details">
<div>
<h4>Pronouns</h4>
<p id="marketPreviewPronouns"></p>
</div>
<div>
<h4>Appearance</h4>
<p id="marketPreviewAppearance"></p>
</div>
<div>
<h4>Background</h4>
<p id="marketPreviewBackground"></p>
</div>
<div>
<h4>Details</h4>
<p id="marketPreviewDetails"></p>
</div>
<div>
<h4>Example dialogues</h4>
<p id="marketPreviewExampleDialogues"></p>
</div>
</div>
</div>
</div>
<div class="market-preview-actions">
<div id="marketPreviewStepOne" class="roleplay-starter-page hidden">
<label id="marketPreviewUserPersonaField" class="field market-preview-user-field hidden">
<span>You in this roleplay</span>
<select id="marketPreviewUserPersonaSelect"></select>
</label>
</div>
<div id="marketPreviewStepTwo" class="roleplay-starter-page hidden">
<label id="marketPreviewScenarioField" class="field market-preview-scenario-field">
<span>Scenario (optional)</span>
<textarea id="marketPreviewScenario" rows="4" placeholder="Describe the opening scene you want. Leave it empty and the AI Character will invent its own scenario."></textarea>
</label>
<p class="subtitle">Leave this empty if you want the AI Character to invent the scenario on its own. No character limit.</p>
</div>
<div class="market-preview-footer">
<button id="marketPreviewConfirm" type="button" class="primary-action">Take persona</button>
<div class="market-preview-actions">
<div id="marketPreviewStepOne" class="roleplay-starter-page hidden">
<label id="marketPreviewUserPersonaField" class="field market-preview-user-field hidden">
<span>You in this roleplay</span>
<select id="marketPreviewUserPersonaSelect"></select>
</label>
</div>
<div id="marketPreviewStepTwo" class="roleplay-starter-page hidden">
<label id="marketPreviewScenarioField" class="field market-preview-scenario-field">
<span>Scenario (optional)</span>
<textarea id="marketPreviewScenario" rows="4"
placeholder="Describe the opening scene you want. Leave it empty and the AI Character will invent its own scenario."></textarea>
</label>
<p class="subtitle">Leave this empty if you want the AI Character to invent the scenario on its own.
No character limit.</p>
</div>
<div class="market-preview-footer">
<button id="marketPreviewConfirm" type="button" class="primary-action">Take persona</button>
</div>
</div>
</div>
</div>
</div>
<div id="marketPersonaModal" class="modal-overlay hidden">
<div class="modal-card persona-modal-card">
<div class="modal-header">
<div class="persona-modal-heading">
<p class="eyebrow">AI Character editor</p>
<h3 id="marketPersonaFormTitle">Create AI Character</h3>
<div id="marketPersonaModal" class="modal-overlay hidden">
<div class="modal-card persona-modal-card">
<div class="modal-header">
<div class="persona-modal-heading">
<p class="eyebrow">AI Character editor</p>
<h3 id="marketPersonaFormTitle">Create AI Character</h3>
</div>
<button id="marketPersonaClose" type="button" class="icon-button">Close</button>
</div>
<button id="marketPersonaClose" type="button" class="icon-button">Close</button>
</div>
<form id="marketPersonaForm" class="persona-form">
<div class="persona-form-intro">
<p class="persona-helper">Create or update your AI Characters for roleplay chats and marketplace publishing.</p>
</div>
<p id="marketPersonaFormNotice" class="status hidden"></p>
<div class="persona-form-grid">
<label class="field">
<span>Character name</span>
<input id="marketPersonaName" placeholder="Character name" required/>
</label>
<label class="field">
<span>Pronouns</span>
<input id="marketPersonaPronouns" placeholder="e.g. she/her, they/them"/>
</label>
<label class="field field-wide">
<span>Appearance</span>
<textarea id="marketPersonaAppearance" rows="3" placeholder="Describe their look"></textarea>
</label>
<label class="field field-wide">
<span>Background</span>
<textarea id="marketPersonaBackground" rows="3" placeholder="Backstory, history, role, or origins"></textarea>
</label>
<label class="field field-wide">
<span>Details</span>
<textarea id="marketPersonaDetails" rows="4" placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
</label>
<label class="field field-wide">
<span>Example dialogues</span>
<div class="dialogue-format-card" aria-hidden="true">
<p class="dialogue-format-label">Format</p>
<pre class="dialogue-format-example">{{character}}: Hello, my name is marc
<form id="marketPersonaForm" class="persona-form">
<div class="persona-form-intro">
<p class="persona-helper">Create or update your AI Characters for roleplay chats and marketplace
publishing.</p>
</div>
<p id="marketPersonaFormNotice" class="status hidden"></p>
<div class="persona-form-grid">
<label class="field">
<span>Character name</span>
<input id="marketPersonaName" placeholder="Character name" required />
</label>
<label class="field">
<span>Pronouns</span>
<input id="marketPersonaPronouns" placeholder="e.g. she/her, they/them" />
</label>
<label class="field field-wide">
<span>Appearance</span>
<textarea id="marketPersonaAppearance" rows="3" placeholder="Describe their look"></textarea>
</label>
<label class="field field-wide">
<span>Background</span>
<textarea id="marketPersonaBackground" rows="3"
placeholder="Backstory, history, role, or origins"></textarea>
</label>
<label class="field field-wide">
<span>Details</span>
<textarea id="marketPersonaDetails" rows="4"
placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
</label>
<label class="field field-wide">
<span>Example dialogues</span>
<div class="dialogue-format-card" aria-hidden="true">
<p class="dialogue-format-label">Format</p>
<pre class="dialogue-format-example">{{character}}: Hello, my name is marc
{{user}}: Hi, Marc!
{{character}}: Nice to meet you</pre>
</div>
<textarea id="marketPersonaExampleDialogues" rows="5" placeholder="Write example exchanges that define the character's tone, phrasing, pacing, and personality."></textarea>
</div>
<textarea id="marketPersonaExampleDialogues" rows="5"
placeholder="Write example exchanges that define the character's tone, phrasing, pacing, and personality."></textarea>
</label>
</div>
<div class="persona-actions">
<button id="marketPersonaSave" type="submit" class="primary-action">Save AI Character</button>
</div>
</form>
</div>
</div>
<div id="marketActivityOverlay" class="chat-activity-overlay hidden" role="status" aria-live="polite"
aria-atomic="true">
<div class="chat-activity-card">
<div class="chat-activity-mark" aria-hidden="true">
<span></span>
<span></span>
<span></span>
</div>
<p id="marketActivityEyebrow" class="eyebrow">Please wait</p>
<h3 id="marketActivityTitle">Opening roleplay</h3>
<p id="marketActivityDetail" class="subtitle">The AI character is preparing the first message.</p>
</div>
</div>
<div id="marketPopupModal" class="modal-overlay hidden">
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="marketPopupTitle"
aria-describedby="marketPopupDescription">
<div class="modal-header">
<div class="persona-modal-heading">
<p id="marketPopupEyebrow" class="eyebrow">Action</p>
<h3 id="marketPopupTitle">Confirm action</h3>
</div>
<button id="marketPopupClose" type="button" class="icon-button">Close</button>
</div>
<div class="popup-content">
<p id="marketPopupDescription" class="subtitle">Please confirm this action.</p>
<label id="marketPopupField" class="field">
<span id="marketPopupInputLabel">Value</span>
<input id="marketPopupInput" type="text" autocomplete="off" />
</label>
</div>
<div class="persona-actions">
<button id="marketPersonaSave" type="submit" class="primary-action">Save AI Character</button>
<div class="popup-actions">
<button id="marketPopupCancel" type="button" class="secondary-action">Cancel</button>
<button id="marketPopupConfirm" type="button">Confirm</button>
</div>
</form>
</div>
</div>
</div>
<script type="module" src="/js/market.js?v=20260909-account-controls"></script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
<div id="marketActivityOverlay" class="chat-activity-overlay hidden" role="status" aria-live="polite" aria-atomic="true">
<div class="chat-activity-card">
<div class="chat-activity-mark" aria-hidden="true">
<span></span>
<span></span>
<span></span>
</div>
<p id="marketActivityEyebrow" class="eyebrow">Please wait</p>
<h3 id="marketActivityTitle">Opening roleplay</h3>
<p id="marketActivityDetail" class="subtitle">The AI character is preparing the first message.</p>
</div>
</div>
<div id="marketPopupModal" class="modal-overlay hidden">
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="marketPopupTitle" aria-describedby="marketPopupDescription">
<div class="modal-header">
<div class="persona-modal-heading">
<p id="marketPopupEyebrow" class="eyebrow">Action</p>
<h3 id="marketPopupTitle">Confirm action</h3>
</div>
<button id="marketPopupClose" type="button" class="icon-button">Close</button>
</div>
<div class="popup-content">
<p id="marketPopupDescription" class="subtitle">Please confirm this action.</p>
<label id="marketPopupField" class="field">
<span id="marketPopupInputLabel">Value</span>
<input id="marketPopupInput" type="text" autocomplete="off"/>
</label>
</div>
<div class="popup-actions">
<button id="marketPopupCancel" type="button" class="secondary-action">Cancel</button>
<button id="marketPopupConfirm" type="button">Confirm</button>
</div>
</div>
</div>
<script type="module" src="/market.js?v=20260909-account-controls"></script>
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script></body>
</html>
</html>
+84 -76
View File
@@ -1,94 +1,102 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Privacy Policy – DeepFake</title>
<link rel="stylesheet" href="style.css?v=20260909-account-controls" />
<link rel="stylesheet" href="legal.css?v=20260909-account-controls" />
<script src="/app/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls" />
<link rel="stylesheet" href="/css/legal.css?v=20260909-account-controls" />
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet">
</head>
<body class="site-page legal-page">
<!-- site-header -->
<main id="main" class="legal-content">
<h1>Privacy Policy</h1>
<p>Last updated: <strong>06.01.2026</strong></p>
<!-- site-header -->
<main id="main" class="legal-content">
<h1>Privacy Policy</h1>
<p>Last updated: <strong>06.01.2026</strong></p>
<section>
<h2>1. Controller / Contact</h2>
<p><strong>Application:</strong> DeepFake</p>
<p><strong>Owner/Controller:</strong> Kristian Cerni</p>
<p><strong>Contact:</strong> <a href="https://github.com/KrisHD1337">https://github.com/KrisHD1337</a></p>
<p>This policy applies to all users within Switzerland, the EU, and other jurisdictions.</p>
</section>
<section>
<h2>1. Controller / Contact</h2>
<p><strong>Application:</strong> DeepFake</p>
<p><strong>Owner/Controller:</strong> Kristian Cerni</p>
<p><strong>Contact:</strong> <a href="https://github.com/KrisHD1337">https://github.com/KrisHD1337</a></p>
<p>This policy applies to all users within Switzerland, the EU, and other jurisdictions.</p>
</section>
<section>
<h2>2. What We Collect</h2>
<p>We collect only what’s needed to provide our service:</p>
<ul>
<li><strong>Account Data:</strong> Username, email (optional), hashed password</li>
<li><strong>Chat Data:</strong> Messages you send and the AI responses</li>
</ul>
<p>All data is processed to provide your chat history and account functionality.</p>
</section>
<section>
<h2>2. What We Collect</h2>
<p>We collect only what’s needed to provide our service:</p>
<ul>
<li><strong>Account Data:</strong> Username, email (optional), hashed password</li>
<li><strong>Chat Data:</strong> Messages you send and the AI responses</li>
</ul>
<p>All data is processed to provide your chat history and account functionality.</p>
</section>
<section>
<h2>3. Legal Basis (GDPR)</h2>
<p>For EU/EEA users, we process data based on:</p>
<ul>
<li>Contract performance (to provide your account and chat history)</li>
<li>Legitimate interests (system security, fraud prevention)</li>
</ul>
<p>Your data will not be processed for marketing without explicit consent.</p>
</section>
<section>
<h2>3. Legal Basis (GDPR)</h2>
<p>For EU/EEA users, we process data based on:</p>
<ul>
<li>Contract performance (to provide your account and chat history)</li>
<li>Legitimate interests (system security, fraud prevention)</li>
</ul>
<p>Your data will not be processed for marketing without explicit consent.</p>
</section>
<section>
<h2>4. How We Use Data</h2>
<p>Your data is used to:</p>
<ul>
<li>Authenticate and manage your account</li>
<li>Store and retrieve chat history</li>
<li>Improve and maintain the app</li>
</ul>
<p>We do not sell your data.</p>
</section>
<section>
<h2>4. How We Use Data</h2>
<p>Your data is used to:</p>
<ul>
<li>Authenticate and manage your account</li>
<li>Store and retrieve chat history</li>
<li>Improve and maintain the app</li>
</ul>
<p>We do not sell your data.</p>
</section>
<section>
<h2>5. Data Retention</h2>
<p>We retain data as long as your account is active. If you request deletion, your data will be deleted unless we must retain it for legal reasons.</p>
</section>
<section>
<h2>5. Data Retention</h2>
<p>We retain data as long as your account is active. If you request deletion, your data will be deleted
unless we must retain it for legal reasons.</p>
</section>
<section>
<h2>6. Data Transfers</h2>
<p>Your data may be stored or processed where our servers reside. Switzerland has an adequacy decision with the EU, meaning transfers can occur without additional safeguards.</p>
</section>
<section>
<h2>6. Data Transfers</h2>
<p>Your data may be stored or processed where our servers reside. Switzerland has an adequacy decision with
the EU, meaning transfers can occur without additional safeguards.</p>
</section>
<section>
<h2>7. Your Rights (GDPR + Swiss rights)</h2>
<p>Depending on jurisdiction, you may have the right to:</p>
<ul>
<li>Access your data</li>
<li>Correct inaccuracies</li>
<li>Request deletion</li>
<li>Restrict or object to processing</li>
<li>Request data portability (GDPR)</li>
</ul>
<p>To exercise these rights, contact us at: <a href="mailto:kriscerni@gmail.com">kriscerni@gmail.com</a></p>
</section>
<section>
<h2>7. Your Rights (GDPR + Swiss rights)</h2>
<p>Depending on jurisdiction, you may have the right to:</p>
<ul>
<li>Access your data</li>
<li>Correct inaccuracies</li>
<li>Request deletion</li>
<li>Restrict or object to processing</li>
<li>Request data portability (GDPR)</li>
</ul>
<p>To exercise these rights, contact us at: <a href="mailto:kriscerni@gmail.com">kriscerni@gmail.com</a></p>
</section>
<section>
<h2>8. Data Security</h2>
<p>We implement reasonable technical and organizational measures to protect your data — encryption for passwords and secure database access.</p>
</section>
<section>
<h2>8. Data Security</h2>
<p>We implement reasonable technical and organizational measures to protect your data — encryption for
passwords and secure database access.</p>
</section>
<section>
<h2>9. Changes to this Policy</h2>
<p>We may update this document. Changes will be posted with a “Last updated” date.</p>
</section>
</main>
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
<section>
<h2>9. Changes to this Policy</h2>
<p>We may update this document. Changes will be posted with a “Last updated” date.</p>
</section>
</main>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
</html>
</html>
+306 -285
View File
@@ -1,303 +1,324 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<meta charset="UTF-8"/>
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
<meta charset="UTF-8" />
<title>Elsewhere Settings</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script src="/app/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/style.css?v=20260909-account-controls"/>
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet">
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
</head>
<body class="site-page settings-page" data-theme="fakegpt" data-theme-mode="light">
<!-- site-header -->
<main id="main" class="settings-shell page-content">
<header class="market-header market-header-simple">
<div>
<p class="eyebrow">Elsewhere settings</p>
<h1>Preferences</h1>
</div>
</header>
<p id="settingsNotice" class="status">Loading settings...</p>
<section class="settings-layout">
<aside class="settings-nav" aria-label="Settings sections" role="tablist">
<button class="settings-nav-item active" type="button" role="tab" aria-selected="true" data-settings-view="personal">Account</button>
<button class="settings-nav-item" type="button" role="tab" aria-selected="false" data-settings-view="design">Appearance</button>
<button id="diagnosticsNavItem" class="settings-nav-item hidden" type="button" role="tab" aria-selected="false" data-settings-view="diagnostics">Diagnostics</button>
</aside>
<div class="settings-content">
<section class="settings-view active" role="tabpanel" data-settings-panel="personal">
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Profile</p>
<h2>Username</h2>
</div>
</div>
<form id="usernameForm" class="settings-form-stack">
<label class="field">
<span>Current username</span>
<input id="currentUsername" readonly/>
</label>
<label class="field">
<span>New username</span>
<input id="newUsername" autocomplete="username"/>
</label>
<label class="field">
<span>Current password</span>
<input id="usernamePassword" type="password" autocomplete="current-password"/>
</label>
<button id="saveUsername" type="submit">Save username</button>
</form>
</div>
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Security</p>
<h2>Password</h2>
</div>
</div>
<form id="passwordForm" class="settings-form-stack">
<label class="field">
<span>Current password</span>
<input id="currentPassword" type="password" autocomplete="current-password"/>
</label>
<label class="field">
<span>New password</span>
<input id="newPassword" type="password" autocomplete="new-password"/>
</label>
<label class="field">
<span>Repeat new password</span>
<input id="repeatPassword" type="password" autocomplete="new-password"/>
</label>
<button id="savePassword" type="submit">Save password</button>
</form>
</div>
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Account data</p>
<h2>Export and sessions</h2>
<p class="subtitle">Download your account data or sign out every active browser session.</p>
</div>
</div>
<div class="persona-actions">
<button id="exportAccount" type="button" class="secondary-action">Export account</button>
<button id="logoutAllSessions" type="button" class="secondary-action">Logout all sessions</button>
</div>
</div>
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Danger zone</p>
<h2>Delete account</h2>
<p class="subtitle">This permanently removes your account, chats, personas, templates, and market activity.</p>
</div>
</div>
<div class="persona-actions">
<button id="deleteAccount" type="button" class="secondary-action danger">Delete account</button>
</div>
</div>
</section>
<section class="settings-view" role="tabpanel" aria-hidden="true" data-settings-panel="my-roleplay-companions">
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Create & manage</p>
<h2>My personas</h2>
</div>
<div class="market-header-actions action-pair">
<button id="createAiCharacter" type="button" class="secondary-action">Create AI Character</button>
<button id="createUserPersona" type="button" class="secondary-action">Create User Persona</button>
</div>
</div>
<h3>AI Characters</h3>
<div id="settingsAiCharacterList" class="persona-list"></div>
<h3>User Personas</h3>
<div id="settingsUserPersonaList" class="persona-list"></div>
</div>
</section>
<section class="settings-view" role="tabpanel" aria-hidden="true" data-settings-panel="design">
<div class="settings-panel settings-panel-compact">
<div class="settings-section-header">
<div>
<p class="eyebrow">Appearance</p>
<h2>Make yourself at home</h2>
<p class="subtitle">Choose a style for your conversation space.</p>
</div>
</div>
<div class="settings-theme-row">
<label class="field">
<span>Style</span>
<select id="settingsThemeSelect">
<option value="fakegpt">Grove</option>
<option value="fraud">Parchment</option>
<option value="germini">Aurora</option>
<option value="slopilot">Current</option>
<option value="beta-ai">Iris</option>
<option value="confusity">Graphite</option>
</select>
</label>
<label class="field">
<span>Light or dark</span>
<select id="themeModeSelect">
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
<label class="field">
<span>Workspace mode</span>
<select id="workspaceModeSelect">
<option value="basic">Basic</option>
<option value="advanced">Advanced</option>
</select>
</label>
</div>
</div>
</section>
<section class="settings-view" role="tabpanel" aria-hidden="true" data-settings-panel="diagnostics">
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Admin</p>
<h2>Model diagnostics</h2>
<p class="subtitle">Backend status, model inventory, latency, and configuration warnings.</p>
</div>
<button id="refreshDiagnostics" type="button" class="secondary-action">Refresh</button>
</div>
<div class="diagnostics-grid">
<div class="diagnostics-card">
<span>Status</span>
<strong id="diagnosticsStatus">Unknown</strong>
<p id="diagnosticsBackend" class="subtitle">Backend unavailable</p>
</div>
<div class="diagnostics-card">
<span>Latency</span>
<strong id="diagnosticsLatency">n/a</strong>
<p id="diagnosticsCheckedAt" class="subtitle">Not checked yet</p>
</div>
<div class="diagnostics-card">
<span>Models</span>
<strong id="diagnosticsModelCount">0</strong>
<p id="diagnosticsCacheAge" class="subtitle">Cache age n/a</p>
</div>
<div class="diagnostics-card">
<span>Active requests</span>
<strong id="diagnosticsActiveRequests">0</strong>
<p id="diagnosticsLastFailure" class="subtitle">No recorded failures</p>
</div>
</div>
<div class="diagnostics-columns">
<div>
<h3>Available models</h3>
<div id="diagnosticsModels" class="diagnostics-list"></div>
</div>
<div>
<h3>Config warnings</h3>
<div id="diagnosticsWarnings" class="diagnostics-list"></div>
</div>
</div>
</div>
</section>
</div>
</section>
</main>
<div id="settingsPersonaModal" class="modal-overlay hidden">
<div class="modal-card persona-modal-card">
<div class="modal-header">
<div class="persona-modal-heading">
<p class="eyebrow">Persona editor</p>
<h3 id="settingsPersonaFormTitle">Create persona</h3>
<!-- site-header -->
<main id="main" class="settings-shell page-content">
<header class="market-header market-header-simple">
<div>
<p class="eyebrow">Elsewhere settings</p>
<h1>Preferences</h1>
</div>
<button id="settingsPersonaClose" type="button" class="icon-button">Close</button>
</div>
<form id="settingsPersonaForm" class="persona-form">
<div class="persona-form-intro">
<p id="settingsPersonaHelper" class="persona-helper">Create a persona for roleplay chats.</p>
</header>
<p id="settingsNotice" class="status">Loading settings...</p>
<section class="settings-layout">
<aside class="settings-nav" aria-label="Settings sections" role="tablist">
<button class="settings-nav-item active" type="button" role="tab" aria-selected="true"
data-settings-view="personal">Account</button>
<button class="settings-nav-item" type="button" role="tab" aria-selected="false"
data-settings-view="design">Appearance</button>
<button id="diagnosticsNavItem" class="settings-nav-item hidden" type="button" role="tab"
aria-selected="false" data-settings-view="diagnostics">Diagnostics</button>
</aside>
<div class="settings-content">
<section class="settings-view active" role="tabpanel" data-settings-panel="personal">
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Profile</p>
<h2>Username</h2>
</div>
</div>
<form id="usernameForm" class="settings-form-stack">
<label class="field">
<span>Current username</span>
<input id="currentUsername" readonly />
</label>
<label class="field">
<span>New username</span>
<input id="newUsername" autocomplete="username" />
</label>
<label class="field">
<span>Current password</span>
<input id="usernamePassword" type="password" autocomplete="current-password" />
</label>
<button id="saveUsername" type="submit">Save username</button>
</form>
</div>
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Security</p>
<h2>Password</h2>
</div>
</div>
<form id="passwordForm" class="settings-form-stack">
<label class="field">
<span>Current password</span>
<input id="currentPassword" type="password" autocomplete="current-password" />
</label>
<label class="field">
<span>New password</span>
<input id="newPassword" type="password" autocomplete="new-password" />
</label>
<label class="field">
<span>Repeat new password</span>
<input id="repeatPassword" type="password" autocomplete="new-password" />
</label>
<button id="savePassword" type="submit">Save password</button>
</form>
</div>
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Account data</p>
<h2>Export and sessions</h2>
<p class="subtitle">Download your account data or sign out every active browser session.
</p>
</div>
</div>
<div class="persona-actions">
<button id="exportAccount" type="button" class="secondary-action">Export account</button>
<button id="logoutAllSessions" type="button" class="secondary-action">Logout all
sessions</button>
</div>
</div>
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Danger zone</p>
<h2>Delete account</h2>
<p class="subtitle">This permanently removes your account, chats, personas, templates,
and market activity.</p>
</div>
</div>
<div class="persona-actions">
<button id="deleteAccount" type="button" class="secondary-action danger">Delete
account</button>
</div>
</div>
</section>
<section class="settings-view" role="tabpanel" aria-hidden="true"
data-settings-panel="my-roleplay-companions">
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Create & manage</p>
<h2>My personas</h2>
</div>
<div class="market-header-actions action-pair">
<button id="createAiCharacter" type="button" class="secondary-action">Create AI
Character</button>
<button id="createUserPersona" type="button" class="secondary-action">Create User
Persona</button>
</div>
</div>
<h3>AI Characters</h3>
<div id="settingsAiCharacterList" class="persona-list"></div>
<h3>User Personas</h3>
<div id="settingsUserPersonaList" class="persona-list"></div>
</div>
</section>
<section class="settings-view" role="tabpanel" aria-hidden="true" data-settings-panel="design">
<div class="settings-panel settings-panel-compact">
<div class="settings-section-header">
<div>
<p class="eyebrow">Appearance</p>
<h2>Make yourself at home</h2>
<p class="subtitle">Choose a style for your conversation space.</p>
</div>
</div>
<div class="settings-theme-row">
<label class="field">
<span>Style</span>
<select id="settingsThemeSelect">
<option value="fakegpt">Grove</option>
<option value="fraud">Parchment</option>
<option value="germini">Aurora</option>
<option value="slopilot">Current</option>
<option value="beta-ai">Iris</option>
<option value="confusity">Graphite</option>
</select>
</label>
<label class="field">
<span>Light or dark</span>
<select id="themeModeSelect">
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
<label class="field">
<span>Workspace mode</span>
<select id="workspaceModeSelect">
<option value="basic">Basic</option>
<option value="advanced">Advanced</option>
</select>
</label>
</div>
</div>
</section>
<section class="settings-view" role="tabpanel" aria-hidden="true" data-settings-panel="diagnostics">
<div class="settings-panel">
<div class="settings-section-header">
<div>
<p class="eyebrow">Admin</p>
<h2>Model diagnostics</h2>
<p class="subtitle">Backend status, model inventory, latency, and configuration
warnings.</p>
</div>
<button id="refreshDiagnostics" type="button" class="secondary-action">Refresh</button>
</div>
<div class="diagnostics-grid">
<div class="diagnostics-card">
<span>Status</span>
<strong id="diagnosticsStatus">Unknown</strong>
<p id="diagnosticsBackend" class="subtitle">Backend unavailable</p>
</div>
<div class="diagnostics-card">
<span>Latency</span>
<strong id="diagnosticsLatency">n/a</strong>
<p id="diagnosticsCheckedAt" class="subtitle">Not checked yet</p>
</div>
<div class="diagnostics-card">
<span>Models</span>
<strong id="diagnosticsModelCount">0</strong>
<p id="diagnosticsCacheAge" class="subtitle">Cache age n/a</p>
</div>
<div class="diagnostics-card">
<span>Active requests</span>
<strong id="diagnosticsActiveRequests">0</strong>
<p id="diagnosticsLastFailure" class="subtitle">No recorded failures</p>
</div>
</div>
<div class="diagnostics-columns">
<div>
<h3>Available models</h3>
<div id="diagnosticsModels" class="diagnostics-list"></div>
</div>
<div>
<h3>Config warnings</h3>
<div id="diagnosticsWarnings" class="diagnostics-list"></div>
</div>
</div>
</div>
</section>
</div>
<p id="settingsPersonaFormNotice" class="status hidden"></p>
<div class="persona-form-grid">
<label class="field field-wide">
<span>Persona for</span>
<select id="settingsPersonaType">
<option value="assistant">AI Character</option>
<option value="user">Your persona</option>
</select>
</label>
<label class="field">
<span>Name</span>
<input id="settingsPersonaName" placeholder="Persona name" required/>
</label>
<label class="field">
<span>Pronouns</span>
<input id="settingsPersonaPronouns" placeholder="e.g. she/her, they/them"/>
</label>
<label class="field field-wide">
<span>Appearance</span>
<textarea id="settingsPersonaAppearance" rows="3" placeholder="Describe their look"></textarea>
</label>
<label class="field field-wide">
<span>Background</span>
<textarea id="settingsPersonaBackground" rows="3" placeholder="Backstory, history, role, or origins"></textarea>
</label>
<label class="field field-wide">
<span>Details</span>
<textarea id="settingsPersonaDetails" rows="4" placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
</label>
<label id="settingsPersonaExamplesField" class="field field-wide hidden">
<span>Example dialogues</span>
<div class="dialogue-format-card" aria-hidden="true">
<p class="dialogue-format-label">Format</p>
<pre class="dialogue-format-example">{{character}}: Hello, my name is marc
</section>
</main>
<div id="settingsPersonaModal" class="modal-overlay hidden">
<div class="modal-card persona-modal-card">
<div class="modal-header">
<div class="persona-modal-heading">
<p class="eyebrow">Persona editor</p>
<h3 id="settingsPersonaFormTitle">Create persona</h3>
</div>
<button id="settingsPersonaClose" type="button" class="icon-button">Close</button>
</div>
<form id="settingsPersonaForm" class="persona-form">
<div class="persona-form-intro">
<p id="settingsPersonaHelper" class="persona-helper">Create a persona for roleplay chats.</p>
</div>
<p id="settingsPersonaFormNotice" class="status hidden"></p>
<div class="persona-form-grid">
<label class="field field-wide">
<span>Persona for</span>
<select id="settingsPersonaType">
<option value="assistant">AI Character</option>
<option value="user">Your persona</option>
</select>
</label>
<label class="field">
<span>Name</span>
<input id="settingsPersonaName" placeholder="Persona name" required />
</label>
<label class="field">
<span>Pronouns</span>
<input id="settingsPersonaPronouns" placeholder="e.g. she/her, they/them" />
</label>
<label class="field field-wide">
<span>Appearance</span>
<textarea id="settingsPersonaAppearance" rows="3" placeholder="Describe their look"></textarea>
</label>
<label class="field field-wide">
<span>Background</span>
<textarea id="settingsPersonaBackground" rows="3"
placeholder="Backstory, history, role, or origins"></textarea>
</label>
<label class="field field-wide">
<span>Details</span>
<textarea id="settingsPersonaDetails" rows="4"
placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
</label>
<label id="settingsPersonaExamplesField" class="field field-wide hidden">
<span>Example dialogues</span>
<div class="dialogue-format-card" aria-hidden="true">
<p class="dialogue-format-label">Format</p>
<pre class="dialogue-format-example">{{character}}: Hello, my name is marc
{{user}}: Hi, Marc!
{{character}}: Nice to meet you</pre>
</div>
<textarea id="settingsPersonaExampleDialogues" rows="5" placeholder="Write example exchanges that define the character's tone, phrasing, pacing, and personality."></textarea>
</div>
<textarea id="settingsPersonaExampleDialogues" rows="5"
placeholder="Write example exchanges that define the character's tone, phrasing, pacing, and personality."></textarea>
</label>
</div>
<div class="persona-actions">
<button id="settingsPersonaSave" type="submit" class="primary-action">Save persona</button>
</div>
</form>
</div>
</div>
<div id="settingsPopupModal" class="modal-overlay hidden">
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="settingsPopupTitle"
aria-describedby="settingsPopupDescription">
<div class="modal-header">
<div class="persona-modal-heading">
<p id="settingsPopupEyebrow" class="eyebrow">Action</p>
<h3 id="settingsPopupTitle">Confirm action</h3>
</div>
<button id="settingsPopupClose" type="button" class="icon-button">Close</button>
</div>
<div class="popup-content">
<p id="settingsPopupDescription" class="subtitle">Please confirm this action.</p>
<label id="settingsPopupField" class="field">
<span id="settingsPopupInputLabel">Value</span>
<input id="settingsPopupInput" type="text" autocomplete="off" />
</label>
</div>
<div class="persona-actions">
<button id="settingsPersonaSave" type="submit" class="primary-action">Save persona</button>
<div class="popup-actions">
<button id="settingsPopupCancel" type="button" class="secondary-action">Cancel</button>
<button id="settingsPopupConfirm" type="button">Confirm</button>
</div>
</form>
</div>
</div>
</div>
<script type="module" src="/js/settings.js?v=20260909-account-controls"></script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
<div id="settingsPopupModal" class="modal-overlay hidden">
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="settingsPopupTitle" aria-describedby="settingsPopupDescription">
<div class="modal-header">
<div class="persona-modal-heading">
<p id="settingsPopupEyebrow" class="eyebrow">Action</p>
<h3 id="settingsPopupTitle">Confirm action</h3>
</div>
<button id="settingsPopupClose" type="button" class="icon-button">Close</button>
</div>
<div class="popup-content">
<p id="settingsPopupDescription" class="subtitle">Please confirm this action.</p>
<label id="settingsPopupField" class="field">
<span id="settingsPopupInputLabel">Value</span>
<input id="settingsPopupInput" type="text" autocomplete="off"/>
</label>
</div>
<div class="popup-actions">
<button id="settingsPopupCancel" type="button" class="secondary-action">Cancel</button>
<button id="settingsPopupConfirm" type="button">Confirm</button>
</div>
</div>
</div>
<script type="module" src="/settings.js?v=20260909-account-controls"></script>
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script></body>
</html>
</html>
+47 -38
View File
@@ -1,52 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Terms of Service – DeepFake</title>
<link rel="stylesheet" href="style.css?v=20260909-account-controls" />
<link rel="stylesheet" href="legal.css?v=20260909-account-controls" />
<script src="/app/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/shell.css?v=20260909-account-controls">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls" />
<link rel="stylesheet" href="/css/legal.css?v=20260909-account-controls" />
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet">
</head>
<body class="site-page legal-page">
<!-- site-header -->
<main id="main" class="legal-content">
<h1>Terms of Service</h1>
<p>Last updated: <strong>06.01.2026</strong></p>
<!-- site-header -->
<main id="main" class="legal-content">
<h1>Terms of Service</h1>
<p>Last updated: <strong>06.01.2026</strong></p>
<section>
<h2>Service</h2>
<p>You agree that DeepFake AI is provided “as is,” and functionality may change without notice.</p>
</section>
<section>
<h2>Service</h2>
<p>You agree that DeepFake AI is provided “as is,” and functionality may change without notice.</p>
</section>
<section>
<h2>User Conduct</h2>
<p>You are responsible for content you submit and for maintaining secure account credentials. Do not use DeepFake for unlawful purposes.</p>
</section>
<section>
<h2>User Conduct</h2>
<p>You are responsible for content you submit and for maintaining secure account credentials. Do not use
DeepFake for unlawful purposes.</p>
</section>
<section>
<h2>Data Processing</h2>
<p>By using the service, you consent to storing and processing your data as described in the <a href="/privacy.html">Privacy Policy</a>.</p>
</section>
<section>
<h2>Data Processing</h2>
<p>By using the service, you consent to storing and processing your data as described in the <a
href="/privacy.html">Privacy Policy</a>.</p>
</section>
<section>
<h2>Limitation of Liability</h2>
<p>We are not liable for service interruptions, data loss, or any indirect damages arising from your use of DeepFake AI.</p>
</section>
<section>
<h2>Limitation of Liability</h2>
<p>We are not liable for service interruptions, data loss, or any indirect damages arising from your use of
DeepFake AI.</p>
</section>
<section>
<h2>Governing Law</h2>
<p>These Terms are governed by applicable laws in your jurisdiction (e.g., Switzerland or EU Member States).</p>
</section>
<section>
<h2>Governing Law</h2>
<p>These Terms are governed by applicable laws in your jurisdiction (e.g., Switzerland or EU Member States).
</p>
</section>
<section>
<h2>Changes to Terms</h2>
<p>We may modify these Terms at any time. Updates will be reflected with a new “Last updated” date.</p>
</section>
</main>
<script type="module" src="/app/workspaceNavigation.js?v=20260909-account-controls"></script>
<section>
<h2>Changes to Terms</h2>
<p>We may modify these Terms at any time. Updates will be reflected with a new “Last updated” date.</p>
</section>
</main>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
</html>
</html>