diff --git a/app/not-found.tsx b/app/not-found.tsx new file mode 100644 index 0000000..f73cab3 --- /dev/null +++ b/app/not-found.tsx @@ -0,0 +1,25 @@ +import Link from 'next/link'; + +export default function NotFound() { + return ( +
+
+

+ 404 +

+

+ 找不到頁面 +

+

+ 您造訪的連結可能已失效或不存在。 +

+ + 返回首頁 + +
+
+ ); +} diff --git a/app/pages/[slug]/page.tsx b/app/pages/[slug]/page.tsx index 9b2e7ec..c3eb9df 100644 --- a/app/pages/[slug]/page.tsx +++ b/app/pages/[slug]/page.tsx @@ -11,6 +11,7 @@ import { ScrollReveal } from '@/components/scroll-reveal'; import { SectionDivider } from '@/components/section-divider'; import { JsonLd } from '@/components/json-ld'; import { DevEnvDeviceHero } from '@/components/dev-env-device-hero'; +import { HomeLabDeviceHero } from '@/components/homelab-device-hero'; export function generateStaticParams() { const params = allPages.map((page) => ({ @@ -76,11 +77,11 @@ export default async function StaticPage({ params }: Props) { <> - -
+ +
-
+
{page.published_at && (

{new Date(page.published_at).toLocaleDateString( @@ -118,6 +119,8 @@ export default async function StaticPage({ params }: Props) { > {slug === 'dev-env' ? ( + ) : slug === 'homelab' ? ( + ) : ( page.feature_image && (

diff --git a/components/homelab-device-hero.tsx b/components/homelab-device-hero.tsx new file mode 100644 index 0000000..ad612ba --- /dev/null +++ b/components/homelab-device-hero.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { SiTruenas, SiProxmox } from 'react-icons/si'; +import { FiServer } from 'react-icons/fi'; + +/** + * HomeLab 設備展示:Proxmox VE + VyOS、Switch、NAS (TrueNAS) + * 使用純 CSS 藝術,取代 HomeLab 頁的 feature_image + */ +export function HomeLabDeviceHero() { + return ( +
+
+
+ {/* Proxmox VE + VyOS Host */} +
+
+
+ + + +
+
+ + +
+
+
+ + {/* 網路線 - 連接 Proxmox 與 Switch */} +
+ +
+ + {/* Switch */} +
+
+
+ {[1, 2].map((row) => ( +
+ {Array.from({ length: 8 }).map((_, i) => ( +
+ +
+ ))} +
+ ))} +
+
+
+ + {/* 網路線 - 連接 Switch 與 NAS */} +
+ +
+ + {/* NAS - TrueNAS */} +
+
+
+ {[1, 2, 3, 4].map((i) => ( +
+ ))} +
+
+ +
+
+
+
+
+
+ ); +} diff --git a/styles/globals.css b/styles/globals.css index e34ee1d..eb6785e 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -770,6 +770,504 @@ color: rgba(255, 255, 255, 0.4); } +/* HomeLab device hero - Switch, NAS (TrueNAS), Router (VyOS) */ +@media (prefers-reduced-motion: reduce) { + .homelab-device-hero .homelab-device-scene { + animation: none; + } +} + +.homelab-device-hero { + min-height: 280px; +} + +@media (min-width: 1024px) { + .homelab-device-hero { + min-height: 340px; + } +} + +@media (min-width: 1280px) { + .homelab-device-hero { + min-height: 380px; + } +} + +@media (min-width: 1536px) { + .homelab-device-hero { + min-height: 420px; + } +} + +.homelab-device-scene { + display: flex; + align-items: center; + justify-content: center; + perspective: 800px; + transform-style: preserve-3d; + animation: device-float 8s ease-in-out infinite; +} + +.homelab-rack { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: nowrap; + gap: 12px; + width: 100%; + min-width: 320px; + max-width: 420px; + margin: 0 auto; + padding: 16px 20px; + background: linear-gradient(180deg, #b8b8b8 0%, #9a9a9a 50%, #888888 100%); + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); +} + +@media (min-width: 640px) { + .homelab-rack { + max-width: 520px; + gap: 16px; + padding: 20px 24px; + } +} + +@media (min-width: 1024px) { + .homelab-rack { + max-width: 800px; + padding: 24px 32px; + gap: 24px; + } +} + +@media (min-width: 1280px) { + .homelab-rack { + max-width: 900px; + padding: 28px 40px; + gap: 28px; + } +} + +@media (min-width: 1536px) { + .homelab-rack { + max-width: 1000px; + padding: 32px 48px; + gap: 32px; + } +} + +.dark .homelab-rack { + background: linear-gradient(180deg, #4a4a4a 0%, #3d3d3d 50%, #2a2a2a 100%); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); +} + +/* Cable / 線路 - 虛線連接,垂直置中對齊設備 */ +.homelab-cable { + display: flex; + align-items: center; + align-self: center; + flex-shrink: 0; + min-width: 20px; + width: 20px; +} + +.homelab-cable-line { + display: block; + width: 100%; + height: 0; + border-top: 2px dashed #64748b; + flex-shrink: 0; +} + +@media (min-width: 640px) { + .homelab-cable { + min-width: 24px; + width: 24px; + } + .homelab-cable-line { + border-top-width: 2px; + } +} + +@media (min-width: 1024px) { + .homelab-cable { + min-width: 48px; + width: 48px; + } + .homelab-cable-line { + border-top-width: 2px; + } +} + +.dark .homelab-cable-line { + border-top-color: #94a3b8; +} + +/* Router */ +.homelab-router { + flex-shrink: 0; +} + +.homelab-router-body { + width: 64px; + height: 40px; + padding: 4px 6px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; + background: linear-gradient(145deg, #3d3d3d 0%, #2a2a2a 100%); + border-radius: 6px; + border: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: + 0 4px 12px rgba(0, 0, 0, 0.25), + inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +@media (min-width: 640px) { + .homelab-router-body { + width: 80px; + height: 48px; + padding: 6px 8px; + gap: 4px; + } +} + +@media (min-width: 1024px) { + .homelab-router-body { + width: 104px; + height: 64px; + padding: 8px 10px; + gap: 6px; + border-radius: 8px; + } +} + +@media (min-width: 1280px) { + .homelab-router-body { + width: 116px; + height: 72px; + padding: 10px 12px; + gap: 8px; + } +} + +.dark .homelab-router-body { + background: linear-gradient(145deg, #2a2a2a 0%, #1a1a1a 100%); + border-color: rgba(255, 255, 255, 0.1); +} + +.homelab-router-leds { + display: flex; + gap: 4px; +} + +.homelab-led { + width: 4px; + height: 4px; + border-radius: 50%; +} + +@media (min-width: 1024px) { + .homelab-led { + width: 5px; + height: 5px; + } +} + +.homelab-led-power { + background: #22c55e; + box-shadow: 0 0 4px #22c55e; +} + +.homelab-led-wan { + background: #3b82f6; + box-shadow: 0 0 4px #3b82f6; +} + +.homelab-led-lan { + background: #eab308; + box-shadow: 0 0 4px #eab308; +} + +.homelab-router-logos { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + flex: 1; +} + +@media (min-width: 1024px) { + .homelab-router-logos { + gap: 8px; + } +} + +.homelab-proxmox-logo { + color: #e57008; + width: 16px; + height: 16px; +} + +@media (min-width: 640px) { + .homelab-proxmox-logo { + width: 20px; + height: 20px; + } +} + +@media (min-width: 1024px) { + .homelab-proxmox-logo { + width: 24px; + height: 24px; + } +} + +@media (min-width: 1280px) { + .homelab-proxmox-logo { + width: 28px; + height: 28px; + } +} + +.dark .homelab-proxmox-logo { + color: #f59e0b; +} + +.homelab-router-logos .homelab-logo-svg { + flex-shrink: 0; +} + +.homelab-router-icon { + width: 16px; + height: 16px; + color: #64748b; +} + +@media (min-width: 640px) { + .homelab-router-icon { + width: 20px; + height: 20px; + } +} + +@media (min-width: 1024px) { + .homelab-router-icon { + width: 24px; + height: 24px; + } +} + +@media (min-width: 1280px) { + .homelab-router-icon { + width: 28px; + height: 28px; + } +} + +.dark .homelab-router-icon { + color: #94a3b8; +} + +/* Switch */ +.homelab-switch { + flex-shrink: 0; +} + +.homelab-switch-body { + width: 120px; + height: 48px; + padding: 6px 10px; + background: linear-gradient(145deg, #3d3d3d 0%, #2a2a2a 100%); + border-radius: 6px; + border: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: + 0 4px 12px rgba(0, 0, 0, 0.25), + inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +@media (min-width: 640px) { + .homelab-switch-body { + width: 160px; + height: 60px; + padding: 8px 12px; + } +} + +@media (min-width: 1024px) { + .homelab-switch-body { + width: 220px; + height: 76px; + padding: 10px 14px; + border-radius: 8px; + } +} + +@media (min-width: 1280px) { + .homelab-switch-body { + width: 260px; + height: 86px; + padding: 12px 16px; + } +} + +.dark .homelab-switch-body { + background: linear-gradient(145deg, #2a2a2a 0%, #1a1a1a 100%); + border-color: rgba(255, 255, 255, 0.1); +} + +.homelab-switch-ports { + display: flex; + flex-direction: column; + gap: 4px; + height: 100%; + justify-content: center; +} + +.homelab-port-row { + display: flex; + gap: 4px; + justify-content: center; + flex-wrap: wrap; +} + +.homelab-port { + display: flex; + flex-direction: column; + align-items: center; + gap: 1px; +} + +.homelab-port-led { + width: 3px; + height: 3px; + border-radius: 50%; + background: #444; +} + +@media (min-width: 640px) { + .homelab-port-led { + width: 4px; + height: 4px; + } +} + +@media (min-width: 1024px) { + .homelab-port-led { + width: 5px; + height: 5px; + } +} + +.homelab-port-led-active { + background: #22c55e; + box-shadow: 0 0 3px #22c55e; +} + +.dark .homelab-port-led { + background: #555; +} + +.dark .homelab-port-led-active { + background: #4ade80; + box-shadow: 0 0 3px #4ade80; +} + +/* NAS */ +.homelab-nas { + flex-shrink: 0; +} + +.homelab-nas-body { + width: 60px; + height: 48px; + padding: 4px 6px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; + background: linear-gradient(145deg, #3d3d3d 0%, #2a2a2a 100%); + border-radius: 6px; + border: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: + 0 4px 12px rgba(0, 0, 0, 0.25), + inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +@media (min-width: 640px) { + .homelab-nas-body { + width: 88px; + height: 64px; + padding: 6px 8px; + gap: 4px; + } +} + +@media (min-width: 1024px) { + .homelab-nas-body { + width: 108px; + height: 76px; + padding: 8px 10px; + gap: 6px; + border-radius: 8px; + } +} + +@media (min-width: 1280px) { + .homelab-nas-body { + width: 120px; + height: 86px; + padding: 10px 12px; + gap: 8px; + } +} + +.dark .homelab-nas-body { + background: linear-gradient(145deg, #2a2a2a 0%, #1a1a1a 100%); + border-color: rgba(255, 255, 255, 0.1); +} + +.homelab-nas-drives { + display: flex; + gap: 3px; +} + +.homelab-drive-slot { + width: 12px; + height: 4px; + background: rgba(255, 255, 255, 0.12); + border-radius: 1px; +} + +@media (min-width: 1024px) { + .homelab-drive-slot { + width: 14px; + height: 5px; + } +} + +.dark .homelab-drive-slot { + background: rgba(255, 255, 255, 0.08); +} + +.homelab-nas-logo { + display: flex; + align-items: center; + justify-content: center; + flex: 1; +} + +.homelab-truenas-logo { + color: #0095d5; +} + +.dark .homelab-truenas-logo { + color: #38bdf8; +} + @keyframes mastodon-shimmer { 0% { background-position: -200% 0; }