rebrand
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
# KrisHD_AI
|
||||
# Elsewhere
|
||||
|
||||
KrisHD_AI is an Express + SQLite app for account-based chat, persona management, roleplay chat sessions, and a simple persona market backed by a remote model API.
|
||||
Elsewhere is an Express + SQLite app for account-based chat, persona management, roleplay chat sessions, and a simple persona market backed by a remote model API.
|
||||
|
||||
A space for every conversation. Six appearance styles (Grove, Parchment, Aurora, Current, Iris, and Graphite) each support light and dark modes. The Elsewhere identity stays consistent across styles. Internal theme and storage keys remain stable to preserve saved preferences.
|
||||
|
||||
## Setup
|
||||
|
||||
Node.js 22 or newer is required.
|
||||
|
||||
1. Install dependencies with `npm install`.
|
||||
2. Copy `.env.example` to `.env` and set at least `SESSION_SECRET`.
|
||||
3. Start the app with `npm run dev` for local development or `npm start` for a normal server run.
|
||||
|
||||
+5
-2
@@ -3,6 +3,9 @@
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "server.js",
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "node --watch server.js",
|
||||
@@ -10,8 +13,8 @@
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"better-sqlite3": "^9.5.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"better-sqlite3": "^13.0.3",
|
||||
"body-parser": "^1.20.2",
|
||||
"express": "^4.19.2",
|
||||
"express-session": "^1.17.3",
|
||||
|
||||
@@ -6,11 +6,12 @@ export const onboardingPrompts = {
|
||||
roleplay: ["Start a roleplay scene with immediate tension.", "Give me a dramatic opening with clear stakes."]
|
||||
};
|
||||
|
||||
// Stable keys preserve existing saved appearance preferences.
|
||||
export const themes = {
|
||||
"fakegpt": {name: "FakeGPT", short: "FG"},
|
||||
"fraud": {name: "Fraud", short: "FR"},
|
||||
"germini": {name: "Germini", short: "GE"},
|
||||
"slopilot": {name: "Slopilot", short: "SP"},
|
||||
"beta-ai": {name: "Beta AI", short: "BA"},
|
||||
"confusity": {name: "Confusity", short: "CF"}
|
||||
"fakegpt": {name: "Grove", short: "GR"},
|
||||
"fraud": {name: "Parchment", short: "PA"},
|
||||
"germini": {name: "Aurora", short: "AU"},
|
||||
"slopilot": {name: "Current", short: "CU"},
|
||||
"beta-ai": {name: "Iris", short: "IR"},
|
||||
"confusity": {name: "Graphite", short: "GP"}
|
||||
};
|
||||
|
||||
+4
-4
@@ -112,8 +112,8 @@ const onboardingContinue = $("onboardingContinue");
|
||||
const onboardingSkip = $("onboardingSkip");
|
||||
const authScreens = document.querySelectorAll(".auth-screen");
|
||||
const toggleButtons = document.querySelectorAll(".auth-toggle .toggle");
|
||||
const themeNameTargets = document.querySelectorAll("[data-theme-name]");
|
||||
const themeLogoTargets = document.querySelectorAll("[data-theme-logo]");
|
||||
const brandNameTargets = document.querySelectorAll("[data-brand-name]");
|
||||
const brandLogoTargets = document.querySelectorAll("[data-brand-logo]");
|
||||
const modelSection = document.querySelector(".chat-drawer-section--model");
|
||||
const personaSection = document.querySelector(".chat-drawer-section--persona");
|
||||
|
||||
@@ -231,8 +231,8 @@ export {
|
||||
sendBtn,
|
||||
saveGenerationSettingsBtn,
|
||||
sessionUser,
|
||||
themeLogoTargets,
|
||||
themeNameTargets,
|
||||
brandLogoTargets,
|
||||
brandNameTargets,
|
||||
toggleButtons,
|
||||
userPersonaList
|
||||
};
|
||||
|
||||
@@ -24,16 +24,17 @@ export function applyTheme(themeKey, {
|
||||
persist = true,
|
||||
title = null,
|
||||
themeSelect = null,
|
||||
nameTargets = document.querySelectorAll("[data-theme-name]"),
|
||||
logoTargets = document.querySelectorAll("[data-theme-logo]")
|
||||
nameTargets = document.querySelectorAll("[data-brand-name]"),
|
||||
logoTargets = document.querySelectorAll("[data-brand-logo]")
|
||||
} = {}) {
|
||||
const nextTheme = normalizeTheme(themeKey);
|
||||
const theme = themes[nextTheme];
|
||||
document.documentElement.dataset.theme = nextTheme;
|
||||
document.body.dataset.theme = nextTheme;
|
||||
if (typeof title === "function") document.title = title(theme);
|
||||
if (typeof title === "string") document.title = title;
|
||||
nameTargets.forEach((target) => { target.textContent = theme.name; });
|
||||
logoTargets.forEach((target) => { target.textContent = theme.short; });
|
||||
nameTargets.forEach((target) => { target.textContent = "elsewhere"; });
|
||||
logoTargets.forEach((target) => { target.textContent = ""; target.classList.add("doorway-mark"); });
|
||||
if (themeSelect) themeSelect.value = nextTheme;
|
||||
if (persist) localStorage.setItem(THEME_STORAGE_KEY, nextTheme);
|
||||
document.dispatchEvent(new CustomEvent("appearancechange", {detail: {theme: nextTheme}}));
|
||||
@@ -42,6 +43,7 @@ export function applyTheme(themeKey, {
|
||||
|
||||
export function applyThemeMode(modeKey, {persist = true, modeSelect = null} = {}) {
|
||||
const nextMode = normalizeThemeMode(modeKey);
|
||||
document.documentElement.dataset.themeMode = nextMode;
|
||||
document.body.dataset.themeMode = nextMode;
|
||||
if (modeSelect) modeSelect.value = nextMode;
|
||||
if (persist) localStorage.setItem(THEME_MODE_STORAGE_KEY, nextMode);
|
||||
|
||||
+10
-8
@@ -2,12 +2,13 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>FakeGPT</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/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"></script>
|
||||
<link rel="stylesheet" href="style.css?v=20260809-2"/>
|
||||
<link rel="stylesheet" href="style.css?v=20260909-elsewhere"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body data-theme="fakegpt" data-theme-mode="light">
|
||||
@@ -15,10 +16,11 @@
|
||||
<section id="auth" class="card auth-card auth-card-compact">
|
||||
<div class="auth-hero auth-hero-compact">
|
||||
<div class="brand">
|
||||
<div class="logo logo-placeholder" aria-hidden="true"></div>
|
||||
<div class="logo doorway-mark" aria-hidden="true"></div>
|
||||
<div>
|
||||
<p class="eyebrow">Private workspace</p>
|
||||
<h1 data-theme-name>FakeGPT</h1>
|
||||
<p class="eyebrow">Your conversation space</p>
|
||||
<h1 class="brand-wordmark" data-brand-name>elsewhere</h1>
|
||||
<p class="brand-tagline">A space for every conversation.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,7 +82,7 @@
|
||||
<div class="sidebar-workspace-card">
|
||||
<div>
|
||||
<p class="eyebrow">Workspace</p>
|
||||
<h2 data-theme-name>FakeGPT</h2>
|
||||
<h2 class="brand-wordmark" data-brand-name>elsewhere</h2>
|
||||
<p id="sessionUser" class="sidebar-account-name">Signed out</p>
|
||||
</div>
|
||||
<div class="sidebar-header-actions sidebar-workspace-actions">
|
||||
@@ -409,11 +411,11 @@
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">Get started</p>
|
||||
<h3 id="onboardingTitle">What do you want to do?</h3>
|
||||
<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">Pick one path to set up your first successful chat.</p>
|
||||
<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>
|
||||
|
||||
+4
-3
@@ -2,12 +2,13 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>FakeGPT Market</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<title>Elsewhere Market</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"></script>
|
||||
<link rel="stylesheet" href="style.css?v=20260809-2"/>
|
||||
<link rel="stylesheet" href="style.css?v=20260909-elsewhere"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body data-theme="fakegpt" data-theme-mode="light">
|
||||
@@ -15,7 +16,7 @@
|
||||
<header class="market-header market-header-simple">
|
||||
<div>
|
||||
<p class="eyebrow">Shared personas</p>
|
||||
<h1><span data-theme-name>FakeGPT</span> Market</h1>
|
||||
<h1><span class="brand-wordmark" data-brand-name>elsewhere</span> Market</h1>
|
||||
</div>
|
||||
<div class="market-header-actions">
|
||||
<button id="refreshMarket" class="secondary-action" type="button">Refresh</button>
|
||||
|
||||
+2
-2
@@ -59,7 +59,7 @@ const marketPopupDescription = $("marketPopupDescription");
|
||||
const marketPopupField = $("marketPopupField");
|
||||
const marketPopupInputLabel = $("marketPopupInputLabel");
|
||||
const marketPopupInput = $("marketPopupInput");
|
||||
const themeNameTargets = document.querySelectorAll("[data-theme-name]");
|
||||
const brandNameTargets = document.querySelectorAll("[data-brand-name]");
|
||||
const marketPersonaFields = {
|
||||
name: marketPersonaName,
|
||||
pronouns: marketPersonaPronouns,
|
||||
@@ -673,7 +673,7 @@ marketPersonaForm.addEventListener("submit", (event) => {
|
||||
});
|
||||
|
||||
window.addEventListener("load", async () => {
|
||||
initializeAppearance({title: (theme) => `${theme.name} Market`, nameTargets: themeNameTargets});
|
||||
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});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<!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"/>
|
||||
<title>Privacy Policy – DeepFake</title>
|
||||
|
||||
+8
-8
@@ -111,8 +111,8 @@ import {
|
||||
sendBtn,
|
||||
saveGenerationSettingsBtn,
|
||||
sessionUser,
|
||||
themeLogoTargets,
|
||||
themeNameTargets,
|
||||
brandLogoTargets,
|
||||
brandNameTargets,
|
||||
toggleButtons,
|
||||
userPersonaList
|
||||
} from "./app/dom.js";
|
||||
@@ -227,8 +227,8 @@ function renderOnboardingStep() {
|
||||
onboardingBack.classList.toggle("hidden", onboardingStep === 1);
|
||||
onboardingContinue.classList.toggle("hidden", onboardingStep === 2);
|
||||
if (onboardingStep === 1) {
|
||||
onboardingTitle.textContent = "What do you want to do?";
|
||||
onboardingSubtitle.textContent = "Choose one path. You can switch anytime.";
|
||||
onboardingTitle.textContent = "Where shall we start?";
|
||||
onboardingSubtitle.textContent = "Ask a question, explore an idea, or step into a story.";
|
||||
[["ask", "Ask questions"], ["brainstorm", "Brainstorm ideas"], ["roleplay", "Roleplay with a character"]].forEach(([key, label]) => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
@@ -812,7 +812,7 @@ function renderMessageEmptyState() {
|
||||
actions.className = "messages-empty-actions";
|
||||
|
||||
if (!chat) {
|
||||
title.textContent = "Start a chat";
|
||||
title.textContent = "Where shall we start?";
|
||||
description.textContent = "Choose a starting point.";
|
||||
actions.append(
|
||||
createStarterAction("New chat", "primary-action", () => { void createNewChat(); }),
|
||||
@@ -951,9 +951,9 @@ function updateModelHelp() {
|
||||
function applyTheme(themeKey, persist = true) {
|
||||
applySharedTheme(themeKey, {
|
||||
persist,
|
||||
title: (theme) => theme.name,
|
||||
nameTargets: themeNameTargets,
|
||||
logoTargets: themeLogoTargets
|
||||
title: "Elsewhere",
|
||||
nameTargets: brandNameTargets,
|
||||
logoTargets: brandLogoTargets
|
||||
});
|
||||
updateWorkspaceCopy();
|
||||
renderModelSection();
|
||||
|
||||
+14
-13
@@ -1,20 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Settings</title>
|
||||
<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"></script>
|
||||
<link rel="stylesheet" href="style.css?v=20260809-2"/>
|
||||
<link rel="stylesheet" href="style.css?v=20260909-elsewhere"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body data-theme="fakegpt" data-theme-mode="light">
|
||||
<div class="settings-shell card">
|
||||
<header class="market-header market-header-simple">
|
||||
<div>
|
||||
<p class="eyebrow">Settings</p>
|
||||
<p class="eyebrow">Elsewhere settings</p>
|
||||
<h1>Preferences</h1>
|
||||
</div>
|
||||
<div class="market-header-actions">
|
||||
@@ -135,24 +136,24 @@
|
||||
<div class="settings-section-header">
|
||||
<div>
|
||||
<p class="eyebrow">Appearance</p>
|
||||
<h2>Theme</h2>
|
||||
<p class="subtitle">Themes are only changed here.</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>Theme</span>
|
||||
<span>Style</span>
|
||||
<select id="settingsThemeSelect">
|
||||
<option value="fakegpt">FakeGPT</option>
|
||||
<option value="fraud">Fraud</option>
|
||||
<option value="germini">Germini</option>
|
||||
<option value="slopilot">Slopilot</option>
|
||||
<option value="beta-ai">Beta AI</option>
|
||||
<option value="confusity">Confusity</option>
|
||||
<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>Theme mode</span>
|
||||
<span>Light or dark</span>
|
||||
<select id="themeModeSelect">
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
|
||||
+4
-4
@@ -127,7 +127,7 @@ function promptSettingsPopup({
|
||||
}
|
||||
|
||||
function applyTheme(themeKey, persist = true) {
|
||||
return applySharedTheme(themeKey, {persist, title: "Settings", themeSelect: settingsThemeSelect});
|
||||
return applySharedTheme(themeKey, {persist, title: "Elsewhere Settings", themeSelect: settingsThemeSelect});
|
||||
}
|
||||
|
||||
function applyThemeMode(modeKey, persist = true) {
|
||||
@@ -526,11 +526,11 @@ passwordForm.addEventListener("submit", async (event) => {
|
||||
|
||||
settingsThemeSelect.addEventListener("change", (event) => {
|
||||
applyTheme(event.target.value);
|
||||
setNotice("Theme updated.", "success");
|
||||
setNotice("Style updated.", "success");
|
||||
});
|
||||
themeModeSelect.addEventListener("change", (event) => {
|
||||
applyThemeMode(event.target.value);
|
||||
setNotice(`Theme mode set to ${document.body.dataset.themeMode}.`, "success");
|
||||
setNotice(`Appearance set to ${document.body.dataset.themeMode}.`, "success");
|
||||
});
|
||||
workspaceModeSelect.addEventListener("change", (event) => {
|
||||
const mode = event.target.value === "advanced" ? "advanced" : "basic";
|
||||
@@ -572,7 +572,7 @@ settingsPopupInput.addEventListener("keydown", (event) => {
|
||||
});
|
||||
|
||||
window.addEventListener("load", async () => {
|
||||
initializeAppearance({title: "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 view = params.get("view") || "personal";
|
||||
|
||||
+8
-1
@@ -1,4 +1,4 @@
|
||||
/* KrisHD AI design system — rebuilt from scratch. */
|
||||
/* Elsewhere design system. */
|
||||
|
||||
/* ---------- Foundation ---------- */
|
||||
:root {
|
||||
@@ -611,3 +611,10 @@ input:focus, textarea:focus, select:focus { border-color: var(--brand); }
|
||||
@keyframes loading-bar { 0%, 100% { height: 7px; opacity: .45; } 50% { height: 18px; opacity: 1; } }
|
||||
@keyframes pulse { 50% { opacity: .35; } }
|
||||
@keyframes wave { 50% { height: 28px; } }
|
||||
|
||||
/* Permanent Elsewhere identity, shared by every appearance style. */
|
||||
.brand-wordmark { font-family: "Manrope", "Segoe UI", sans-serif; font-weight: 700; letter-spacing: -.055em; }
|
||||
.brand-tagline { margin: 12px 0 0; color: var(--text-soft); font-size: .95rem; }
|
||||
.doorway-mark { position: relative; flex-shrink: 0; }
|
||||
.doorway-mark::before { content: ""; width: 22px; height: 30px; border: 2px solid currentColor; border-bottom: 0; border-radius: 10px 10px 0 0; }
|
||||
.doorway-mark::after { content: ""; position: absolute; width: 13px; height: 25px; left: calc(50% - 10px); top: calc(50% - 8px); border: 2px solid currentColor; background: var(--brand); transform: skewY(-18deg); transform-origin: left top; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<!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"/>
|
||||
<title>Terms of Service – DeepFake</title>
|
||||
|
||||
Reference in New Issue
Block a user