Workspace UI: AI/ressurs-paneler, innstillinger, kontekst-velger
- AI-verktøy og Ressursforbruk registrert som BlockShell-paneler i verktøymenyen (🤖 og 📊) - Innstillingsmeny (⚙️) lengst til høyre i header: tre hue-slidere (bakgrunn, overflate, aksent) + logg ut. Lagres i workspace-metadata. - Kontekst-velger: to grupper (Mine flater / Delte flater), inline rename (✏️), "+ Ny arbeidsflate"-knapp - Mørke overrides for manglende Tailwind bg-farger i app.css Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a1a1b8c460
commit
d82fab25df
4 changed files with 430 additions and 30 deletions
|
|
@ -4,20 +4,21 @@ Funnet ved manuell testing av frontend. Fikses som en samlet sesjon.
|
|||
|
||||
## Workspace
|
||||
|
||||
- [ ] AI-verktøy er hardkodet utenfor workspace (footer). Skal være et valgfritt BlockShell-panel som alle andre verktøy.
|
||||
- [ ] Ressursforbruk er hardkodet utenfor workspace. Skal være et valgfritt BlockShell-panel.
|
||||
- [x] AI-verktøy er nå et valgfritt BlockShell-panel (🤖 i verktøymenyen).
|
||||
- [x] Ressursforbruk er nå et valgfritt BlockShell-panel (📊 i verktøymenyen).
|
||||
- [x] BlockShell-knapper (minimer, maksimer, lukk) fikset:
|
||||
- Minimer → kollapser til kompakt ikon/fane, bevarer posisjon
|
||||
- Maksimer → fullskjerm overlay (portalt til body), Escape for å gå tilbake
|
||||
- Lukk → fjern panel fra workspace
|
||||
- [x] Kanban-panel kan nå lukkes (samme fix)
|
||||
- [ ] Fjern footer-feltet helt. Alt som var der (AI, ressurs) blir paneler i canvas. Canvas får full høyde.
|
||||
- [x] Ingen footer — canvas har full høyde.
|
||||
- [ ] Workspace-modifikatorer (zoom-knapper, fullskjerm, snap-to-grid, tilpass) er uvirksomme. Zoom via musehjul fungerer.
|
||||
|
||||
## Header / innstillinger
|
||||
|
||||
- [ ] Fargevelger i header-meny (⚙️): bakgrunn, overflate, accent-hue. Lagres i person-node metadata.preferences.theme. Tre slidere er nok.
|
||||
- [ ] Innstillingsmeny (⚙️-ikon i header): tema, varsler, profil. Ikke et panel i workspace — det styrer *hele* brukeropplevelsen.
|
||||
- [x] Innstillingsmeny (⚙️-ikon lengst til høyre i header): tre hue-slidere (bakgrunn, overflate, aksent). Lagres i workspace-node metadata.preferences.theme.
|
||||
- [x] Logg ut-knapp i innstillingsmenyen.
|
||||
- [ ] Gjenstående: varsler, profil-innstillinger.
|
||||
|
||||
## Stor refaktor: workspace er appen
|
||||
|
||||
|
|
@ -40,16 +41,15 @@ Funnet ved manuell testing av frontend. Fikses som en samlet sesjon.
|
|||
|
||||
## Kontekst-velger (arbeidsflate-dropdown i header)
|
||||
|
||||
- [ ] Vis "Mine flater" og "Delte flater" som to grupper i dropdown.
|
||||
- [ ] ✏️-ikon på egne flater for inline rename (klikk, skriv, enter).
|
||||
- [ ] "+ Ny arbeidsflate"-knapp nederst i dropdown → opprett blank workspace-node.
|
||||
- [ ] "Del med..."-handling (høyreklikk eller ⚙️) → velg person/team, velg rolle → member_of-edge.
|
||||
- [ ] Flaten dukker opp under "Delte flater" hos mottaker.
|
||||
- [ ] Bruk begrepet "arbeidsflate" konsekvent, ikke "workspace".
|
||||
- [x] Vis "Mine flater" og "Delte flater" som to grupper i dropdown.
|
||||
- [x] ✏️-ikon på egne flater for inline rename (klikk, skriv, enter).
|
||||
- [x] "+ Ny arbeidsflate"-knapp nederst i dropdown → opprett blank workspace-node.
|
||||
- [ ] "Del med..."-handling → velg person/team, velg rolle → member_of-edge. (v2)
|
||||
- [x] Bruk begrepet "arbeidsflate" konsekvent, ikke "workspace".
|
||||
|
||||
## Tema (pågår)
|
||||
## Tema
|
||||
|
||||
- [x] Mørkt tema: arbeidsflaten (canvas + header)
|
||||
- [x] Mørkt tema: canvas-bakgrunn + grid-linjer
|
||||
- [x] Hue-slidere for bakgrunn, overflate, aksent (lagres i workspace-metadata)
|
||||
- [ ] Gjenstående lyse elementer i chat, board, kalender, admin (CSS-override dekker noe, men hardkodede farger i style-blokker gjenstår)
|
||||
- [ ] Lys/mørk-toggle i innstillingsmeny
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ div[style*="display: contents"] { background-color: var(--color-bg) !important;
|
|||
.hover\:bg-indigo-600:hover, .hover\:bg-indigo-700:hover { background-color: #7577f5 !important; }
|
||||
.text-indigo-600, .text-indigo-500, .text-indigo-700 { color: #6366f1 !important; }
|
||||
.bg-indigo-50, .bg-indigo-100 { background-color: rgba(99, 102, 241, 0.15) !important; }
|
||||
.bg-purple-50 { background-color: rgba(139, 92, 246, 0.1) !important; }
|
||||
.bg-green-50 { background-color: rgba(34, 197, 94, 0.1) !important; }
|
||||
.bg-red-50 { background-color: rgba(239, 68, 68, 0.1) !important; }
|
||||
.bg-blue-50 { background-color: rgba(59, 130, 246, 0.1) !important; }
|
||||
.bg-yellow-50 { background-color: rgba(234, 179, 8, 0.1) !important; }
|
||||
.border-indigo-300 { border-color: #6366f1 !important; }
|
||||
.hover\:border-indigo-300:hover { border-color: #6366f1 !important; }
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ export const TRAIT_PANEL_INFO: Record<string, TraitPanelInfo> = {
|
|||
mixer: { title: 'Mikser', icon: '🎚️', defaultWidth: 450, defaultHeight: 400 },
|
||||
orchestration: { title: 'Orkestrering', icon: '⚡', defaultWidth: 550, defaultHeight: 500 },
|
||||
mindmap: { title: 'Tankekart', icon: '🧠', defaultWidth: 600, defaultHeight: 500 },
|
||||
ai: { title: 'AI-verktøy', icon: '🤖', defaultWidth: 420, defaultHeight: 500 },
|
||||
usage: { title: 'Ressursforbruk', icon: '📊', defaultWidth: 380, defaultHeight: 350 },
|
||||
};
|
||||
|
||||
/** Default info for unknown traits */
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import { connectionState, nodeStore, edgeStore, nodeAccessStore, nodeVisibility } from '$lib/realtime';
|
||||
import type { Node } from '$lib/realtime';
|
||||
import { fetchMyWorkspace, updateNode } from '$lib/api';
|
||||
import { fetchMyWorkspace, updateNode, createNode, createEdge } from '$lib/api';
|
||||
import { signOut } from '@auth/sveltekit/client';
|
||||
|
||||
// Canvas + BlockShell
|
||||
import Canvas from '$lib/components/canvas/Canvas.svelte';
|
||||
|
|
@ -36,6 +37,8 @@
|
|||
import MixerTrait from '$lib/components/traits/MixerTrait.svelte';
|
||||
import MindMapTrait from '$lib/components/traits/MindMapTrait.svelte';
|
||||
import GenericTrait from '$lib/components/traits/GenericTrait.svelte';
|
||||
import AiToolPanel from '$lib/components/AiToolPanel.svelte';
|
||||
import NodeUsage from '$lib/components/NodeUsage.svelte';
|
||||
|
||||
import { createBlockReceiver, executeTransfer, resolveTransferMode, type DragPayload } from '$lib/transfer';
|
||||
import type { BlockReceiver } from '$lib/components/blockshell/types';
|
||||
|
|
@ -70,6 +73,10 @@
|
|||
} else if (!layoutInitialized) {
|
||||
layoutInitialized = true;
|
||||
}
|
||||
// Load theme preferences
|
||||
if (res.metadata) {
|
||||
loadThemeFromMetadata(res.metadata as Record<string, unknown>);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
workspaceError = err.message;
|
||||
|
|
@ -120,12 +127,12 @@
|
|||
|
||||
let saveTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
function persistLayout() {
|
||||
/** Persist all workspace metadata (layout + preferences) */
|
||||
function persistMetadata() {
|
||||
if (!accessToken || !workspaceNodeId) return;
|
||||
clearTimeout(saveTimeout);
|
||||
saveTimeout = setTimeout(async () => {
|
||||
try {
|
||||
// Read current metadata from node store
|
||||
const currentMeta = workspaceNode
|
||||
? JSON.parse(workspaceNode.metadata ?? '{}')
|
||||
: {};
|
||||
|
|
@ -134,14 +141,21 @@
|
|||
metadata: {
|
||||
...currentMeta,
|
||||
workspace_layout: layout,
|
||||
preferences: {
|
||||
...(currentMeta.preferences ?? {}),
|
||||
theme: { hueBg: themeHueBg, hueSurface: themeHueSurface, hueAccent: themeHueAccent },
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('Failed to persist workspace layout:', err);
|
||||
console.warn('Failed to persist workspace metadata:', err);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Keep old name as alias for callers
|
||||
function persistLayout() { persistMetadata(); }
|
||||
|
||||
function handleObjectMove(id: string, x: number, y: number) {
|
||||
const idx = layout.panels.findIndex(p => p.trait === id);
|
||||
if (idx >= 0) {
|
||||
|
|
@ -240,11 +254,67 @@
|
|||
return collectionNodes.filter(n => (n.title ?? '').toLowerCase().includes(q));
|
||||
});
|
||||
|
||||
const myCollections = $derived(filteredCollections.filter(n => n.createdBy === nodeId));
|
||||
const sharedCollections = $derived(filteredCollections.filter(n => n.createdBy !== nodeId));
|
||||
|
||||
let renamingId = $state<string | undefined>(undefined);
|
||||
let renameValue = $state('');
|
||||
|
||||
function startRename(node: Node) {
|
||||
renamingId = node.id;
|
||||
renameValue = node.title || '';
|
||||
}
|
||||
|
||||
async function commitRename() {
|
||||
if (!renamingId || !accessToken) return;
|
||||
const id = renamingId;
|
||||
renamingId = undefined;
|
||||
if (renameValue.trim()) {
|
||||
try {
|
||||
await updateNode(accessToken, { node_id: id, title: renameValue.trim() });
|
||||
} catch (e) {
|
||||
console.error('Feil ved omdøping:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleRenameKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') { e.preventDefault(); commitRename(); }
|
||||
if (e.key === 'Escape') { renamingId = undefined; }
|
||||
}
|
||||
|
||||
let isCreatingWorkspace = $state(false);
|
||||
|
||||
async function createNewWorkspace() {
|
||||
if (!accessToken || !nodeId || isCreatingWorkspace) return;
|
||||
isCreatingWorkspace = true;
|
||||
selectorOpen = false;
|
||||
try {
|
||||
const { node_id } = await createNode(accessToken, {
|
||||
node_kind: 'collection',
|
||||
title: 'Ny arbeidsflate',
|
||||
visibility: 'hidden',
|
||||
});
|
||||
await createEdge(accessToken, {
|
||||
source_id: nodeId,
|
||||
target_id: node_id,
|
||||
edge_type: 'owner',
|
||||
});
|
||||
goto(`/collection/${node_id}`);
|
||||
} catch (e) {
|
||||
console.error('Feil ved oppretting av arbeidsflate:', e);
|
||||
} finally {
|
||||
isCreatingWorkspace = false;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSelector() {
|
||||
selectorOpen = !selectorOpen;
|
||||
toolMenuOpen = false;
|
||||
settingsOpen = false;
|
||||
if (selectorOpen) {
|
||||
searchQuery = '';
|
||||
renamingId = undefined;
|
||||
requestAnimationFrame(() => searchInput?.focus());
|
||||
}
|
||||
}
|
||||
|
|
@ -271,6 +341,7 @@
|
|||
function toggleToolMenu() {
|
||||
toolMenuOpen = !toolMenuOpen;
|
||||
selectorOpen = false;
|
||||
settingsOpen = false;
|
||||
}
|
||||
|
||||
function addTool(trait: string) {
|
||||
|
|
@ -278,6 +349,48 @@
|
|||
toolMenuOpen = false;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Settings menu (theme + sign out)
|
||||
// =========================================================================
|
||||
|
||||
let settingsOpen = $state(false);
|
||||
let themeHueBg = $state(0);
|
||||
let themeHueSurface = $state(0);
|
||||
let themeHueAccent = $state(240); // default indigo ≈ 240
|
||||
|
||||
function toggleSettings() {
|
||||
settingsOpen = !settingsOpen;
|
||||
selectorOpen = false;
|
||||
toolMenuOpen = false;
|
||||
}
|
||||
|
||||
function hslColor(hue: number, sat: number, light: number): string {
|
||||
return `hsl(${hue}, ${sat}%, ${light}%)`;
|
||||
}
|
||||
|
||||
function applyTheme() {
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty('--color-bg', hslColor(themeHueBg, themeHueBg ? 10 : 0, 4));
|
||||
root.style.setProperty('--color-surface', hslColor(themeHueSurface, themeHueSurface ? 8 : 0, 12));
|
||||
root.style.setProperty('--color-surface-hover', hslColor(themeHueSurface, themeHueSurface ? 6 : 0, 15));
|
||||
root.style.setProperty('--color-border', hslColor(themeHueSurface, themeHueSurface ? 5 : 0, 18));
|
||||
root.style.setProperty('--color-accent', hslColor(themeHueAccent, 70, 60));
|
||||
root.style.setProperty('--color-accent-hover', hslColor(themeHueAccent, 70, 65));
|
||||
root.style.setProperty('--color-accent-glow', `hsla(${themeHueAccent}, 70%, 60%, 0.15)`);
|
||||
persistMetadata();
|
||||
}
|
||||
|
||||
function loadThemeFromMetadata(meta: Record<string, unknown>) {
|
||||
const prefs = meta.preferences as Record<string, unknown> | undefined;
|
||||
const theme = prefs?.theme as Record<string, number> | undefined;
|
||||
if (theme) {
|
||||
themeHueBg = theme.hueBg ?? 0;
|
||||
themeHueSurface = theme.hueSurface ?? 0;
|
||||
themeHueAccent = theme.hueAccent ?? 240;
|
||||
applyTheme();
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Mobile detection
|
||||
// =========================================================================
|
||||
|
|
@ -310,19 +423,24 @@
|
|||
if (toolMenuOpen && !target.closest('.tool-menu')) {
|
||||
toolMenuOpen = false;
|
||||
}
|
||||
if (settingsOpen && !target.closest('.settings-menu')) {
|
||||
settingsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
selectorOpen = false;
|
||||
toolMenuOpen = false;
|
||||
settingsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Trait components that have dedicated implementations */
|
||||
const knownTraits = new Set([
|
||||
'editor', 'chat', 'kanban', 'podcast', 'publishing',
|
||||
'rss', 'calendar', 'recording', 'transcription', 'studio', 'mixer', 'mindmap'
|
||||
'rss', 'calendar', 'recording', 'transcription', 'studio', 'mixer', 'mindmap',
|
||||
'ai', 'usage'
|
||||
]);
|
||||
|
||||
// =========================================================================
|
||||
|
|
@ -402,24 +520,65 @@
|
|||
bind:this={searchInput}
|
||||
bind:value={searchQuery}
|
||||
type="text"
|
||||
placeholder="Søk samlinger..."
|
||||
placeholder="Søk arbeidsflater..."
|
||||
class="context-selector-search-input"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
<div class="context-selector-list">
|
||||
{#each filteredCollections as node (node.id)}
|
||||
{#if myCollections.length > 0}
|
||||
<div class="context-selector-group-label">Mine flater</div>
|
||||
{#each myCollections as node (node.id)}
|
||||
<div class="context-selector-item-row">
|
||||
{#if renamingId === node.id}
|
||||
<input
|
||||
class="context-selector-rename-input"
|
||||
bind:value={renameValue}
|
||||
onblur={commitRename}
|
||||
onkeydown={handleRenameKeydown}
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
{:else}
|
||||
<button
|
||||
class="context-selector-item"
|
||||
onclick={() => selectCollection(node.id)}
|
||||
>
|
||||
<span class="context-selector-item-title">{node.title || 'Uten tittel'}</span>
|
||||
</button>
|
||||
{:else}
|
||||
<div class="context-selector-empty">
|
||||
{searchQuery ? 'Ingen treff' : 'Ingen samlinger'}
|
||||
<button
|
||||
class="context-selector-rename-btn"
|
||||
onclick={(e) => { e.stopPropagation(); startRename(node); }}
|
||||
title="Gi nytt navn"
|
||||
>✏️</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if sharedCollections.length > 0}
|
||||
<div class="context-selector-group-label">Delte flater</div>
|
||||
{#each sharedCollections as node (node.id)}
|
||||
<button
|
||||
class="context-selector-item"
|
||||
onclick={() => selectCollection(node.id)}
|
||||
>
|
||||
<span class="context-selector-item-title">{node.title || 'Uten tittel'}</span>
|
||||
</button>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if myCollections.length === 0 && sharedCollections.length === 0}
|
||||
<div class="context-selector-empty">
|
||||
{searchQuery ? 'Ingen treff' : 'Ingen arbeidsflater'}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="context-selector-footer">
|
||||
<button
|
||||
class="context-selector-new-btn"
|
||||
onclick={createNewWorkspace}
|
||||
disabled={isCreatingWorkspace}
|
||||
>
|
||||
+ Ny arbeidsflate
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -463,6 +622,44 @@
|
|||
{:else}
|
||||
<span class="context-status" title="{connectionState.current}">●</span>
|
||||
{/if}
|
||||
|
||||
<div class="settings-menu">
|
||||
<button
|
||||
class="context-btn settings-trigger"
|
||||
onclick={toggleSettings}
|
||||
title="Innstillinger"
|
||||
>
|
||||
⚙
|
||||
</button>
|
||||
|
||||
{#if settingsOpen}
|
||||
<div class="settings-dropdown">
|
||||
<div class="settings-title">Tema</div>
|
||||
<label class="settings-slider">
|
||||
<span class="settings-slider-label">Bakgrunn</span>
|
||||
<input type="range" min="0" max="360" bind:value={themeHueBg} oninput={applyTheme} />
|
||||
</label>
|
||||
<label class="settings-slider">
|
||||
<span class="settings-slider-label">Overflate</span>
|
||||
<input type="range" min="0" max="360" bind:value={themeHueSurface} oninput={applyTheme} />
|
||||
</label>
|
||||
<label class="settings-slider">
|
||||
<span class="settings-slider-label">Aksent</span>
|
||||
<input type="range" min="0" max="360" bind:value={themeHueAccent} oninput={applyTheme} />
|
||||
</label>
|
||||
<div class="settings-divider"></div>
|
||||
{#if $page.data.session?.user}
|
||||
<div class="settings-user">{$page.data.session.user.name}</div>
|
||||
{/if}
|
||||
<button
|
||||
class="settings-signout"
|
||||
onclick={() => signOut()}
|
||||
>
|
||||
Logg ut
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -545,6 +742,12 @@
|
|||
<MixerTrait collection={undefined} config={{}} {accessToken} />
|
||||
{:else if panel.trait === 'mindmap'}
|
||||
<MindMapTrait collection={undefined} config={{}} userId={nodeId} {accessToken} />
|
||||
{:else if panel.trait === 'ai'}
|
||||
<AiToolPanel {accessToken} userId={nodeId} />
|
||||
{:else if panel.trait === 'usage'}
|
||||
{#if nodeId && accessToken}
|
||||
<NodeUsage nodeId={nodeId} {accessToken} />
|
||||
{/if}
|
||||
{/if}
|
||||
{:else}
|
||||
<GenericTrait name={panel.trait} config={{}} />
|
||||
|
|
@ -602,6 +805,12 @@
|
|||
<MixerTrait collection={undefined} config={{}} {accessToken} />
|
||||
{:else if trait === 'mindmap'}
|
||||
<MindMapTrait collection={undefined} config={{}} userId={nodeId} {accessToken} />
|
||||
{:else if trait === 'ai'}
|
||||
<AiToolPanel {accessToken} userId={nodeId} />
|
||||
{:else if trait === 'usage'}
|
||||
{#if nodeId && accessToken}
|
||||
<NodeUsage nodeId={nodeId} {accessToken} />
|
||||
{/if}
|
||||
{/if}
|
||||
{:else}
|
||||
<GenericTrait name={trait} config={{}} />
|
||||
|
|
@ -1045,6 +1254,189 @@
|
|||
min-height: 100%;
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
/* Context selector — groups, rename, new */
|
||||
/* ================================================================= */
|
||||
.context-selector-group-label {
|
||||
padding: 8px 10px 4px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #5a5a66;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.context-selector-item-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.context-selector-item-row .context-selector-item {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.context-selector-rename-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 4px 6px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.context-selector-item-row:hover .context-selector-rename-btn {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.context-selector-rename-btn:hover {
|
||||
opacity: 1 !important;
|
||||
background: #242428;
|
||||
}
|
||||
|
||||
.context-selector-rename-input {
|
||||
flex: 1;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #6366f1 !important;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
background: #141416 !important;
|
||||
color: #e8e8ec !important;
|
||||
margin: 2px 4px;
|
||||
}
|
||||
|
||||
.context-selector-footer {
|
||||
border-top: 1px solid #2a2a2e;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.context-selector-new-btn {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border: 1px dashed #2a2a2e;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: #5a5a66;
|
||||
transition: border-color 0.1s, color 0.1s;
|
||||
}
|
||||
|
||||
.context-selector-new-btn:hover:not(:disabled) {
|
||||
border-color: #6366f1;
|
||||
color: #8a8a96;
|
||||
}
|
||||
|
||||
.context-selector-new-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
/* Settings menu */
|
||||
/* ================================================================= */
|
||||
.settings-menu {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-trigger {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.settings-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
right: 0;
|
||||
min-width: 220px;
|
||||
background: #1c1c20;
|
||||
background: var(--color-surface, #1c1c20);
|
||||
border: 1px solid #2a2a2e;
|
||||
border: 1px solid var(--color-border, #2a2a2e);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
||||
z-index: 50;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #5a5a66;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.settings-slider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.settings-slider-label {
|
||||
font-size: 12px;
|
||||
color: #8a8a96;
|
||||
min-width: 65px;
|
||||
}
|
||||
|
||||
.settings-slider input[type="range"] {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: #2a2a2e !important;
|
||||
border: none !important;
|
||||
border-radius: 2px;
|
||||
outline: none;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.settings-slider input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-accent, #6366f1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.settings-divider {
|
||||
height: 1px;
|
||||
background: #2a2a2e;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.settings-user {
|
||||
font-size: 13px;
|
||||
color: #8a8a96;
|
||||
padding: 4px 0 8px;
|
||||
}
|
||||
|
||||
.settings-signout {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: #8a8a96;
|
||||
text-align: left;
|
||||
transition: background 0.1s;
|
||||
}
|
||||
|
||||
.settings-signout:hover {
|
||||
background: #242428;
|
||||
color: #e8e8ec;
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
/* Responsive */
|
||||
/* ================================================================= */
|
||||
|
|
@ -1063,7 +1455,8 @@
|
|||
max-width: calc(100vw - 24px);
|
||||
}
|
||||
|
||||
.tool-menu-dropdown {
|
||||
.tool-menu-dropdown,
|
||||
.settings-dropdown {
|
||||
max-width: calc(100vw - 24px);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue