feat: add Backspace design prototype and rebrand from Opencord
- Add Backspace-design-prototype.html: finalized "Aether Drift" design language with warm matte surfaces and subtle frosted glass accents - Update CLAUDE.md with DESIGN SYSTEM section and remove Discord clone references - Rename all Opencord references to Backspace across the full codebase - Archive outdated design experiments and Discord-specific assets - Add science-backed accessibility fallback (prefers-reduced-transparency)
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Opencord</title>
|
||||
<title>Backspace</title>
|
||||
</head>
|
||||
<body class="bg-discord-bg-primary text-discord-text-primary">
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@opencord/web",
|
||||
"name": "@backspace/web",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "^2.7.4",
|
||||
"@opencord/shared": "workspace:*",
|
||||
"@backspace/shared": "workspace:*",
|
||||
"@sapphi-red/web-noise-suppressor": "^0.3.5",
|
||||
"livekit-client": "^2.9.0",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
|
||||
@@ -26,12 +26,12 @@ import type {
|
||||
Friend,
|
||||
FriendRequest,
|
||||
InstanceStreamingLimits,
|
||||
} from '@opencord/shared';
|
||||
} from '@backspace/shared';
|
||||
|
||||
const BASE_URL = '/api';
|
||||
|
||||
function getToken(): string | null {
|
||||
return localStorage.getItem('opencord_token');
|
||||
return localStorage.getItem('backspace_token');
|
||||
}
|
||||
|
||||
async function request<T>(
|
||||
|
||||
@@ -22,7 +22,7 @@ export function Embed({ url }: EmbedProps) {
|
||||
// Simple fetch from our new API
|
||||
fetch(`/api/utils/metadata?url=${encodeURIComponent(url)}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('opencord_token')}`
|
||||
'Authorization': `Bearer ${localStorage.getItem('backspace_token')}`
|
||||
}
|
||||
})
|
||||
.then(res => res.json())
|
||||
|
||||
@@ -5,7 +5,7 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import { FriendsPage } from './FriendsPage';
|
||||
import { useSocialStore } from '../../stores/socialStore';
|
||||
import { useServerStore } from '../../stores/serverStore';
|
||||
import type { Friend, FriendRequest } from '@opencord/shared';
|
||||
import type { Friend, FriendRequest } from '@backspace/shared';
|
||||
|
||||
// Mock the api module
|
||||
vi.mock('../../api/client', () => ({
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Avatar } from '../ui/Avatar';
|
||||
import { MemberListToggleButton } from '../layout/MemberListToggleButton';
|
||||
import { LoadingSpinner } from '../ui/LoadingSpinner';
|
||||
import { api } from '../../api/client';
|
||||
import type { Friend, FriendRequest } from '@opencord/shared';
|
||||
import type { Friend, FriendRequest } from '@backspace/shared';
|
||||
|
||||
type Tab = 'online' | 'all' | 'pending' | 'add';
|
||||
|
||||
@@ -143,7 +143,7 @@ export function FriendsPage() {
|
||||
return (
|
||||
<div className="flex-1 p-8">
|
||||
<h2 className="text-base font-bold text-discord-text-primary uppercase mb-2">Add Friend</h2>
|
||||
<p className="text-sm text-discord-text-muted mb-4">You can add friends with their Opencord username.</p>
|
||||
<p className="text-sm text-discord-text-muted mb-4">You can add friends with their Backspace username.</p>
|
||||
<form onSubmit={handleAddFriend} className="relative mb-8">
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useMemo, useRef, useEffect } from 'react';
|
||||
import type { MemberWithUser } from '@opencord/shared';
|
||||
import type { MemberWithUser } from '@backspace/shared';
|
||||
import { Avatar } from '../ui/Avatar';
|
||||
import { useServerStore } from '../../stores/serverStore';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { MessageWithUser } from '@opencord/shared';
|
||||
import type { MessageWithUser } from '@backspace/shared';
|
||||
import { MarkdownRenderer } from './MarkdownRenderer';
|
||||
import { Avatar } from '../ui/Avatar';
|
||||
import { ContextMenu } from '../ui/ContextMenu';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { isDmChannel, useServerStore } from '../../stores/serverStore';
|
||||
import { wsSend } from '../../hooks/useWebSocket';
|
||||
import { api } from '../../api/client';
|
||||
import { MentionPopover } from './MentionPopover';
|
||||
import type { MemberWithUser } from '@opencord/shared';
|
||||
import type { MemberWithUser } from '@backspace/shared';
|
||||
|
||||
interface MessageInputProps {
|
||||
channelId: string;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useAuthStore } from '../../stores/authStore';
|
||||
import { useSocialStore } from '../../stores/socialStore';
|
||||
import { Avatar } from '../ui/Avatar';
|
||||
import { LoadingSpinner } from '../ui/LoadingSpinner';
|
||||
import type { MessageWithUser } from '@opencord/shared';
|
||||
import type { MessageWithUser } from '@backspace/shared';
|
||||
|
||||
const EMPTY_MESSAGES: MessageWithUser[] = [];
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useMemo } from 'react';
|
||||
import { useSocialStore } from '../../stores/socialStore';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
import { Avatar } from '../ui/Avatar';
|
||||
import type { Friend } from '@opencord/shared';
|
||||
import type { Friend } from '@backspace/shared';
|
||||
|
||||
export function ActivityPanel() {
|
||||
const friends = useSocialStore((s) => s.friends);
|
||||
|
||||
@@ -53,12 +53,12 @@ export function AppLayout() {
|
||||
|
||||
// MutationObserver: neutralize rogue LiveKit <audio> elements that bypass our Web Audio pipeline.
|
||||
// LiveKit can re-attach hidden <audio> elements after .detach(), causing full-volume playback
|
||||
// that ignores our volume/mute controls. Any <audio> without data-opencord is immediately killed.
|
||||
// that ignores our volume/mute controls. Any <audio> without data-backspace is immediately killed.
|
||||
useEffect(() => {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node instanceof HTMLAudioElement && !node.dataset.opencord) {
|
||||
if (node instanceof HTMLAudioElement && !node.dataset.backspace) {
|
||||
node.muted = true;
|
||||
node.volume = 0;
|
||||
node.pause();
|
||||
@@ -210,7 +210,7 @@ export function AppLayout() {
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
<p className="text-discord-text-muted">Loading Opencord...</p>
|
||||
<p className="text-discord-text-muted">Loading Backspace...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { MemberWithUser } from '@opencord/shared';
|
||||
import type { MemberWithUser } from '@backspace/shared';
|
||||
import { useServerStore } from '../../stores/serverStore';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
import { Avatar } from '../ui/Avatar';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Avatar } from '../ui/Avatar';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
import { useServerStore } from '../../stores/serverStore';
|
||||
import { api } from '../../api/client';
|
||||
import type { User } from '@opencord/shared';
|
||||
import type { User } from '@backspace/shared';
|
||||
|
||||
export function AddDmMemberModal() {
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Avatar } from '../ui/Avatar';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
import { useServerStore } from '../../stores/serverStore';
|
||||
import { api } from '../../api/client';
|
||||
import type { User } from '@opencord/shared';
|
||||
import type { User } from '@backspace/shared';
|
||||
|
||||
export function NewDmModal() {
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Avatar } from '../ui/Avatar';
|
||||
import { api } from '../../api/client';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { hasPermissionBit, PermissionBits } from '../../utils/permissions';
|
||||
import type { InstanceStreamingLimits } from '@opencord/shared';
|
||||
import type { InstanceStreamingLimits } from '@backspace/shared';
|
||||
|
||||
const VALID_RESOLUTIONS = [540, 720, 1080] as const;
|
||||
const VALID_FRAMERATES = [30, 45, 60] as const;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import type { User } from '@opencord/shared';
|
||||
import type { User } from '@backspace/shared';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
|
||||
interface AvatarProps {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { User } from '@opencord/shared';
|
||||
import type { User } from '@backspace/shared';
|
||||
import { Avatar } from '../ui/Avatar';
|
||||
import { api } from '../../api/client';
|
||||
import { useServerStore } from '../../stores/serverStore';
|
||||
@@ -60,7 +60,7 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout
|
||||
<div className="w-full h-[1px] bg-discord-modifier-accent mb-3" />
|
||||
|
||||
<div className="mb-3">
|
||||
<div className="text-[12px] font-bold text-discord-text-header uppercase mb-1">Opencord Member Since</div>
|
||||
<div className="text-[12px] font-bold text-discord-text-header uppercase mb-1">Backspace Member Since</div>
|
||||
<div className="text-[12px] text-discord-text-normal font-medium">
|
||||
{new Date(user.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@ function AudioTrackElement({
|
||||
// The <audio> element is always muted — it serves only as a Chrome
|
||||
// keep-alive so Chrome continues processing the WebRTC track.
|
||||
// Real audio output goes through the Web Audio pipeline.
|
||||
return <audio ref={audioRef} autoPlay playsInline data-opencord="keepalive" />;
|
||||
return <audio ref={audioRef} autoPlay playsInline data-backspace="keepalive" />;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useChatStore } from '../stores/chatStore';
|
||||
import { useVoiceStore } from '../stores/voiceStore';
|
||||
import { useSocialStore } from '../stores/socialStore';
|
||||
import { useSettingsStore } from '../stores/settingsStore';
|
||||
import type { ServerEvent, ClientEvent, ActiveCallInfo } from '@opencord/shared';
|
||||
import type { ServerEvent, ClientEvent, ActiveCallInfo } from '@backspace/shared';
|
||||
|
||||
let globalWs: WebSocket | null = null;
|
||||
let reconnectAttempts = 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { create } from 'zustand';
|
||||
import type { User } from '@opencord/shared';
|
||||
import type { User } from '@backspace/shared';
|
||||
import { api } from '../api/client';
|
||||
import { useChatStore } from './chatStore';
|
||||
import { useServerStore } from './serverStore';
|
||||
@@ -21,7 +21,7 @@ interface AuthState {
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
token: localStorage.getItem('opencord_token'),
|
||||
token: localStorage.getItem('backspace_token'),
|
||||
user: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
@@ -30,7 +30,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
const response = await api.auth.login({ username, password });
|
||||
localStorage.setItem('opencord_token', response.token);
|
||||
localStorage.setItem('backspace_token', response.token);
|
||||
set({ token: response.token, user: response.user, isLoading: false });
|
||||
} catch (err) {
|
||||
set({ isLoading: false, error: err instanceof Error ? err.message : 'Login failed' });
|
||||
@@ -42,7 +42,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
const response = await api.auth.register({ username, password, displayName });
|
||||
localStorage.setItem('opencord_token', response.token);
|
||||
localStorage.setItem('backspace_token', response.token);
|
||||
set({ token: response.token, user: response.user, isLoading: false });
|
||||
} catch (err) {
|
||||
set({ isLoading: false, error: err instanceof Error ? err.message : 'Registration failed' });
|
||||
@@ -51,7 +51,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
localStorage.removeItem('opencord_token');
|
||||
localStorage.removeItem('backspace_token');
|
||||
// Clear all user-scoped state to prevent data leaking between sessions
|
||||
useChatStore.getState().clearAllMessages();
|
||||
useServerStore.getState().populateFromReady([], [], []);
|
||||
@@ -69,7 +69,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
const user = await api.users.me();
|
||||
set({ user, isLoading: false });
|
||||
} catch {
|
||||
localStorage.removeItem('opencord_token');
|
||||
localStorage.removeItem('backspace_token');
|
||||
set({ token: null, user: null, isLoading: false });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { create } from 'zustand';
|
||||
import type { MessageWithUser, Reaction, ReadState } from '@opencord/shared';
|
||||
import type { MessageWithUser, Reaction, ReadState } from '@backspace/shared';
|
||||
import { api } from '../api/client';
|
||||
import { wsSend } from '../hooks/useWebSocket';
|
||||
import { isDmChannel, useServerStore } from './serverStore';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { create } from 'zustand';
|
||||
import type { Server, Channel, MemberWithUser, ServerWithChannelsAndMembers, Role, ServerFolder, DmChannel, User } from '@opencord/shared';
|
||||
import type { Server, Channel, MemberWithUser, ServerWithChannelsAndMembers, Role, ServerFolder, DmChannel, User } from '@backspace/shared';
|
||||
import { api } from '../api/client';
|
||||
|
||||
interface ServerState {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { create } from 'zustand';
|
||||
import type { InstanceStreamingLimits } from '@opencord/shared';
|
||||
import type { InstanceStreamingLimits } from '@backspace/shared';
|
||||
import { api } from '../api/client';
|
||||
|
||||
interface SettingsState {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { create } from 'zustand';
|
||||
import type { Friend, FriendRequest, User } from '@opencord/shared';
|
||||
import type { Friend, FriendRequest, User } from '@backspace/shared';
|
||||
import { api } from '../api/client';
|
||||
|
||||
interface SocialState {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import type { User } from '@opencord/shared';
|
||||
import type { User } from '@backspace/shared';
|
||||
|
||||
type ModalType =
|
||||
| 'createServer'
|
||||
@@ -100,7 +100,7 @@ export const useUIStore = create<UIState>()(
|
||||
setPipCollapsed: (collapsed) => set({ pipCollapsed: collapsed }),
|
||||
}),
|
||||
{
|
||||
name: 'opencord-ui-settings',
|
||||
name: 'backspace-ui-settings',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
partialize: (state) => ({
|
||||
memberListOpen: state.memberListOpen,
|
||||
|
||||
@@ -323,7 +323,7 @@ export const useVoiceStore = create<VoiceState>()(
|
||||
}),
|
||||
}),
|
||||
{
|
||||
name: 'opencord-voice-settings',
|
||||
name: 'backspace-voice-settings',
|
||||
version: 6,
|
||||
migrate: (persistedState: any, version: number) => {
|
||||
if (version === 0) {
|
||||
|
||||
@@ -8,4 +8,4 @@ export {
|
||||
hasPermissionBit,
|
||||
permissionsToString,
|
||||
stringToPermissions,
|
||||
} from '@opencord/shared/src/permissions';
|
||||
} from '@backspace/shared/src/permissions';
|
||||
|
||||
Reference in New Issue
Block a user