From d5d0fb904a33e9570290da1d7058c6630611146a Mon Sep 17 00:00:00 2001 From: Sticks Date: Fri, 30 Jan 2026 17:58:52 -0500 Subject: [PATCH] remove text based componets, add blog, minor theming changes --- app/assets/css/blog-prose.css | 196 +++++++++++++++++++++++++++ app/assets/css/main.css | 226 +++++++++++++++++-------------- app/components/About.vue | 139 ++++++++++++++++--- app/components/AboutShrimpOS.vue | 148 ++++++++++++++++++++ app/components/Blog.vue | 220 ++++++++++++++++++++++++++++++ app/components/Contact.vue | 115 ++++++++++++---- app/components/DesktopIcon.vue | 12 +- app/components/Experience.vue | 186 ++++++++++++++----------- app/components/Projects.vue | 139 ++++++++++++++----- app/components/TaskbarButton.vue | 72 ++++++++++ app/components/Window.vue | 6 +- app/pages/desktop.vue | 130 ++++++++++++------ app/pages/index.vue | 53 ++++++-- content.config.ts | 16 +++ content/blog/hello-world.md | 136 +++++++++++++++++++ nuxt.config.ts | 12 +- package-lock.json | 22 ++- package.json | 4 +- 18 files changed, 1510 insertions(+), 322 deletions(-) create mode 100644 app/assets/css/blog-prose.css create mode 100644 app/components/AboutShrimpOS.vue create mode 100644 app/components/Blog.vue create mode 100644 app/components/TaskbarButton.vue create mode 100644 content.config.ts create mode 100644 content/blog/hello-world.md diff --git a/app/assets/css/blog-prose.css b/app/assets/css/blog-prose.css new file mode 100644 index 0000000..76f1d7f --- /dev/null +++ b/app/assets/css/blog-prose.css @@ -0,0 +1,196 @@ +/* Nuxt Content Blog Prose Styling + +This file provides styles for blog content rendered via Nuxt Content. +It includes general typography styles as well as specific styles +for Shiki code blocks. + +*/ + +.blog-prose { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.8; + color: #1a1a1a; +} + +/* Headings */ +.blog-prose :is(h1, h2, h3, h4) { + color: #000080; + font-weight: 700; +} + +.blog-prose h1 { + font-size: 2.25rem; + margin: 2rem 0 1rem; + padding-bottom: 0.5rem; + border-bottom: 3px solid #000080; +} + +.blog-prose h2 { + font-size: 1.875rem; + margin: 2rem 0 0.875rem; + padding-bottom: 0.375rem; + border-bottom: 2px solid #808080; +} + +.blog-prose h3 { + font-size: 1.5rem; + margin: 1.75rem 0 0.75rem; +} + +.blog-prose h4 { + font-size: 1.25rem; + margin: 1.5rem 0 0.5rem; +} + +/* Paragraphs + lists */ +.blog-prose p { + margin: 0 0 1.25rem; + font-size: 1.0625rem; +} + +.blog-prose :is(ul, ol) { + margin: 0 0 1.25rem 1.5rem; +} + +.blog-prose li { + margin-bottom: 0.5rem; + line-height: 1.75; +} + +/* Links */ +.blog-prose a { + color: #0000ff; + text-decoration: underline; +} + +.blog-prose a:hover { + color: #0066ff; + background: #f0f0ff; +} + +.blog-prose a:visited { + color: #800080; +} + +/* Strong / emphasis */ +.blog-prose strong { + font-weight: 700; + color: #000; +} + +.blog-prose em { + font-style: italic; +} + +/* Horizontal rule */ +.blog-prose hr { + border: none; + border-top: 2px solid #808080; + margin: 2rem 0; +} + +/* Images */ +.blog-prose img { + border: 2px solid #808080; + border-radius: 4px; + margin: 1.5rem 0; + max-width: 100%; + height: auto; + box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.2); +} + +/* Blockquote */ +.blog-prose blockquote { + border-left: 4px solid #000080; + background: #f0f0f0; + padding: 1rem 1.25rem; + margin: 1.5rem 0; + font-style: italic; + border-radius: 0 4px 4px 0; + box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); +} + +.blog-prose blockquote p { + margin-bottom: 0.5rem; +} + +.blog-prose blockquote p:last-child { + margin-bottom: 0; +} + +/* Tables */ +.blog-prose table { + width: 100%; + border-collapse: collapse; + margin: 1.5rem 0; + border: 2px solid #808080; +} + +.blog-prose th { + background: #000080; + color: #fff; + font-weight: 700; + padding: 0.75rem; + text-align: left; + border: 1px solid #808080; +} + +.blog-prose td { + padding: 0.75rem; + border: 1px solid #808080; +} + +.blog-prose tr:nth-child(even) { + background: #f5f5f5; +} + +/* inline code: code that is NOT within a pre tag */ +.blog-prose :not(pre) > code { + background: #f5f5f5; + border: 1px solid #d0d0d0; + padding: 0.2em 0.4em; + border-radius: 3px; + font-family: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + 'Liberation Mono', 'Courier New', monospace; + font-size: 0.9em; + color: #d63384; +} + +/* ========================================================= + Shiki code blocks + ========================================================= */ +.blog-prose pre.shiki, +.blog-prose pre.shiki * { + background-image: none !important; + filter: none !important; + text-shadow: none !important; +} + +/* The Shiki container */ +.blog-prose pre.shiki { + margin: 1.5rem 0; + padding: 1.25rem 1.5rem; + border-radius: 10px; + overflow-x: auto; + border: 1px solid rgba(0, 0, 0, 0.15); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); + + font-family: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + 'Liberation Mono', 'Courier New', monospace; + font-size: 0.95rem; + line-height: 1.6; +} + +/* Shiki usually includes a inside the
 */
+.blog-prose pre.shiki code {
+    background: transparent !important;
+    border: 0 !important;
+    padding: 0 !important;
+}
+
+.blog-prose pre.shiki .line {
+    display: block;
+    min-height: 1.4em;
+}
diff --git a/app/assets/css/main.css b/app/assets/css/main.css
index 98276c6..72cb784 100644
--- a/app/assets/css/main.css
+++ b/app/assets/css/main.css
@@ -2,124 +2,148 @@
 
 /* Fallout Terminal Theme */
 @layer base {
-  :root {
-    --terminal-green: #00ff41;
-    --terminal-green-dim: #00cc33;
-    --terminal-green-dark: #008822;
-    --terminal-bg: #0a0e0a;
-    --terminal-error: #ff0000;
-  }
+    :root {
+        --terminal-green: #00ff41;
+        --terminal-green-dim: #00cc33;
+        --terminal-green-dark: #008822;
+        --terminal-bg: #0a0e0a;
+        --terminal-error: #ff0000;
+    }
 
-  body {
-    font-family: 'Courier New', Courier, monospace;
-  }
+    body {
+        font-family: 'Courier New', Courier, monospace;
+    }
 
-  /* Windows content styling */
-  .window-content {
-    color: #000000 !important;
-    font-size: 16px;
-    line-height: 1.8;
-    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
-  }
+    /* Windows content styling */
+    .window-content {
+        color: #000000 !important;
+        font-size: 16px;
+        line-height: 1.8;
+        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+    }
 
-  .window-content *,
-  .window-content p,
-  .window-content span,
-  .window-content div,
-  .window-content pre {
-    color: #000000 !important;
-    text-shadow: none !important;
-  }
+    .window-content h1,
+    .window-content h2,
+    .window-content p,
+    .window-content span,
+    .window-content div,
+    .window-content pre {
+        color: #000000 !important;
+        text-shadow: none !important;
+    }
 
-  .window-content pre {
-    background: #f0f0f0;
-    padding: 12px;
-    border-radius: 4px;
-    overflow-x: auto;
-  }
+    .window-content pre {
+        background: #f0f0f0;
+        padding: 12px;
+        border-radius: 4px;
+        overflow-x: auto;
+    }
 }
 
 /* CRT Screen Effects */
 @layer utilities {
-  @keyframes flicker {
-    0% { opacity: 1; }
-    2% { opacity: 0.98; }
-    4% { opacity: 1; }
-    8% { opacity: 0.97; }
-    10% { opacity: 1; }
-    100% { opacity: 1; }
-  }
+    @keyframes flicker {
+        0% {
+            opacity: 1;
+        }
+        2% {
+            opacity: 0.98;
+        }
+        4% {
+            opacity: 1;
+        }
+        8% {
+            opacity: 0.97;
+        }
+        10% {
+            opacity: 1;
+        }
+        100% {
+            opacity: 1;
+        }
+    }
 
-  @keyframes glow {
-    0%, 100% { text-shadow: 0 0 10px rgba(0, 255, 65, 0.5); }
-    50% { text-shadow: 0 0 15px rgba(0, 255, 65, 0.7); }
-  }
+    @keyframes glow {
+        0%,
+        100% {
+            text-shadow: 0 0 10px rgba(0, 255, 65, 0.5);
+        }
+        50% {
+            text-shadow: 0 0 15px rgba(0, 255, 65, 0.7);
+        }
+    }
 
-  @keyframes blink {
-    0%, 50% { opacity: 1; }
-    50.1%, 100% { opacity: 0; }
-  }
+    @keyframes blink {
+        0%,
+        50% {
+            opacity: 1;
+        }
+        50.1%,
+        100% {
+            opacity: 0;
+        }
+    }
 
-  .animate-flicker {
-    animation: flicker 4s infinite;
-  }
+    .animate-flicker {
+        animation: flicker 4s infinite;
+    }
 
-  .animate-glow {
-    animation: glow 2s ease-in-out infinite;
-  }
+    .animate-glow {
+        animation: glow 2s ease-in-out infinite;
+    }
 
-  .animate-blink {
-    animation: blink 1s step-end infinite;
-  }
+    .animate-blink {
+        animation: blink 1s step-end infinite;
+    }
 
-  .text-terminal {
-    color: var(--terminal-green);
-    text-shadow: 0 0 5px rgba(0, 255, 65, 0.5);
-  }
+    .text-terminal {
+        color: var(--terminal-green);
+        text-shadow: 0 0 5px rgba(0, 255, 65, 0.5);
+    }
 
-  .text-terminal-dim {
-    color: var(--terminal-green-dim);
-    text-shadow: 0 0 5px rgba(0, 255, 65, 0.5);
-  }
+    .text-terminal-dim {
+        color: var(--terminal-green-dim);
+        text-shadow: 0 0 5px rgba(0, 255, 65, 0.5);
+    }
 
-  .text-terminal-error {
-    color: var(--terminal-error);
-    text-shadow: 0 0 5px rgba(255, 0, 0, 0.5);
-  }
+    .text-terminal-error {
+        color: var(--terminal-error);
+        text-shadow: 0 0 5px rgba(255, 0, 0, 0.5);
+    }
 
-  .bg-terminal {
-    background-color: var(--terminal-bg);
-  }
+    .bg-terminal {
+        background-color: var(--terminal-bg);
+    }
 
-  .crt-scanline::before {
-    content: '';
-    position: fixed;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    background: linear-gradient(
-      transparent 50%,
-      rgba(0, 255, 65, 0.05) 50%
-    );
-    background-size: 100% 4px;
-    pointer-events: none;
-    z-index: 1000;
-  }
+    .crt-scanline::before {
+        content: '';
+        position: fixed;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        background: linear-gradient(
+            transparent 50%,
+            rgba(0, 255, 65, 0.05) 50%
+        );
+        background-size: 100% 4px;
+        pointer-events: none;
+        z-index: 1000;
+    }
 
-  .crt-vignette::after {
-    content: '';
-    position: fixed;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    background: radial-gradient(
-      ellipse at center,
-      transparent 0%,
-      rgba(0, 0, 0, 0.3) 100%
-    );
-    pointer-events: none;
-    z-index: 999;
-  }
+    .crt-vignette::after {
+        content: '';
+        position: fixed;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        background: radial-gradient(
+            ellipse at center,
+            transparent 0%,
+            rgba(0, 0, 0, 0.3) 100%
+        );
+        pointer-events: none;
+        z-index: 999;
+    }
 }
diff --git a/app/components/About.vue b/app/components/About.vue
index 239697b..5e558ab 100644
--- a/app/components/About.vue
+++ b/app/components/About.vue
@@ -1,23 +1,126 @@
+
+
 
 
 
+
+
diff --git a/app/components/AboutShrimpOS.vue b/app/components/AboutShrimpOS.vue
new file mode 100644
index 0000000..b325f27
--- /dev/null
+++ b/app/components/AboutShrimpOS.vue
@@ -0,0 +1,148 @@
+
+
+
+
+
diff --git a/app/components/Blog.vue b/app/components/Blog.vue
new file mode 100644
index 0000000..dcd7105
--- /dev/null
+++ b/app/components/Blog.vue
@@ -0,0 +1,220 @@
+
+
+
+
+
+
+
diff --git a/app/components/Contact.vue b/app/components/Contact.vue
index f74dab8..857424a 100644
--- a/app/components/Contact.vue
+++ b/app/components/Contact.vue
@@ -1,34 +1,95 @@
+
 
 
 
+
+
diff --git a/app/components/DesktopIcon.vue b/app/components/DesktopIcon.vue
index cb3cc02..31d056f 100644
--- a/app/components/DesktopIcon.vue
+++ b/app/components/DesktopIcon.vue
@@ -5,16 +5,22 @@
     @click="$emit('click')"
     @dblclick="$emit('dblclick')"
   >
-    
- {{ icon }} +
+
{{ label }}
+ + diff --git a/app/components/Projects.vue b/app/components/Projects.vue index f325644..e2ff267 100644 --- a/app/components/Projects.vue +++ b/app/components/Projects.vue @@ -1,57 +1,128 @@ + + + diff --git a/app/components/TaskbarButton.vue b/app/components/TaskbarButton.vue new file mode 100644 index 0000000..7403533 --- /dev/null +++ b/app/components/TaskbarButton.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/app/components/Window.vue b/app/components/Window.vue index d1d60e4..87423dd 100644 --- a/app/components/Window.vue +++ b/app/components/Window.vue @@ -1,3 +1,4 @@ + +``` + +That's it! With just a few lines of code, I was able to fetch and display my blog posts on my site. + +## The Window System + +One of the unique features of my portfolio site is the window system. I wanted to create a desktop-like experience for users, where they could open and close different windows to view my projects and blog posts. In classic windows 95 style! + +Currently, windows can be opened and closed, and moved around. Hopefully in the future I can add resizing and minimizing as well. + +Here's a code snippet of how I implemented the window system using Vue's component system: + +```vue + + + +``` + +Really simple boilerplate for draggable windows! Each window component manages its own position and dragging state. The parent component can handle opening, closing, and activating windows. + +## Conclusion + +Overall, I'm really happy with how my portfolio site turned out using Nuxt 3 and Nuxt Content. The development experience was smooth and enjoyable, and I was able to add a blog and a unique window system with ease. I'm excited to continue adding more content and features to my site in the future! + +Until next time! + +-Tanner (or sticks) diff --git a/nuxt.config.ts b/nuxt.config.ts index c4efaee..01c5498 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -14,7 +14,11 @@ export default defineNuxtConfig({ '@tresjs/nuxt', ], - css: ['./app/assets/css/main.css'], + nitro: { + preset: 'cloudflare_pages', + }, + + css: ['./app/assets/css/main.css', './app/assets/css/blog-prose.css'], vite: { plugins: [tailwindcss()], }, @@ -38,7 +42,7 @@ export default defineNuxtConfig({ }, { property: 'og:image', - content: 'https://img.sticks.ovh/stickspfpnew.png', + content: 'https://img.sticks.ovh/sticksnewpfp', }, { property: 'twitter:card', content: 'summary_large_image' }, { property: 'twitter:url', content: 'https://sticksdev.tech' }, @@ -49,14 +53,14 @@ export default defineNuxtConfig({ }, { property: 'twitter:image', - content: 'https://sticksdev.tech/images/meta-tags.png', + content: 'https://img.sticks.ovh/sticksnewpfp', }, ], link: [ { rel: 'icon', type: 'image/png', - href: 'https://img.sticks.ovh/stickspfpnew.png', + href: 'https://img.sticks.ovh/sticksnewpfp', }, ], }, diff --git a/package-lock.json b/package-lock.json index 9d16efd..4bf9665 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,11 +20,13 @@ "@vueuse/core": "^14.1.0", "better-sqlite3": "^12.6.2", "eslint": "^9.39.2", + "lucide-vue-next": "^0.563.0", "nuxt": "^4.3.0", "tailwindcss": "^4.1.18", "three": "^0.182.0", "vue": "^3.5.27", - "vue-router": "^4.6.4" + "vue-router": "^4.6.4", + "vue3-simple-icons": "^15.6.0" } }, "node_modules/@alloc/quick-lru": { @@ -13949,6 +13951,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-vue-next": { + "version": "0.563.0", + "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.563.0.tgz", + "integrity": "sha512-zsE/lCKtmaa7bGfhSpN84br1K9YoQ5pCN+2oKWjQQG3Lo6ufUUKBuHSjNFI6RvUevxaajNXb8XwFUKeTXG3sIA==", + "license": "ISC", + "peerDependencies": { + "vue": ">=3.0.1" + } + }, "node_modules/magic-regexp": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/magic-regexp/-/magic-regexp-0.10.0.tgz", @@ -20389,6 +20400,15 @@ "vue": "^3.5.0" } }, + "node_modules/vue3-simple-icons": { + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/vue3-simple-icons/-/vue3-simple-icons-15.6.0.tgz", + "integrity": "sha512-e7FkRt5yY6wDmeBzOL7PQw/n5PIiYh5Xbk9f0aAVJpl1E1ddMRLLRjiD9tG8P2UTtCc0Yh48XN3tn0TdZAuL0w==", + "license": "MIT", + "dependencies": { + "vue": "^3" + } + }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", diff --git a/package.json b/package.json index 6299c89..6c0113c 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,12 @@ "@vueuse/core": "^14.1.0", "better-sqlite3": "^12.6.2", "eslint": "^9.39.2", + "lucide-vue-next": "^0.563.0", "nuxt": "^4.3.0", "tailwindcss": "^4.1.18", "three": "^0.182.0", "vue": "^3.5.27", - "vue-router": "^4.6.4" + "vue-router": "^4.6.4", + "vue3-simple-icons": "^15.6.0" } }