diff --git a/.gitignore b/.gitignore index 3d75e90..41efa23 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,9 @@ .DS_Store *.pem +# lint cache +.cache + # debug npm-debug.log* yarn-debug.log* diff --git a/eslint.config.mjs b/eslint.config.mjs index bb572f5..27bdd40 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -17,20 +17,19 @@ const compat = new FlatCompat({ export default [ stylistic.configs.recommended, + nextPlugin.configs.recommended, ...fixupConfigRules(compat.extends( 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-type-checked', 'plugin:import/warnings', - 'plugin:import/typescript', - 'plugin:@next/next/recommended' + 'plugin:import/typescript' )), { plugins: { '@typescript-eslint': fixupPluginRules(typescriptEslint), 'perfectionist': perfectionist, 'import': fixupPluginRules(_import), - 'yenz': fixupPluginRules(yenz), - '@next/next': fixupPluginRules(nextPlugin) + 'yenz': fixupPluginRules(yenz) }, languageOptions: { @@ -48,6 +47,8 @@ export default [ 'perfectionist/sort-switch-case': 'off', '@stylistic/comma-dangle': 'off', '@stylistic/indent': 'off', + // Its autofix drops whitespace between inline text and elements. + '@stylistic/jsx-one-expression-per-line': 'off', '@stylistic/no-multiple-empty-lines': 'off', '@stylistic/no-trailing-spaces': ['error'], '@typescript-eslint/explicit-function-return-type': ['error', { @@ -141,6 +142,12 @@ export default [ } }, { - ignores: ['src/components/ui/**'], + ignores: [ + '.next/**', + 'out/**', + 'build/**', + 'next-env.d.ts', + 'src/components/ui/**' + ], } ] diff --git a/next.config.ts b/next.config.ts index e9ffa30..05a61f0 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,8 @@ -import type { NextConfig } from "next"; +import type { NextConfig } from 'next' + const nextConfig: NextConfig = { /* config options here */ -}; +} -export default nextConfig; +export default nextConfig diff --git a/package.json b/package.json index e2e40c7..624241f 100644 --- a/package.json +++ b/package.json @@ -1,44 +1,44 @@ { "name": "void-works", - "version": "0.6.0", + "version": "0.7.0", "private": true, "scripts": { "dev": "next dev --turbopack", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "eslint . --cache --cache-location .cache/eslint" }, "dependencies": { "@radix-ui/react-slot": "^1.2.3", - "@vercel/analytics": "1.5.0", + "@vercel/analytics": "2.0.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "lucide-react": "^0.511.0", + "lucide-react": "1.46.0", "next": "^16.2.9", "next-themes": "^0.4.6", - "react": "19.0.0", - "react-dom": "19.0.0", + "react": "19.3.0", + "react-dom": "19.3.0", "tailwind-merge": "^3.6.0" }, "devDependencies": { - "@eslint/compat": "1.2.9", - "@eslint/eslintrc": "3.3.5", + "@eslint/compat": "2.1.1", + "@eslint/eslintrc": "3.3.7", "@eslint/js": "10.0.1", "@next/eslint-plugin-next": "^16.1.2", - "@stylistic/eslint-plugin": "4.4.0", + "@stylistic/eslint-plugin": "5.10.0", "@tailwindcss/postcss": "^4", "@types/jest": "^30.0.0", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "@typescript-eslint/eslint-plugin": "8.33.0", - "@typescript-eslint/parser": "8.33.0", - "eslint": "^9", + "@types/node": "22.20.3", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@typescript-eslint/eslint-plugin": "8.70.0", + "@typescript-eslint/parser": "8.70.0", + "eslint": "10.10.0", "eslint-config-next": "^16.1.2", - "eslint-plugin-import": "2.31.0", - "eslint-plugin-jest": "29.15.2", - "eslint-plugin-perfectionist": "4.13.0", - "eslint-plugin-yenz": "1.2.0", + "eslint-plugin-import": "2.32.0", + "eslint-plugin-jest": "29.16.6", + "eslint-plugin-perfectionist": "5.11.1", + "eslint-plugin-yenz": "2.2.2", "tailwindcss": "^4", "tw-animate-css": "^1.3.2", "typescript": "^5" diff --git a/postcss.config.mjs b/postcss.config.mjs index c7bcb4b..86e8e3c 100644 --- a/postcss.config.mjs +++ b/postcss.config.mjs @@ -1,5 +1,5 @@ const config = { - plugins: ["@tailwindcss/postcss"], -}; + plugins: ['@tailwindcss/postcss'], +} -export default config; +export default config diff --git a/src/app/.well-known/[...path]/route.ts b/src/app/.well-known/[...path]/route.ts index 970657c..2ea46d6 100644 --- a/src/app/.well-known/[...path]/route.ts +++ b/src/app/.well-known/[...path]/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' + export async function GET( request: NextRequest, { params }: { params: Promise<{ path: string[] }> }, diff --git a/src/app/globals.css b/src/app/globals.css index e1ca93a..c80145b 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -6,8 +6,14 @@ @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); + --font-sans: var(--font-instrument-sans), system-ui, sans-serif; + --font-mono: var(--font-ibm-plex-mono), ui-monospace, monospace; + --color-brand: var(--brand); + --color-brand-solid: var(--brand-solid); + --color-brand-solid-hover: var(--brand-solid-hover); + --color-brand-soft: var(--brand-soft); + --color-brand-softer: var(--brand-softer); + --color-body-muted: var(--body-muted); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); @@ -45,10 +51,10 @@ :root { --radius: 0.625rem; - --background: oklch(0 0 0); - --foreground: oklch(1 0 0); - --card: oklch(0.15 0 0); - --card-foreground: oklch(1 0 0); + --background: #08080a; + --foreground: #f4f4f6; + --card: #101013; + --card-foreground: #f4f4f6; --popover: oklch(0.15 0 0); --popover-foreground: oklch(1 0 0); --primary: oklch(0.929 0.013 255.508); @@ -56,13 +62,20 @@ --secondary: oklch(0.279 0.041 260.031); --secondary-foreground: oklch(1 0 0); --muted: oklch(0.279 0.041 260.031); - --muted-foreground: oklch(0.704 0.04 256.788); + --muted-foreground: #9a9aa2; --accent: oklch(0.279 0.041 260.031); --accent-foreground: oklch(1 0 0); --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); + --border: rgb(255 255 255 / 8%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.551 0.027 264.364); + --brand: #5b6cf0; + /* Deeper indigo for white-text surfaces: meets 4.5:1 contrast (base ~5.3:1, hover ~4.7:1) */ + --brand-solid: #4c5ce6; + --brand-solid-hover: #5566eb; + --brand-soft: #8e9bff; + --brand-softer: #b4bdff; + --body-muted: #a8a8b0; --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); @@ -119,4 +132,13 @@ body { @apply bg-background text-foreground; } + ::selection { + background: var(--brand-solid); + color: #ffffff; + } + @media (prefers-reduced-motion: no-preference) { + html { + scroll-behavior: smooth; + } + } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9dbdd69..21f5a5b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,20 +1,21 @@ import { Analytics } from '@vercel/analytics/next' import type { Metadata } from 'next' -import { Geist, Geist_Mono } from 'next/font/google' +import { IBM_Plex_Mono, Instrument_Sans } from 'next/font/google' import { Widget as ProductlaneWidget } from '@/components/productlane/widget' import './globals.css' -const geistSans = Geist({ - variable: '--font-geist-sans', +const instrumentSans = Instrument_Sans({ + variable: '--font-instrument-sans', subsets: ['latin'], }) -const geistMono = Geist_Mono({ - variable: '--font-geist-mono', +const ibmPlexMono = IBM_Plex_Mono({ + variable: '--font-ibm-plex-mono', subsets: ['latin'], + weight: ['400', '500'], }) export const metadata: Metadata = { @@ -22,15 +23,11 @@ export const metadata: Metadata = { description: 'Developer tools for the modern era', } -function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode -}>): React.ReactNode { +function RootLayout({ children }: Readonly<{ children: React.ReactNode }>): React.ReactNode { return ( {children} diff --git a/src/app/page.tsx b/src/app/page.tsx index b051c7c..58bd319 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,17 +1,18 @@ import type { Metadata } from 'next' -import Image from 'next/image' -import { AnimatedProductSection } from '@/components/animated-product-section' -import { FixedFooterLinks } from '@/components/fixed-footer-links' import { Footer } from '@/components/footer' -import { ScrollIndicator } from '@/components/scroll-indicator' -import { Button } from '@/components/ui/button' +import { Header } from '@/components/header' +import { Hero } from '@/components/hero' +import { ProjectsSection } from '@/components/projects-section' const APP_NAME = 'Void Works' const APP_DESCRIPTION = 'Creating innovative solutions across platforms and technologies' const DEFAULT_URL = 'https://voidworks.io' +// Regenerate daily so the footer's copyright year stays current. +export const revalidate = 86400 + export const metadata: Metadata = { title: APP_NAME, description: APP_DESCRIPTION, @@ -103,20 +104,6 @@ export default function Home(): React.ReactNode { } const productSchemas = [ - { - '@context': 'https://schema.org', - '@type': 'SoftwareApplication', - 'name': 'Shortcut Assistant', - 'applicationCategory': 'BrowserApplication', - 'operatingSystem': 'Chrome', - 'description': 'Enhance your browsing experience with powerful keyboard shortcuts and productivity tools.', - 'url': 'https://chromewebstore.google.com/detail/shortcut-assistant/kmdlofehocppnlkpokdbiaalcelhedef', - 'offers': { - '@type': 'Offer', - 'price': '0', - 'priceCurrency': 'USD', - }, - }, { '@context': 'https://schema.org', '@type': 'MobileApplication', @@ -142,7 +129,7 @@ export default function Home(): React.ReactNode { ] return ( -
+
{/* Structured Data */}