diff --git a/src/assets/styles/variables.scss b/src/assets/styles/variables.scss
index 74108c6..6fd23bb 100644
--- a/src/assets/styles/variables.scss
+++ b/src/assets/styles/variables.scss
@@ -20,6 +20,8 @@ $unit-8x: $unit * 8;
$unit-10x: $unit * 10;
$unit-12x: $unit * 12;
$unit-14x: $unit * 14;
+$unit-16x: $unit * 16;
+$unit-18x: $unit * 18;
$unit-20x: $unit * 20;
/* Page properties
diff --git a/src/lib/components/ProjectItem.svelte b/src/lib/components/ProjectItem.svelte
index e1fb196..1d24ae9 100644
--- a/src/lib/components/ProjectItem.svelte
+++ b/src/lib/components/ProjectItem.svelte
@@ -188,22 +188,55 @@
{@html highlightedDescription}
-
+
{#if isListOnly}
-
{:else if isPasswordProtected}
-
-
-
-
+
+
+
+
Password Required
@@ -248,7 +281,7 @@
}
&.odd {
- flex-direction: row-reverse;
+ // flex-direction: row-reverse;
}
}
diff --git a/src/lib/components/SegmentedController.svelte b/src/lib/components/SegmentedController.svelte
index 08c693b..31ae2df 100644
--- a/src/lib/components/SegmentedController.svelte
+++ b/src/lib/components/SegmentedController.svelte
@@ -16,8 +16,8 @@
const navItems: NavItem[] = [
{ icon: WorkIcon, text: 'Work', href: '/', variant: 'work' },
- { icon: PhotosIcon, text: 'Photos', href: '/photos', variant: 'photos' },
{ icon: LabsIcon, text: 'Labs', href: '/labs', variant: 'labs' },
+ { icon: PhotosIcon, text: 'Photos', href: '/photos', variant: 'photos' },
{ icon: UniverseIcon, text: 'Universe', href: '/universe', variant: 'universe' }
]
@@ -28,9 +28,9 @@
const activeIndex = $derived(
currentPath === '/'
? 0
- : currentPath.startsWith('/photos')
+ : currentPath.startsWith('/labs')
? 1
- : currentPath.startsWith('/labs')
+ : currentPath.startsWith('/photos')
? 2
: currentPath.startsWith('/universe')
? 3
diff --git a/src/lib/components/admin/AdminPage.svelte b/src/lib/components/admin/AdminPage.svelte
index 544fc21..5f3d840 100644
--- a/src/lib/components/admin/AdminPage.svelte
+++ b/src/lib/components/admin/AdminPage.svelte
@@ -31,6 +31,7 @@
margin: 0 auto $unit-6x;
width: calc(100% - #{$unit-6x});
max-width: 900px; // Much wider for admin
+ min-height: calc(100vh - #{$unit-16x}); // Full height minus margins
overflow: hidden; // Ensure border-radius clips content
&:first-child {
diff --git a/src/lib/components/admin/Button.svelte b/src/lib/components/admin/Button.svelte
index ea338a2..2a9fde4 100644
--- a/src/lib/components/admin/Button.svelte
+++ b/src/lib/components/admin/Button.svelte
@@ -3,7 +3,7 @@
interface Props extends HTMLButtonAttributes {
variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'text' | 'overlay'
- size?: 'small' | 'medium' | 'large' | 'icon'
+ buttonSize?: 'small' | 'medium' | 'large' | 'icon'
iconOnly?: boolean
iconPosition?: 'left' | 'right'
pill?: boolean
@@ -16,7 +16,7 @@
let {
variant = 'primary',
- size = 'medium',
+ buttonSize = 'medium',
iconOnly = false,
iconPosition = 'left',
pill = true,
@@ -41,10 +41,10 @@
// Size
if (!iconOnly) {
- classes.push(`btn-${size}`)
+ classes.push(`btn-${buttonSize}`)
} else {
classes.push('btn-icon')
- classes.push(`btn-icon-${size}`)
+ classes.push(`btn-icon-${buttonSize}`)
}
// States
@@ -254,8 +254,8 @@
}
&.btn-icon-large {
- width: 40px;
- height: 40px;
+ width: 44px;
+ height: 44px;
border-radius: 10px;
}
diff --git a/src/lib/components/admin/DeleteConfirmationModal.svelte b/src/lib/components/admin/DeleteConfirmationModal.svelte
index 6e4c838..aa3dc38 100644
--- a/src/lib/components/admin/DeleteConfirmationModal.svelte
+++ b/src/lib/components/admin/DeleteConfirmationModal.svelte
@@ -64,7 +64,7 @@
display: flex;
align-items: center;
justify-content: center;
- z-index: 1000;
+ z-index: 1050;
}
.modal {
diff --git a/src/lib/components/admin/DropdownItem.svelte b/src/lib/components/admin/DropdownItem.svelte
new file mode 100644
index 0000000..315d8a6
--- /dev/null
+++ b/src/lib/components/admin/DropdownItem.svelte
@@ -0,0 +1,59 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/admin/DropdownMenu.svelte b/src/lib/components/admin/DropdownMenu.svelte
new file mode 100644
index 0000000..70308b8
--- /dev/null
+++ b/src/lib/components/admin/DropdownMenu.svelte
@@ -0,0 +1,134 @@
+
+
+{#if isOpen && browser}
+
+{/if}
+
+
\ No newline at end of file
diff --git a/src/lib/components/admin/DropdownMenuContainer.svelte b/src/lib/components/admin/DropdownMenuContainer.svelte
new file mode 100644
index 0000000..55d2551
--- /dev/null
+++ b/src/lib/components/admin/DropdownMenuContainer.svelte
@@ -0,0 +1,28 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/admin/EditorWithUpload.svelte b/src/lib/components/admin/EditorWithUpload.svelte
index 7b5e346..68b161d 100644
--- a/src/lib/components/admin/EditorWithUpload.svelte
+++ b/src/lib/components/admin/EditorWithUpload.svelte
@@ -206,28 +206,54 @@
}
})
- // Custom image paste handler
- function handleImagePaste(view: any, event: ClipboardEvent) {
- const item = event.clipboardData?.items[0]
+ // Custom paste handler for both images and text
+ function handlePaste(view: any, event: ClipboardEvent) {
+ const clipboardData = event.clipboardData
+ if (!clipboardData) return false
- if (item?.type.indexOf('image') !== 0) {
- return false
+ // Check for images first
+ const imageItem = Array.from(clipboardData.items).find(item => item.type.indexOf('image') === 0)
+ if (imageItem) {
+ const file = imageItem.getAsFile()
+ if (!file) return false
+
+ // Check file size (2MB max)
+ const filesize = file.size / 1024 / 1024
+ if (filesize > 2) {
+ alert(`Image too large! File size: ${filesize.toFixed(2)} MB (max 2MB)`)
+ return true
+ }
+
+ // Upload to our media API
+ uploadImage(file)
+ return true // Prevent default paste behavior
}
- const file = item.getAsFile()
- if (!file) return false
-
- // Check file size (2MB max)
- const filesize = file.size / 1024 / 1024
- if (filesize > 2) {
- alert(`Image too large! File size: ${filesize.toFixed(2)} MB (max 2MB)`)
- return true
+ // Handle text paste - strip HTML formatting
+ const htmlData = clipboardData.getData('text/html')
+ const plainText = clipboardData.getData('text/plain')
+
+ if (htmlData && plainText) {
+ // If we have both HTML and plain text, use plain text to strip formatting
+ event.preventDefault()
+
+ // Use editor commands to insert text so all callbacks are triggered
+ const editorInstance = (view as any).editor
+ if (editorInstance) {
+ editorInstance.chain().focus().insertContent(plainText).run()
+ } else {
+ // Fallback to manual transaction
+ const { state, dispatch } = view
+ const { selection } = state
+ const transaction = state.tr.insertText(plainText, selection.from, selection.to)
+ dispatch(transaction)
+ }
+
+ return true // Prevent default paste behavior
}
- // Upload to our media API
- uploadImage(file)
-
- return true // Prevent default paste behavior
+ // Let default handling take care of plain text only
+ return false
}
async function uploadImage(file: File) {
@@ -321,9 +347,10 @@
attributes: {
class: 'prose prose-sm max-w-none focus:outline-none'
},
- handlePaste: handleImagePaste
+ handlePaste: handlePaste
}
- }
+ },
+ placeholder
)
// Add placeholder
diff --git a/src/lib/components/admin/EssayForm.svelte b/src/lib/components/admin/EssayForm.svelte
index 1406133..ba17ea8 100644
--- a/src/lib/components/admin/EssayForm.svelte
+++ b/src/lib/components/admin/EssayForm.svelte
@@ -204,7 +204,7 @@
-
@@ -307,7 +307,7 @@