+
@@ -209,27 +210,27 @@ onMounted(() => {
cursor: pointer;
align-items: center;
gap: 0.65rem;
- border-radius: 12px;
+ border-radius: 10px;
padding: 0.5rem 0.875rem;
font-size: 0.875rem;
font-weight: 500;
- color: var(--ink-muted);
+ color: rgba(255, 255, 255, 0.85);
transition: background-color 0.15s ease, color 0.15s ease;
}
.nav-link i {
font-size: 0.9rem;
}
.nav-link:hover {
- background-color: var(--primary-soft);
- color: var(--ink);
+ background-color: rgba(255, 255, 255, 0.14);
+ color: #fff;
}
.nav-link.router-link-active {
- background-color: var(--primary-soft);
- color: var(--primary);
+ background-color: rgba(255, 255, 255, 0.2);
+ color: #fff;
font-weight: 600;
}
.app-dark .nav-link.router-link-active {
- color: #9ba6f8;
+ color: #fff;
}
.nav-link.router-link-active::before {
content: '';
@@ -239,6 +240,6 @@ onMounted(() => {
height: 60%;
width: 3px;
border-radius: 999px;
- background: var(--magenta);
+ background: #fff;
}
diff --git a/src/style.css b/src/style.css
index 20fe6ec..2f6ffc6 100644
--- a/src/style.css
+++ b/src/style.css
@@ -3,9 +3,9 @@
@custom-variant dark (&:where(.app-dark, .app-dark *));
@theme {
- /* Display = heavy geometric grotesque (DESIGN.md substitute for ABC Ginto Nord) */
- --font-sans: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
- --font-display: 'Hanken Grotesk', 'Inter', Helvetica, Arial, sans-serif;
+ /* Display = Bricolage Grotesque (characterful grotesque), body = IBM Plex Sans */
+ --font-sans: 'IBM Plex Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ --font-display: 'Bricolage Grotesque', 'IBM Plex Sans', Helvetica, Arial, sans-serif;
/* Blurple — brand primary, remaps every indigo-* utility */
--color-indigo-50: #eef0fe;
@@ -51,42 +51,42 @@
--color-green-900: #073a1f;
--color-green-950: #042313;
- /* Cool indigo-tinted neutrals — remaps every slate-* utility */
- --color-slate-50: #f6f7fb;
- --color-slate-100: #eef0f7;
- --color-slate-200: #dde0ed;
- --color-slate-300: #c1c6dd;
- --color-slate-400: #8e95b5;
- --color-slate-500: #666d92;
- --color-slate-600: #4a5177;
- --color-slate-700: #2d3358;
- --color-slate-800: #1e2353;
- --color-slate-900: #141840;
- --color-slate-950: #0a0d3a;
+ /* Warm neutral stones — remaps every slate-* utility (no indigo tint) */
+ --color-slate-50: #f8f7f4;
+ --color-slate-100: #f0eee9;
+ --color-slate-200: #e5e2db;
+ --color-slate-300: #cfcbc2;
+ --color-slate-400: #a3a09a;
+ --color-slate-500: #6e6b66;
+ --color-slate-600: #52504c;
+ --color-slate-700: #3b3a37;
+ --color-slate-800: #26262a;
+ --color-slate-900: #17181c;
+ --color-slate-950: #0e0f12;
- --shadow-subtle: rgba(69, 42, 124, 0.1) 0px 3px 34px;
- --shadow-micro: rgba(20, 24, 64, 0.06) 0px 1px 3px;
+ --shadow-subtle: rgba(28, 25, 20, 0.08) 0 8px 32px;
+ --shadow-micro: rgba(28, 25, 20, 0.05) 0 1px 2px;
}
:root {
--font-family: var(--font-sans);
- --ink: #141840;
- --ink-muted: #666d92;
+ --ink: #17181c;
+ --ink-muted: #6e6b66;
--primary: #5865f2;
--magenta: #ec48bd;
- --canvas: #f6f7fb;
+ --canvas: #f8f7f4;
--panel: #ffffff;
- --hairline: #dde0ed;
- --primary-soft: rgba(88, 101, 242, 0.12);
+ --hairline: #e5e2db;
+ --primary-soft: rgba(88, 101, 242, 0.1);
}
.app-dark {
- --ink: #ffffff;
- --ink-muted: #8e95b5;
- --canvas: #0a0d3a;
- --panel: #141840;
- --hairline: #2d3358;
- --primary-soft: rgba(123, 136, 245, 0.2);
+ --ink: #f3f2ef;
+ --ink-muted: #a3a09a;
+ --canvas: #0e0f12;
+ --panel: #17191e;
+ --hairline: #2c2f36;
+ --primary-soft: rgba(123, 136, 245, 0.18);
}
@layer base {
@@ -174,25 +174,25 @@
}
.app-dark .ck.ck-editor {
- --ck-color-base-background: #0a0d3a;
- --ck-color-base-border: #2d3358;
- --ck-color-base-text: #ffffff;
- --ck-color-text: #ffffff;
+ --ck-color-base-background: #0e0f12;
+ --ck-color-base-border: #2c2f36;
+ --ck-color-base-text: #f3f2ef;
+ --ck-color-text: #f3f2ef;
--ck-color-focus-border: #5865f2;
- --ck-color-toolbar-background: #141840;
- --ck-color-toolbar-border: #2d3358;
- --ck-color-dropdown-panel-background: #141840;
- --ck-color-panel-background: #141840;
- --ck-color-panel-border: #2d3358;
- --ck-color-button-default-hover-background: #2d3358;
- --ck-color-button-default-active-background: #2d3358;
- --ck-color-button-on-background: #2d3358;
- --ck-color-input-background: #0a0d3a;
- --ck-color-input-border: #2d3358;
- --ck-color-input-text: #ffffff;
- --ck-color-tooltip-background: #2d3358;
- --ck-color-tooltip-text: #ffffff;
- --ck-color-table-border: #4a5177;
+ --ck-color-toolbar-background: #17191e;
+ --ck-color-toolbar-border: #2c2f36;
+ --ck-color-dropdown-panel-background: #17191e;
+ --ck-color-panel-background: #17191e;
+ --ck-color-panel-border: #2c2f36;
+ --ck-color-button-default-hover-background: #2c2f36;
+ --ck-color-button-default-active-background: #2c2f36;
+ --ck-color-button-on-background: #2c2f36;
+ --ck-color-input-background: #0e0f12;
+ --ck-color-input-border: #2c2f36;
+ --ck-color-input-text: #f3f2ef;
+ --ck-color-tooltip-background: #2c2f36;
+ --ck-color-tooltip-text: #f3f2ef;
+ --ck-color-table-border: #52504c;
--ck-color-link-default: #9ba6f8;
}
}
@@ -203,28 +203,28 @@
}
.field > label {
- @apply mb-1.5 block text-[13px] font-semibold uppercase tracking-[0.06em];
+ @apply mb-1.5 block text-[12px] font-semibold uppercase tracking-[0.08em];
color: var(--ink-muted);
}
.page-title {
font-family: var(--font-display);
- font-size: clamp(28px, 3vw, 38px);
- font-weight: 800;
- line-height: 1.05;
- letter-spacing: -0.02em;
+ font-size: clamp(26px, 2.6vw, 36px);
+ font-weight: 700;
+ line-height: 1.1;
+ letter-spacing: -0.025em;
color: var(--ink);
}
.page-subtitle {
- font-size: 15px;
- line-height: 1.5;
+ font-size: 14px;
+ line-height: 1.55;
color: var(--ink-muted);
}
.muted-note {
font-size: 13px;
- line-height: 1.45;
+ line-height: 1.5;
color: var(--ink-muted);
}
@@ -232,16 +232,16 @@
.eyebrow {
font-size: 11px;
font-weight: 700;
- letter-spacing: 0.18em;
+ letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--ink-muted);
}
.table-title {
font-family: var(--font-display);
- font-size: 1.25rem;
+ font-size: 1.0625rem;
font-weight: 700;
- letter-spacing: -0.01em;
+ letter-spacing: -0.015em;
color: var(--ink);
}
@@ -249,13 +249,33 @@
.panel {
background: var(--panel);
border: 1px solid var(--hairline);
- border-radius: 16px;
+ border-radius: 14px;
+ box-shadow: var(--shadow-micro);
+ }
+
+ /* Table headers read as quiet eyebrows, not rows of body text */
+ .p-datatable .p-datatable-header-cell,
+ .p-treetable .p-treetable-header-cell {
+ text-transform: uppercase;
+ font-size: 0.72rem;
+ letter-spacing: 0.06em;
}
.search-input.p-inputtext,
.search-input.p-select {
border-radius: 12px !important;
}
+
+ /* Numeric columns line up table to table */
+ .p-datatable-table,
+ .p-treetable-table {
+ font-variant-numeric: tabular-nums;
+ }
+
+ /* Restrained "blurple" is an accent, not a wash */
+ ::selection {
+ background: var(--primary-soft);
+ }
}
@media (prefers-reduced-motion: reduce) {
diff --git a/src/theme.ts b/src/theme.ts
index c62eb20..2ca1ef8 100644
--- a/src/theme.ts
+++ b/src/theme.ts
@@ -32,32 +32,32 @@ const green = {
const lightSurface = {
0: '#ffffff',
- 50: '#f6f7fb',
- 100: '#eef0f7',
- 200: '#dde0ed',
- 300: '#c1c6dd',
- 400: '#8e95b5',
- 500: '#666d92',
- 600: '#4a5177',
- 700: '#2d3358',
- 800: '#1e2353',
- 900: '#141840',
- 950: '#0a0d3a',
+ 50: '#f8f7f4',
+ 100: '#f0eee9',
+ 200: '#e5e2db',
+ 300: '#cfcbc2',
+ 400: '#a3a09a',
+ 500: '#6e6b66',
+ 600: '#52504c',
+ 700: '#3b3a37',
+ 800: '#26262a',
+ 900: '#17181c',
+ 950: '#0e0f12',
}
const darkSurface = {
0: '#ffffff',
- 50: '#f6f7fb',
- 100: '#dde0ed',
- 200: '#c1c6dd',
- 300: '#8e95b5',
- 400: '#666d92',
- 500: '#4a5177',
- 600: '#2d3358',
- 700: '#242a4d',
- 800: '#1e2353',
- 900: '#141840',
- 950: '#0a0d3a',
+ 50: '#f8f7f4',
+ 100: '#e5e2db',
+ 200: '#cfcbc2',
+ 300: '#a3a09a',
+ 400: '#6e6b66',
+ 500: '#52504c',
+ 600: '#2c2f36',
+ 700: '#26282e',
+ 800: '#1d1f24',
+ 900: '#17191e',
+ 950: '#0e0f12',
}
export default definePreset(Aura, {
@@ -113,34 +113,34 @@ export default definePreset(Aura, {
background: '#ffffff',
borderColor: '{surface.200}',
color: '{text.color}',
- shadow: 'rgba(69, 42, 124, 0.14) 0px 12px 48px',
+ shadow: 'rgba(24, 22, 18, 0.12) 0px 12px 48px',
},
},
},
dark: {
surface: darkSurface,
text: {
- color: '#ffffff',
+ color: '#f3f2ef',
hoverColor: '#ffffff',
mutedColor: '{surface.300}',
hoverMutedColor: '{surface.200}',
},
primary: {
color: '{primary.400}',
- contrastColor: '#0a0d3a',
+ contrastColor: '#17191e',
hoverColor: '{primary.300}',
activeColor: '{primary.200}',
},
content: {
- background: '#141840',
+ background: '#17191e',
borderColor: '{surface.600}',
},
overlay: {
modal: {
- background: '#141840',
+ background: '#17191e',
borderColor: '{surface.600}',
- color: '#ffffff',
- shadow: 'rgba(0, 0, 0, 0.5) 0px 12px 48px',
+ color: '#f3f2ef',
+ shadow: 'rgba(0, 0, 0, 0.55) 0px 12px 48px',
},
},
},
diff --git a/src/views/auth/LoginView.vue b/src/views/auth/LoginView.vue
index 146f0c1..3d7a4f2 100644
--- a/src/views/auth/LoginView.vue
+++ b/src/views/auth/LoginView.vue
@@ -13,13 +13,15 @@
- MY WORKSPACE
+ MY WORKSPACE
-
+
-
+
+
My Workspace
Sign in
+
Projects, documents and tasks in one place.
@@ -118,6 +122,7 @@ const createPath = ref('/')
const creating = ref(false)
const renameDialog = ref(false)
const renameTitle = ref('')
+const renameTarget = ref<{ id: string; title: string; type: DocumentType } | null>(null)
const moveDialog = ref(false)
const moveTarget = ref
(null)
@@ -294,16 +299,25 @@ async function onCreate() {
function openRename() {
if (doc.value) {
+ renameTarget.value = doc.value
renameTitle.value = doc.value.title
renameDialog.value = true
}
}
+function openRowRename(node: DocumentNode) {
+ renameTarget.value = node
+ renameTitle.value = node.title
+ renameDialog.value = true
+}
+
async function onRename() {
- if (!doc.value || !renameTitle.value) return
+ const target = renameTarget.value
+ if (!target || !renameTitle.value) return
try {
- const updated = await updateDocument(doc.value.id, { title: renameTitle.value, content: doc.value.content })
- doc.value = updated
+ const content = target.type === 'Document' ? (await getDocument(target.id)).content : undefined
+ const updated = await updateDocument(target.id, { title: renameTitle.value, content })
+ if (doc.value?.id === target.id) doc.value = updated
renameDialog.value = false
toast.add({ severity: 'success', summary: 'Renamed', life: 2000 })
await loadTree()
@@ -342,29 +356,31 @@ function findNode(nodes: DocumentNode[], id: string): DocumentNode | null {
return null
}
-function deleteMessage(): string {
- if (!doc.value) return ''
- if (doc.value.type !== 'Folder') return `Delete "${doc.value.title}"?`
- const node = findNode(tree.value, doc.value.id)
+function deleteMessage(target: { id: string; title: string; type: DocumentType }): string {
+ if (target.type !== 'Folder') return `Delete "${target.title}"?`
+ const node = findNode(tree.value, target.id)
const count = node ? countDescendants(node.children) : 0
return count > 0
- ? `Delete folder "${doc.value.title}" and the ${count} item${count === 1 ? '' : 's'} inside it? This cannot be undone.`
- : `Delete folder "${doc.value.title}"?`
+ ? `Delete folder "${target.title}" and the ${count} item${count === 1 ? '' : 's'} inside it? This cannot be undone.`
+ : `Delete folder "${target.title}"?`
}
-function confirmDelete() {
- if (!doc.value) return
+function confirmDelete(target?: { id: string; title: string; type: DocumentType }) {
+ const item = target ?? doc.value
+ if (!item) return
confirm.require({
- message: deleteMessage(),
+ message: deleteMessage(item),
header: 'Delete',
acceptProps: { severity: 'danger' },
rejectProps: { severity: 'secondary', outlined: true },
accept: async () => {
try {
- await deleteDocument(doc.value!.id)
- doc.value = null
- selectedId.value = null
- viewerVisible.value = false
+ await deleteDocument(item.id)
+ if (doc.value?.id === item.id) {
+ doc.value = null
+ selectedId.value = null
+ viewerVisible.value = false
+ }
toast.add({ severity: 'success', summary: 'Deleted', life: 2000 })
await loadTree()
} catch (e) {
diff --git a/src/views/tasks/TasksBoardView.vue b/src/views/tasks/TasksBoardView.vue
index ef6bc6a..e5a6753 100644
--- a/src/views/tasks/TasksBoardView.vue
+++ b/src/views/tasks/TasksBoardView.vue
@@ -1,6 +1,21 @@
-
+
@@ -87,6 +102,26 @@ const members = ref<{ userId: string; displayName: string }[]>([])
const draggingId = ref
(null)
const dragOverStatus = ref(null)
+const taskSearch = ref('')
+const assigneeFilter = ref(null)
+
+const assigneeOptions = computed(() =>
+ members.value.map((m) => ({ label: m.displayName, value: m.userId })),
+)
+
+const filteredTasks = computed(() => {
+ const q = taskSearch.value.toLowerCase()
+ return tasks.value.filter((t) => {
+ if (assigneeFilter.value && t.assigneeId !== assigneeFilter.value) return false
+ if (!q) return true
+ return (
+ t.title.toLowerCase().includes(q) ||
+ (t.description ?? '').toLowerCase().includes(q) ||
+ (t.assigneeName ?? '').toLowerCase().includes(q)
+ )
+ })
+})
+
const dialogVisible = ref(false)
const editingTask = ref(null)
@@ -103,7 +138,7 @@ const columns = [
]
function tasksIn(status: TaskStatus) {
- return tasks.value.filter((t) => t.status === status)
+ return filteredTasks.value.filter((t) => t.status === status)
}
async function load() {