mirror of
https://github.com/LukeHagar/Sveltey.git
synced 2025-12-06 04:21:38 +00:00
Theming, A11y, added resend, formatting
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL"
|
PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL"
|
||||||
PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_ANON_KEY"
|
PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_ANON_KEY"
|
||||||
|
RESEND_API_KEY="YOUR_RESEND_KEY"
|
||||||
|
|||||||
253
package-lock.json
generated
253
package-lock.json
generated
@@ -38,6 +38,7 @@
|
|||||||
"remark-abbr": "^1.4.2",
|
"remark-abbr": "^1.4.2",
|
||||||
"remark-toc": "^9.0.0",
|
"remark-toc": "^9.0.0",
|
||||||
"remark-unwrap-images": "^4.0.1",
|
"remark-unwrap-images": "^4.0.1",
|
||||||
|
"resend": "^4.5.1",
|
||||||
"shiki": "^3.4.2",
|
"shiki": "^3.4.2",
|
||||||
"svelte": "^5.25.0",
|
"svelte": "^5.25.0",
|
||||||
"svelte-check": "^4.0.0",
|
"svelte-check": "^4.0.0",
|
||||||
@@ -876,6 +877,25 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-email/render": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-zNueW5Wn/4jNC1c5LFgXzbUdv5Lhms+FWjOvWAhal7gx5YVf0q6dPJ0dnR70+ifo59gcMLwCZEaTS9EEuUhKvQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"html-to-text": "9.0.5",
|
||||||
|
"prettier": "3.5.3",
|
||||||
|
"react-promise-suspense": "0.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/plugin-inject": {
|
"node_modules/@rollup/plugin-inject": {
|
||||||
"version": "5.0.5",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz",
|
||||||
@@ -1202,6 +1222,20 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@selderee/plugin-htmlparser2": {
|
||||||
|
"version": "0.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz",
|
||||||
|
"integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"selderee": "^0.11.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/killymxi"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@shikijs/core": {
|
"node_modules/@shikijs/core": {
|
||||||
"version": "3.4.2",
|
"version": "3.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.4.2.tgz",
|
||||||
@@ -3548,6 +3582,21 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"entities": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/domain-browser": {
|
"node_modules/domain-browser": {
|
||||||
"version": "4.22.0",
|
"version": "4.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz",
|
||||||
@@ -3561,6 +3610,50 @@
|
|||||||
"url": "https://bevry.me/fund"
|
"url": "https://bevry.me/fund"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@@ -3620,6 +3713,19 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
@@ -4436,6 +4542,23 @@
|
|||||||
"minimalistic-crypto-utils": "^1.0.1"
|
"minimalistic-crypto-utils": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html-to-text": {
|
||||||
|
"version": "9.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz",
|
||||||
|
"integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@selderee/plugin-htmlparser2": "^0.11.0",
|
||||||
|
"deepmerge": "^4.3.1",
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"htmlparser2": "^8.0.2",
|
||||||
|
"selderee": "^0.11.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-void-elements": {
|
"node_modules/html-void-elements": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
|
||||||
@@ -4447,6 +4570,26 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/htmlparser2": {
|
||||||
|
"version": "8.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||||
|
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.0.1",
|
||||||
|
"entities": "^4.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/https-browserify": {
|
"node_modules/https-browserify": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
|
||||||
@@ -4774,6 +4917,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/leac": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/killymxi"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/levn": {
|
"node_modules/levn": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||||
@@ -5792,6 +5945,20 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parseley": {
|
||||||
|
"version": "0.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz",
|
||||||
|
"integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"leac": "^0.6.0",
|
||||||
|
"peberminta": "^0.9.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/killymxi"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-browserify": {
|
"node_modules/path-browserify": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||||
@@ -5843,6 +6010,16 @@
|
|||||||
"node": ">=0.12"
|
"node": ">=0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/peberminta": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/killymxi"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -6304,6 +6481,48 @@
|
|||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react": {
|
||||||
|
"version": "19.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||||
|
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dom": {
|
||||||
|
"version": "19.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
|
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"scheduler": "^0.26.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^19.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-promise-suspense": {
|
||||||
|
"version": "0.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz",
|
||||||
|
"integrity": "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-promise-suspense/node_modules/fast-deep-equal": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/readable-stream": {
|
"node_modules/readable-stream": {
|
||||||
"version": "3.6.2",
|
"version": "3.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
@@ -6495,6 +6714,19 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/resend": {
|
||||||
|
"version": "4.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/resend/-/resend-4.5.1.tgz",
|
||||||
|
"integrity": "sha512-ryhHpZqCBmuVyzM19IO8Egtc2hkWI4JOL5lf5F3P7Dydu3rFeX6lHNpGqG0tjWoZ63rw0l731JEmuJZBdDm3og==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-email/render": "1.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
@@ -6664,6 +6896,14 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/scheduler": {
|
||||||
|
"version": "0.26.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||||
|
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/schema-dts": {
|
"node_modules/schema-dts": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz",
|
||||||
@@ -6671,6 +6911,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/selderee": {
|
||||||
|
"version": "0.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz",
|
||||||
|
"integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"parseley": "^0.12.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/killymxi"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.2",
|
"version": "7.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"remark-abbr": "^1.4.2",
|
"remark-abbr": "^1.4.2",
|
||||||
"remark-toc": "^9.0.0",
|
"remark-toc": "^9.0.0",
|
||||||
"remark-unwrap-images": "^4.0.1",
|
"remark-unwrap-images": "^4.0.1",
|
||||||
|
"resend": "^4.5.1",
|
||||||
"shiki": "^3.4.2",
|
"shiki": "^3.4.2",
|
||||||
"svelte": "^5.25.0",
|
"svelte": "^5.25.0",
|
||||||
"svelte-check": "^4.0.0",
|
"svelte-check": "^4.0.0",
|
||||||
|
|||||||
@@ -36,4 +36,9 @@
|
|||||||
|
|
||||||
div.prose a {
|
div.prose a {
|
||||||
@apply anchor
|
@apply anchor
|
||||||
|
}
|
||||||
|
|
||||||
|
a.disabled {
|
||||||
|
@apply pointer-events-none opacity-50;
|
||||||
|
|
||||||
}
|
}
|
||||||
104
src/lib/components/Header.svelte
Normal file
104
src/lib/components/Header.svelte
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import SvelteyLogoLetter from '$lib/assets/Sveltey-logo-letter.svelte';
|
||||||
|
import ThemeSwitch from '$lib/components/ThemeSwitch.svelte';
|
||||||
|
import { BookOpen, DollarSign, Home, LayoutDashboard, LogOut, User } from '@lucide/svelte';
|
||||||
|
import { Avatar } from '@skeletonlabs/skeleton-svelte';
|
||||||
|
|
||||||
|
let { data } = $props();
|
||||||
|
|
||||||
|
let { session } = $derived(data);
|
||||||
|
|
||||||
|
// Helper function to check if a path is active
|
||||||
|
function isActivePath(path: string): boolean {
|
||||||
|
return page.url.pathname === path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get navigation link classes
|
||||||
|
function getNavClasses(path: string): string {
|
||||||
|
return `btn btn-sm flex items-center gap-2 ${page.url.pathname === path ? 'cursor-default disabled' : ''}`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<header
|
||||||
|
class="bg-surface-50-950-token border-surface-200-700-token sticky top-0 z-50 border-b backdrop-blur-2xl"
|
||||||
|
>
|
||||||
|
<nav class="container mx-auto px-6 py-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<!-- Left side - Brand and Main navigation -->
|
||||||
|
<div class="flex items-center gap-8">
|
||||||
|
<!-- Brand -->
|
||||||
|
<a href="/" class="flex items-center gap-2" aria-label="Sveltey - Go to homepage">
|
||||||
|
<SvelteyLogoLetter size="size-12" />
|
||||||
|
<span class="hidden text-xl font-bold sm:block">Sveltey</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Main Navigation -->
|
||||||
|
<div class="hidden items-center gap-2 md:flex">
|
||||||
|
<a href="/" class={getNavClasses('/')} aria-label="Go to homepage">
|
||||||
|
<Home class="size-4" aria-hidden="true" />
|
||||||
|
<span>Home</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/pricing" class={getNavClasses('/pricing')} aria-label="View pricing plans">
|
||||||
|
<DollarSign class="size-4" aria-hidden="true" />
|
||||||
|
<span>Pricing</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/blog" class={getNavClasses('/blog')} aria-label="Read our blog">
|
||||||
|
<BookOpen class="size-4" aria-hidden="true" />
|
||||||
|
<span>Blog</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{#if session}
|
||||||
|
<a href="/app/dashboard" class={getNavClasses('/app/dashboard')} aria-label="Go to dashboard">
|
||||||
|
<LayoutDashboard class="size-4" aria-hidden="true" />
|
||||||
|
<span>Dashboard</span>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right side - User actions and theme switcher -->
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<ThemeSwitch />
|
||||||
|
|
||||||
|
{#if session}
|
||||||
|
<!-- User Profile Section -->
|
||||||
|
<div class="border-surface-300-600 flex items-center gap-3 border-l pl-3">
|
||||||
|
<Avatar
|
||||||
|
size="size-8"
|
||||||
|
src={session.user.user_metadata.avatar_url}
|
||||||
|
name={session.user.user_metadata.full_name || session.user.email}
|
||||||
|
/>
|
||||||
|
<div class="hidden md:block">
|
||||||
|
<p class="text-sm font-medium">
|
||||||
|
{session.user.user_metadata.full_name || session.user.email?.split('@')[0]}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs opacity-75">{session.user.email}</p>
|
||||||
|
</div>
|
||||||
|
<form action="/auth/logout" method="POST" style="display: inline;">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn flex items-center gap-2"
|
||||||
|
title="Sign Out"
|
||||||
|
aria-label="Sign out of your account"
|
||||||
|
>
|
||||||
|
<LogOut class="size-4" aria-hidden="true" />
|
||||||
|
<span class="hidden sm:inline">Sign Out</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<!-- Authentication Buttons -->
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<a href="/auth" class={getNavClasses('/auth')} aria-label="Sign in or register">
|
||||||
|
<User class="size-4" aria-hidden="true" />
|
||||||
|
<span>Sign In / Register</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { Palette } from '@lucide/svelte';
|
import { Check, ChevronDown, Moon, Palette, Sun } from '@lucide/svelte';
|
||||||
|
|
||||||
// Available color themes
|
// Available color themes
|
||||||
const colorThemes = [
|
const colorThemes = [
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
{ value: 'wintry', label: 'Wintry' }
|
{ value: 'wintry', label: 'Wintry' }
|
||||||
];
|
];
|
||||||
|
|
||||||
let currentColorTheme = $state('skeleton');
|
let currentColorTheme = $state('legacy');
|
||||||
let isDarkMode = $state(true);
|
let isDarkMode = $state(true);
|
||||||
let mounted = $state(false);
|
let mounted = $state(false);
|
||||||
let showDropdown = $state(false);
|
let showDropdown = $state(false);
|
||||||
@@ -98,95 +98,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close dropdown when clicking outside
|
|
||||||
function handleClickOutside(event: MouseEvent) {
|
|
||||||
const target = event.target as HTMLElement;
|
|
||||||
if (!target.closest('.theme-dropdown')) {
|
|
||||||
showDropdown = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (browser) {
|
|
||||||
document.addEventListener('click', handleClickOutside);
|
|
||||||
return () => document.removeEventListener('click', handleClickOutside);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center space-x-3">
|
<!-- Light/Dark Mode Toggle -->
|
||||||
<!-- Light/Dark Mode Toggle -->
|
<button
|
||||||
|
onclick={toggleDarkMode}
|
||||||
|
class="btn btn-sm items-center"
|
||||||
|
title="Toggle light/dark mode"
|
||||||
|
aria-label="Toggle light/dark mode"
|
||||||
|
>
|
||||||
|
{#if !isDarkMode}
|
||||||
|
<Sun class="size-4" aria-hidden="true" />
|
||||||
|
{:else}
|
||||||
|
<Moon class="size-4" aria-hidden="true" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Color Theme Selector -->
|
||||||
|
<div class="relative">
|
||||||
<button
|
<button
|
||||||
onclick={toggleDarkMode}
|
onclick={() => (showDropdown = !showDropdown)}
|
||||||
class="btn btn-sm preset-outlined-surface-500 flex items-center h-8"
|
class="btn btn-sm flex h-8 items-center"
|
||||||
title="Toggle light/dark mode"
|
title="Select color theme"
|
||||||
aria-label="Toggle light/dark mode"
|
aria-label="Select color theme"
|
||||||
|
aria-expanded={showDropdown}
|
||||||
>
|
>
|
||||||
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<Palette class="size-4" aria-hidden="true" />
|
||||||
{#if !isDarkMode}
|
<span class="capitalize">{currentColorTheme}</span>
|
||||||
<!-- Sun icon for dark mode (click to go light) -->
|
<ChevronDown class={`size-4 transition-transform ${showDropdown === true ? 'rotate-180' : ''}`} aria-hidden="true" />
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<!-- Moon icon for light mode (click to go dark) -->
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Color Theme Selector -->
|
{#if showDropdown}
|
||||||
<div class="theme-dropdown relative">
|
<div
|
||||||
<button
|
class="card preset-outlined-primary-500 bg-surface-50-950 absolute left-0 z-50 mt-2 w-48 overflow-hidden rounded-lg shadow-lg"
|
||||||
onclick={() => (showDropdown = !showDropdown)}
|
|
||||||
class="btn btn-sm preset-outlined-surface-500 h-8 flex items-center"
|
|
||||||
title="Select color theme"
|
|
||||||
aria-label="Select color theme"
|
|
||||||
aria-expanded={showDropdown}
|
|
||||||
>
|
>
|
||||||
<Palette class="size-4" />
|
{#each colorThemes as theme}
|
||||||
<span class="text-sm capitalize">{currentColorTheme}</span>
|
<button
|
||||||
<svg
|
onclick={() => selectColorTheme(theme.value)}
|
||||||
class="ml-1 h-3 w-3 transition-transform {showDropdown ? 'rotate-180' : ''}"
|
class="btn btn-sm flex w-full items-center justify-between px-4 py-2 transition-colors"
|
||||||
fill="none"
|
aria-label="Select {theme.label} theme"
|
||||||
stroke="currentColor"
|
>
|
||||||
viewBox="0 0 24 24"
|
<span>{theme.label}</span>
|
||||||
>
|
{#if currentColorTheme === theme.value}
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
<Check class="size-4" aria-hidden="true" />
|
||||||
</svg>
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
{/each}
|
||||||
{#if showDropdown}
|
</div>
|
||||||
<div
|
{/if}
|
||||||
class="card preset-outlined-primary-500 bg-surface-50-950 absolute left-0 z-50 mt-2 w-48 overflow-hidden rounded-lg shadow-lg"
|
|
||||||
>
|
|
||||||
{#each colorThemes as theme}
|
|
||||||
<button
|
|
||||||
onclick={() => selectColorTheme(theme.value)}
|
|
||||||
class="hover:bg-surface-950-50 hover:text-surface-50-950 flex w-full items-center justify-between px-4 py-2 text-left text-sm transition-colors"
|
|
||||||
>
|
|
||||||
<span>{theme.label}</span>
|
|
||||||
{#if currentColorTheme === theme.value}
|
|
||||||
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -99,8 +99,10 @@
|
|||||||
{#each dashboardStats as stat}
|
{#each dashboardStats as stat}
|
||||||
<div
|
<div
|
||||||
class="card preset-outlined-surface-200-800 space-y-4 p-6 text-center transition-all duration-300 hover:scale-105"
|
class="card preset-outlined-surface-200-800 space-y-4 p-6 text-center transition-all duration-300 hover:scale-105"
|
||||||
|
role="article"
|
||||||
|
aria-label="{stat.label}: {stat.value}"
|
||||||
>
|
>
|
||||||
<stat.icon class={`mx-auto size-8 ${stat.color}`} />
|
<stat.icon class={`mx-auto size-8 ${stat.color}`} aria-hidden="true" />
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="text-3xl font-bold">{stat.value}</div>
|
<div class="text-3xl font-bold">{stat.value}</div>
|
||||||
<p class="text-sm opacity-75">{stat.label}</p>
|
<p class="text-sm opacity-75">{stat.label}</p>
|
||||||
@@ -122,16 +124,26 @@
|
|||||||
>
|
>
|
||||||
<action.icon
|
<action.icon
|
||||||
class="text-primary-500 size-10 transition-transform group-hover:scale-110"
|
class="text-primary-500 size-10 transition-transform group-hover:scale-110"
|
||||||
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<h3 class="h4 group-hover:text-primary-500 transition-colors">{action.title}</h3>
|
<h3 class="h4 group-hover:text-primary-500 transition-colors">{action.title}</h3>
|
||||||
<p class="text-sm opacity-75">{action.description}</p>
|
<p class="text-sm opacity-75">{action.description}</p>
|
||||||
|
|
||||||
{#if action.available}
|
{#if action.available}
|
||||||
<a href={action.href} class="btn preset-filled-primary-500 w-full">
|
<a
|
||||||
|
href={action.href}
|
||||||
|
class="btn preset-filled-primary-500 w-full"
|
||||||
|
aria-label="{action.action} - {action.title}"
|
||||||
|
>
|
||||||
{action.action}
|
{action.action}
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<button class="btn preset-outlined-surface-200-800 w-full opacity-50" disabled>
|
<button
|
||||||
|
class="btn preset-outlined-surface-200-800 w-full opacity-50"
|
||||||
|
disabled
|
||||||
|
aria-label="{action.action} - {action.title} (Coming Soon)"
|
||||||
|
title="This feature is coming soon"
|
||||||
|
>
|
||||||
{action.action} (Coming Soon)
|
{action.action} (Coming Soon)
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -146,7 +158,7 @@
|
|||||||
<section class="space-y-8">
|
<section class="space-y-8">
|
||||||
<div class="card preset-outlined-primary-500 space-y-6 p-8 text-center">
|
<div class="card preset-outlined-primary-500 space-y-6 p-8 text-center">
|
||||||
<div class="flex items-center justify-center gap-2">
|
<div class="flex items-center justify-center gap-2">
|
||||||
<Star class="text-primary-500 size-8" />
|
<Star class="text-primary-500 size-8" aria-hidden="true" />
|
||||||
<h3 class="h3 text-primary-500">Current Plan: Starter</h3>
|
<h3 class="h3 text-primary-500">Current Plan: Starter</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="mx-auto max-w-2xl opacity-75">
|
<p class="mx-auto max-w-2xl opacity-75">
|
||||||
@@ -154,12 +166,21 @@
|
|||||||
limits, and priority support.
|
limits, and priority support.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex flex-col justify-center gap-4 sm:flex-row">
|
<div class="flex flex-col justify-center gap-4 sm:flex-row">
|
||||||
<a href="/pricing" class="btn preset-filled-primary-500">
|
<a
|
||||||
<Star class="size-5" />
|
href="/pricing"
|
||||||
|
class="btn preset-filled-primary-500"
|
||||||
|
aria-label="Upgrade your plan to unlock premium features"
|
||||||
|
>
|
||||||
|
<Star class="size-5" aria-hidden="true" />
|
||||||
<span>Upgrade Plan</span>
|
<span>Upgrade Plan</span>
|
||||||
</a>
|
</a>
|
||||||
<button class="btn preset-outlined-surface-200-800" disabled>
|
<button
|
||||||
<BarChart class="size-5" />
|
class="btn preset-outlined-surface-200-800"
|
||||||
|
disabled
|
||||||
|
aria-label="View usage statistics (Coming Soon)"
|
||||||
|
title="Usage statistics feature is coming soon"
|
||||||
|
>
|
||||||
|
<BarChart class="size-5" aria-hidden="true" />
|
||||||
<span>View Usage</span>
|
<span>View Usage</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,17 +191,27 @@
|
|||||||
<section class="space-y-8">
|
<section class="space-y-8">
|
||||||
<h2 class="h2 text-center">Recent Activity</h2>
|
<h2 class="h2 text-center">Recent Activity</h2>
|
||||||
<div class="card preset-outlined-surface-200-800 space-y-4 p-8 text-center">
|
<div class="card preset-outlined-surface-200-800 space-y-4 p-8 text-center">
|
||||||
<BarChart class="text-primary-500 mx-auto size-16 opacity-50" />
|
<BarChart class="text-primary-500 mx-auto size-16 opacity-50" aria-hidden="true" />
|
||||||
<h3 class="h4">No recent activity</h3>
|
<h3 class="h4">No recent activity</h3>
|
||||||
<p class="opacity-75">
|
<p class="opacity-75">
|
||||||
Start using our services to see your activity here. Create your first project or make an
|
Start using our services to see your activity here. Create your first project or make an
|
||||||
API call to get started.
|
API call to get started.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex flex-col justify-center gap-4 sm:flex-row">
|
<div class="flex flex-col justify-center gap-4 sm:flex-row">
|
||||||
<button class="btn preset-filled-primary-500" disabled>
|
<button
|
||||||
|
class="btn preset-filled-primary-500"
|
||||||
|
disabled
|
||||||
|
aria-label="Create new project (Coming Soon)"
|
||||||
|
title="Project creation feature is coming soon"
|
||||||
|
>
|
||||||
Create Project (Coming Soon)
|
Create Project (Coming Soon)
|
||||||
</button>
|
</button>
|
||||||
<button class="btn preset-outlined-surface-200-800" disabled>
|
<button
|
||||||
|
class="btn preset-outlined-surface-200-800"
|
||||||
|
disabled
|
||||||
|
aria-label="View documentation (Coming Soon)"
|
||||||
|
title="Documentation feature is coming soon"
|
||||||
|
>
|
||||||
View Documentation (Coming Soon)
|
View Documentation (Coming Soon)
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -189,17 +220,25 @@
|
|||||||
{:else if session === null}
|
{:else if session === null}
|
||||||
<!-- Access Denied -->
|
<!-- Access Denied -->
|
||||||
<div class="card preset-outlined-error-500 mx-auto max-w-2xl space-y-6 p-8 text-center md:p-12">
|
<div class="card preset-outlined-error-500 mx-auto max-w-2xl space-y-6 p-8 text-center md:p-12">
|
||||||
<Shield class="text-error-500 mx-auto size-16" />
|
<Shield class="text-error-500 mx-auto size-16" aria-hidden="true" />
|
||||||
<h2 class="h3">Access Denied</h2>
|
<h2 class="h3">Access Denied</h2>
|
||||||
<p class="opacity-75">
|
<p class="opacity-75">
|
||||||
You need to be logged in to access your dashboard. Please sign in to continue.
|
You need to be logged in to access your dashboard. Please sign in to continue.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex flex-col justify-center gap-4 sm:flex-row">
|
<div class="flex flex-col justify-center gap-4 sm:flex-row">
|
||||||
<a href="/auth/login" class="btn preset-filled-primary-500">
|
<a
|
||||||
<User class="size-5" />
|
href="/auth/login"
|
||||||
|
class="btn preset-filled-primary-500"
|
||||||
|
aria-label="Sign in to access your dashboard"
|
||||||
|
>
|
||||||
|
<User class="size-5" aria-hidden="true" />
|
||||||
<span>Sign In</span>
|
<span>Sign In</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/auth/signup" class="btn preset-outlined-surface-200-800">
|
<a
|
||||||
|
href="/auth/signup"
|
||||||
|
class="btn preset-outlined-surface-200-800"
|
||||||
|
aria-label="Create new account to get started"
|
||||||
|
>
|
||||||
<span>Create Account</span>
|
<span>Create Account</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -208,8 +247,13 @@
|
|||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<div
|
<div
|
||||||
class="card preset-outlined-surface-200-800 mx-auto max-w-2xl space-y-4 p-8 text-center md:p-12"
|
class="card preset-outlined-surface-200-800 mx-auto max-w-2xl space-y-4 p-8 text-center md:p-12"
|
||||||
|
role="status"
|
||||||
|
aria-live="polite"
|
||||||
>
|
>
|
||||||
<div class="border-primary-500 mx-auto h-12 w-12 animate-spin rounded-full border-b-2"></div>
|
<div
|
||||||
|
class="border-primary-500 mx-auto h-12 w-12 animate-spin rounded-full border-b-2"
|
||||||
|
aria-label="Loading dashboard"
|
||||||
|
></div>
|
||||||
<h3 class="h4">Loading your dashboard...</h3>
|
<h3 class="h4">Loading your dashboard...</h3>
|
||||||
<p class="opacity-75">Please wait while we prepare your personalized experience.</p>
|
<p class="opacity-75">Please wait while we prepare your personalized experience.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -369,6 +369,8 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="absolute right-3 top-1/2 -translate-y-1/2 text-surface-500"
|
class="absolute right-3 top-1/2 -translate-y-1/2 text-surface-500"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
|
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
||||||
|
title={showPassword ? 'Hide password' : 'Show password'}
|
||||||
>
|
>
|
||||||
{#if showPassword}
|
{#if showPassword}
|
||||||
<EyeOff class="size-4" />
|
<EyeOff class="size-4" />
|
||||||
@@ -432,12 +434,14 @@
|
|||||||
class="btn w-full flex items-center justify-center gap-3 {provider.color}"
|
class="btn w-full flex items-center justify-center gap-3 {provider.color}"
|
||||||
onclick={() => handleOAuth(provider.provider)}
|
onclick={() => handleOAuth(provider.provider)}
|
||||||
disabled={!provider.enabled || loading || oauthLoading !== ''}
|
disabled={!provider.enabled || loading || oauthLoading !== ''}
|
||||||
|
aria-label="{provider.description}"
|
||||||
|
title={provider.enabled ? provider.description : `${provider.name} login is disabled in demo mode`}
|
||||||
>
|
>
|
||||||
{#if oauthLoading === provider.provider}
|
{#if oauthLoading === provider.provider}
|
||||||
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-current"></div>
|
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-current" aria-hidden="true"></div>
|
||||||
Connecting...
|
Connecting...
|
||||||
{:else}
|
{:else}
|
||||||
<provider.icon class="size-4" />
|
<provider.icon class="size-4" aria-hidden="true" />
|
||||||
{provider.description}
|
{provider.description}
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -143,6 +143,8 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="absolute right-3 top-1/2 -translate-y-1/2 text-surface-500"
|
class="absolute right-3 top-1/2 -translate-y-1/2 text-surface-500"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
|
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
||||||
|
title={showPassword ? 'Hide password' : 'Show password'}
|
||||||
>
|
>
|
||||||
{#if showPassword}
|
{#if showPassword}
|
||||||
<EyeOff class="size-4" />
|
<EyeOff class="size-4" />
|
||||||
@@ -173,6 +175,8 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="absolute right-3 top-1/2 -translate-y-1/2 text-surface-500"
|
class="absolute right-3 top-1/2 -translate-y-1/2 text-surface-500"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
|
aria-label={showConfirmPassword ? 'Hide confirm password' : 'Show confirm password'}
|
||||||
|
title={showConfirmPassword ? 'Hide confirm password' : 'Show confirm password'}
|
||||||
>
|
>
|
||||||
{#if showConfirmPassword}
|
{#if showConfirmPassword}
|
||||||
<EyeOff class="size-4" />
|
<EyeOff class="size-4" />
|
||||||
|
|||||||
@@ -38,12 +38,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||||
{#each featuredPosts as post}
|
{#each featuredPosts as post}
|
||||||
<a href="/blog/{post.slug}" class="card preset-outlined-primary-500 p-6 md:p-8 space-y-4 hover:scale-105 transition-all duration-300 group">
|
<a href="/blog/{post.slug}" class="card preset-outlined-primary-500 p-6 md:p-8 space-y-4 hover:scale-105 hover:shadow-2xl transition-all duration-300 group">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="badge preset-filled-primary-500 flex items-center gap-1">
|
|
||||||
<Star class="size-3" />
|
|
||||||
Featured
|
|
||||||
</span>
|
|
||||||
<div class="flex items-center gap-1 text-sm opacity-75">
|
<div class="flex items-center gap-1 text-sm opacity-75">
|
||||||
<Calendar class="size-4" />
|
<Calendar class="size-4" />
|
||||||
{formatDate(post.publishedAt)}
|
{formatDate(post.publishedAt)}
|
||||||
@@ -87,7 +83,7 @@
|
|||||||
<h2 class="h2 text-center">Recent Posts</h2>
|
<h2 class="h2 text-center">Recent Posts</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{#each regularPosts as post}
|
{#each regularPosts as post}
|
||||||
<a href="/blog/{post.slug}" class="card preset-outlined-primary-500 p-4 md:p-6 space-y-4 hover:scale-105 transition-all duration-300 group">
|
<a href="/blog/{post.slug}" class="card preset-outlined-primary-500 p-4 md:p-6 space-y-4 hover:scale-105 hover:shadow-2xl transition-all duration-300 group">
|
||||||
<div class="flex items-center gap-1 text-sm opacity-75">
|
<div class="flex items-center gap-1 text-sm opacity-75">
|
||||||
<Calendar class="size-4" />
|
<Calendar class="size-4" />
|
||||||
{formatDate(post.publishedAt)}
|
{formatDate(post.publishedAt)}
|
||||||
|
|||||||
@@ -72,8 +72,9 @@
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="btn btn-sm preset-outlined-secondary-500"
|
class="btn btn-sm preset-outlined-secondary-500"
|
||||||
title="Share on Twitter"
|
title="Share on Twitter"
|
||||||
|
aria-label="Share this article on Twitter"
|
||||||
>
|
>
|
||||||
<Twitter class="size-4" />
|
<Twitter class="size-4" aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://www.linkedin.com/sharing/share-offsite/?url={encodeURIComponent(typeof window !== 'undefined' ? window.location.href : '')}"
|
href="https://www.linkedin.com/sharing/share-offsite/?url={encodeURIComponent(typeof window !== 'undefined' ? window.location.href : '')}"
|
||||||
@@ -81,8 +82,9 @@
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="btn btn-sm preset-outlined-secondary-500"
|
class="btn btn-sm preset-outlined-secondary-500"
|
||||||
title="Share on LinkedIn"
|
title="Share on LinkedIn"
|
||||||
|
aria-label="Share this article on LinkedIn"
|
||||||
>
|
>
|
||||||
<Linkedin class="size-4" />
|
<Linkedin class="size-4" aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,8 +131,9 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="btn btn-sm preset-outlined-surface-200-800"
|
class="btn btn-sm preset-outlined-surface-200-800"
|
||||||
|
aria-label="Share this article on Twitter"
|
||||||
>
|
>
|
||||||
<Twitter class="size-4" />
|
<Twitter class="size-4" aria-hidden="true" />
|
||||||
<span>Twitter</span>
|
<span>Twitter</span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
@@ -138,8 +141,9 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="btn btn-sm preset-outlined-surface-200-800"
|
class="btn btn-sm preset-outlined-surface-200-800"
|
||||||
|
aria-label="Share this article on LinkedIn"
|
||||||
>
|
>
|
||||||
<Linkedin class="size-4" />
|
<Linkedin class="size-4" aria-hidden="true" />
|
||||||
<span>LinkedIn</span>
|
<span>LinkedIn</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
26
src/routes/(marketing)/contact/+page.server.ts
Normal file
26
src/routes/(marketing)/contact/+page.server.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Resend } from 'resend';
|
||||||
|
|
||||||
|
import { RESEND_API_KEY } from '$env/static/private';
|
||||||
|
|
||||||
|
const resend = new Resend(RESEND_API_KEY);
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
sendEmail: async ({ request }) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const email = formData.get('email');
|
||||||
|
const message = formData.get('message');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await resend.emails.send({
|
||||||
|
from: 'your-email@example.com',
|
||||||
|
to: 'recipient@example.com',
|
||||||
|
subject: 'New Contact Form Submission',
|
||||||
|
text: `Email: ${email}\nMessage: ${message}`,
|
||||||
|
});
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending email:', error);
|
||||||
|
return { success: false, error: 'Failed to send email' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
<h1 class="h1">Get in <span class="text-primary-500">Touch</span></h1>
|
<h1 class="h1">Get in <span class="text-primary-500">Touch</span></h1>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xl opacity-75 max-w-2xl mx-auto">
|
<p class="text-xl opacity-75 max-w-2xl mx-auto">
|
||||||
Have questions? We'd love to hear from you. Send us a message and we'll respond as soon as possible.
|
Have questions? We'd love to hear from you. <br> Send us a message and we'll respond as soon as possible.
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<!-- Contact Methods -->
|
<!-- Contact Methods -->
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="card preset-outlined-surface-200-800 p-6 space-y-4">
|
<div class="card preset-outlined-surface-500 p-6 space-y-4">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="w-10 h-10 bg-primary-500 rounded-lg flex items-center justify-center">
|
<div class="w-10 h-10 bg-primary-500 rounded-lg flex items-center justify-center">
|
||||||
<Mail class="size-5 text-white" />
|
<Mail class="size-5 text-white" />
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card preset-outlined-surface-200-800 p-6 space-y-4">
|
<div class="card preset-outlined-surface-500 p-6 space-y-4">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="w-10 h-10 bg-secondary-500 rounded-lg flex items-center justify-center">
|
<div class="w-10 h-10 bg-secondary-500 rounded-lg flex items-center justify-center">
|
||||||
<Phone class="size-5 text-white" />
|
<Phone class="size-5 text-white" />
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card preset-outlined-surface-200-800 p-6 space-y-4">
|
<div class="card preset-outlined-surface-500 p-6 space-y-4">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="w-10 h-10 bg-tertiary-500 rounded-lg flex items-center justify-center">
|
<div class="w-10 h-10 bg-tertiary-500 rounded-lg flex items-center justify-center">
|
||||||
<MapPin class="size-5 text-white" />
|
<MapPin class="size-5 text-white" />
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Response Time -->
|
<!-- Response Time -->
|
||||||
<div class="card preset-filled-primary-500 p-6 text-center">
|
<div class="card preset-outlined-primary-500 p-6 text-center">
|
||||||
<Clock class="size-8 mx-auto mb-3" />
|
<Clock class="size-8 mx-auto mb-3" />
|
||||||
<h3 class="h4 mb-2">Quick Response</h3>
|
<h3 class="h4 mb-2">Quick Response</h3>
|
||||||
<p class="text-sm opacity-90">
|
<p class="text-sm opacity-90">
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
|
|
||||||
<!-- Contact Form -->
|
<!-- Contact Form -->
|
||||||
<div class="lg:col-span-2">
|
<div class="lg:col-span-2">
|
||||||
<div class="card preset-outlined-surface-200-800 p-8">
|
<div class="card preset-outlined-surface-500 p-8">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="h3 mb-2">Send us a message</h2>
|
<h2 class="h3 mb-2">Send us a message</h2>
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
Name *
|
Name *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="input preset-outlined-surface-200-800"
|
class="input preset-outlined-surface-500"
|
||||||
type="text"
|
type="text"
|
||||||
id="name"
|
id="name"
|
||||||
bind:value={formData.name}
|
bind:value={formData.name}
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
Email *
|
Email *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="input preset-outlined-surface-200-800"
|
class="input preset-outlined-surface-500"
|
||||||
type="email"
|
type="email"
|
||||||
id="email"
|
id="email"
|
||||||
bind:value={formData.email}
|
bind:value={formData.email}
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
Subject *
|
Subject *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="input preset-outlined-surface-200-800"
|
class="input preset-outlined-surface-500"
|
||||||
type="text"
|
type="text"
|
||||||
id="subject"
|
id="subject"
|
||||||
bind:value={formData.subject}
|
bind:value={formData.subject}
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
Message *
|
Message *
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
class="textarea preset-outlined-surface-200-800"
|
class="textarea preset-outlined-surface-500"
|
||||||
id="message"
|
id="message"
|
||||||
bind:value={formData.message}
|
bind:value={formData.message}
|
||||||
placeholder="Tell us more about your question or feedback..."
|
placeholder="Tell us more about your question or feedback..."
|
||||||
@@ -184,12 +184,13 @@
|
|||||||
type="submit"
|
type="submit"
|
||||||
class="btn preset-filled-primary-500 w-full flex items-center justify-center gap-2"
|
class="btn preset-filled-primary-500 w-full flex items-center justify-center gap-2"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
aria-label={loading ? 'Sending message...' : 'Send message'}
|
||||||
>
|
>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-white" aria-hidden="true"></div>
|
||||||
Sending...
|
Sending...
|
||||||
{:else}
|
{:else}
|
||||||
<Send class="size-4" />
|
<Send class="size-4" aria-hidden="true" />
|
||||||
Send Message
|
Send Message
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -121,9 +121,9 @@
|
|||||||
{#each plan.features as feature}
|
{#each plan.features as feature}
|
||||||
<li class="flex items-center gap-3">
|
<li class="flex items-center gap-3">
|
||||||
{#if feature.included}
|
{#if feature.included}
|
||||||
<Check class="size-5 text-success-500 flex-shrink-0" />
|
<Check class="size-5 text-success-500 flex-shrink-0" aria-hidden="true" />
|
||||||
{:else}
|
{:else}
|
||||||
<X class="size-5 text-error-500 flex-shrink-0" />
|
<X class="size-5 text-error-500 flex-shrink-0" aria-hidden="true" />
|
||||||
{/if}
|
{/if}
|
||||||
<span class="{feature.included ? '' : 'opacity-50'}">{feature.name}</span>
|
<span class="{feature.included ? '' : 'opacity-50'}">{feature.name}</span>
|
||||||
</li>
|
</li>
|
||||||
@@ -131,7 +131,10 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- CTA Button -->
|
<!-- CTA Button -->
|
||||||
<button class="btn w-full {plan.buttonClass}">
|
<button
|
||||||
|
class="btn w-full {plan.buttonClass}"
|
||||||
|
aria-label="{plan.buttonText} - {plan.name} plan"
|
||||||
|
>
|
||||||
{plan.buttonText}
|
{plan.buttonText}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -74,21 +74,30 @@
|
|||||||
<button
|
<button
|
||||||
onclick={() => window.history.back()}
|
onclick={() => window.history.back()}
|
||||||
class="btn preset-outlined-surface-200-800 flex items-center gap-2"
|
class="btn preset-outlined-surface-200-800 flex items-center gap-2"
|
||||||
|
aria-label="Go back to previous page"
|
||||||
|
title="Go back to previous page"
|
||||||
>
|
>
|
||||||
<ArrowLeft class="size-4" />
|
<ArrowLeft class="size-4" aria-hidden="true" />
|
||||||
<span>Go Back</span>
|
<span>Go Back</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onclick={() => window.location.reload()}
|
onclick={() => window.location.reload()}
|
||||||
class="btn preset-outlined-surface-200-800 flex items-center gap-2"
|
class="btn preset-outlined-surface-200-800 flex items-center gap-2"
|
||||||
|
aria-label="Reload current page"
|
||||||
|
title="Reload current page"
|
||||||
>
|
>
|
||||||
<RefreshCw class="size-4" />
|
<RefreshCw class="size-4" aria-hidden="true" />
|
||||||
<span>Try Again</span>
|
<span>Try Again</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<a href="/" class="btn preset-filled-primary-500 flex items-center gap-2">
|
<a
|
||||||
<Home class="size-4" />
|
href="/"
|
||||||
|
class="btn preset-filled-primary-500 flex items-center gap-2"
|
||||||
|
aria-label="Return to homepage"
|
||||||
|
title="Return to homepage"
|
||||||
|
>
|
||||||
|
<Home class="size-4" aria-hidden="true" />
|
||||||
<span>Go Home</span>
|
<span>Go Home</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,14 +2,12 @@
|
|||||||
import { invalidate } from '$app/navigation';
|
import { invalidate } from '$app/navigation';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { toaster } from '$lib';
|
import { toaster } from '$lib';
|
||||||
import ThemeSwitch from '$lib/components/ThemeSwitch.svelte';
|
import { Modal, Toaster } from '@skeletonlabs/skeleton-svelte';
|
||||||
import { Avatar, Modal, Toaster } from '@skeletonlabs/skeleton-svelte';
|
|
||||||
import { BookOpen, DollarSign, Home, LayoutDashboard, LogOut, User } from '@lucide/svelte';
|
|
||||||
import 'prism-themes/themes/prism-vsc-dark-plus.css';
|
import 'prism-themes/themes/prism-vsc-dark-plus.css';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { MetaTags, deepMerge } from 'svelte-meta-tags';
|
import { MetaTags, deepMerge } from 'svelte-meta-tags';
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
import SvelteyLogoLetter from '$lib/assets/Sveltey-logo-letter.svelte';
|
import Header from '$lib/components/Header.svelte';
|
||||||
|
|
||||||
let { data, children } = $props();
|
let { data, children } = $props();
|
||||||
let { session, supabase } = $derived(data);
|
let { session, supabase } = $derived(data);
|
||||||
@@ -66,185 +64,7 @@
|
|||||||
<Toaster {toaster}></Toaster>
|
<Toaster {toaster}></Toaster>
|
||||||
<Modal />
|
<Modal />
|
||||||
|
|
||||||
<!-- Navigation Header -->
|
<Header {data} />
|
||||||
<header class="bg-surface-50-950-token border-surface-200-700-token border-b sticky top-0 z-50 backdrop-blur-2xl">
|
|
||||||
<nav class="container mx-auto px-6 py-4">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<!-- Left side - Brand and Main navigation -->
|
|
||||||
<div class="flex items-center space-x-8">
|
|
||||||
<!-- Brand -->
|
|
||||||
<a href="/" class="flex items-center gap-2 transition-opacity hover:opacity-75">
|
|
||||||
<SvelteyLogoLetter size="size-12" />
|
|
||||||
<span class="text-xl font-bold">Sveltey</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<!-- Main Navigation -->
|
|
||||||
<div class="hidden items-center space-x-1 md:flex">
|
|
||||||
{#if isActivePath('/')}
|
|
||||||
<span class={getNavLinkClasses('/')} aria-current="page">
|
|
||||||
<Home class="size-4" />
|
|
||||||
<span>Home</span>
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<a href="/" class={getNavLinkClasses('/')}>
|
|
||||||
<Home class="size-4" />
|
|
||||||
<span>Home</span>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if isActivePath('/pricing')}
|
|
||||||
<span class={getNavLinkClasses('/pricing')} aria-current="page">
|
|
||||||
<DollarSign class="size-4" />
|
|
||||||
<span>Pricing</span>
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<a href="/pricing" class={getNavLinkClasses('/pricing')}>
|
|
||||||
<DollarSign class="size-4" />
|
|
||||||
<span>Pricing</span>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if isActivePath('/blog')}
|
|
||||||
<span class={getNavLinkClasses('/blog')} aria-current="page">
|
|
||||||
<BookOpen class="size-4" />
|
|
||||||
<span>Blog</span>
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<a href="/blog" class={getNavLinkClasses('/blog')}>
|
|
||||||
<BookOpen class="size-4" />
|
|
||||||
<span>Blog</span>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if session}
|
|
||||||
{#if isActivePath('/app/dashboard')}
|
|
||||||
<span class={getNavLinkClasses('/app/dashboard')} aria-current="page">
|
|
||||||
<LayoutDashboard class="size-4" />
|
|
||||||
<span>Dashboard</span>
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<a href="/app/dashboard" class={getNavLinkClasses('/app/dashboard')}>
|
|
||||||
<LayoutDashboard class="size-4" />
|
|
||||||
<span>Dashboard</span>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Right side - User actions and theme switcher -->
|
|
||||||
<div class="flex items-center space-x-3">
|
|
||||||
<ThemeSwitch />
|
|
||||||
|
|
||||||
{#if session}
|
|
||||||
<!-- User Profile Section -->
|
|
||||||
<div class="border-surface-300-600-token flex items-center gap-3 border-l pl-3">
|
|
||||||
<Avatar
|
|
||||||
size="size-8"
|
|
||||||
src={session.user.user_metadata.avatar_url}
|
|
||||||
name={session.user.user_metadata.full_name || session.user.email}
|
|
||||||
/>
|
|
||||||
<div class="hidden md:block">
|
|
||||||
<p class="text-sm font-medium">
|
|
||||||
{session.user.user_metadata.full_name || session.user.email?.split('@')[0]}
|
|
||||||
</p>
|
|
||||||
<p class="text-xs opacity-75">{session.user.email}</p>
|
|
||||||
</div>
|
|
||||||
<form action="/auth/logout" method="POST" style="display: inline;">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="btn preset-outlined-surface-200-800 btn-sm flex items-center gap-2"
|
|
||||||
title="Sign Out"
|
|
||||||
>
|
|
||||||
<LogOut class="size-4" />
|
|
||||||
<span class="hidden sm:inline">Sign Out</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<!-- Authentication Buttons -->
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
{#if isActivePath('/auth')}
|
|
||||||
<span
|
|
||||||
class="btn preset-filled-primary-500 flex h-8 cursor-default items-center gap-2"
|
|
||||||
aria-current="page"
|
|
||||||
>
|
|
||||||
<User class="size-4" />
|
|
||||||
<span>Sign In</span>
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<a href="/auth" class="btn preset-outlined-surface-500 flex h-8 items-center gap-2">
|
|
||||||
<User class="size-4" />
|
|
||||||
<span>Sign In</span>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
<a
|
|
||||||
href="/auth?mode=signup"
|
|
||||||
class="btn preset-filled-primary-500 flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<span>Get Started</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mobile Navigation Menu (Hidden by default, would need JS to toggle) -->
|
|
||||||
<div class="border-surface-300-600-token mt-4 border-t pt-4 md:hidden">
|
|
||||||
<div class="flex flex-col space-y-2">
|
|
||||||
{#if isActivePath('/')}
|
|
||||||
<span class={getMobileNavLinkClasses('/')} aria-current="page">
|
|
||||||
<Home class="size-4" />
|
|
||||||
<span>Home</span>
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<a href="/" class={getMobileNavLinkClasses('/')}>
|
|
||||||
<Home class="size-4" />
|
|
||||||
<span>Home</span>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if isActivePath('/pricing')}
|
|
||||||
<span class={getMobileNavLinkClasses('/pricing')} aria-current="page">
|
|
||||||
<DollarSign class="size-4" />
|
|
||||||
<span>Pricing</span>
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<a href="/pricing" class={getMobileNavLinkClasses('/pricing')}>
|
|
||||||
<DollarSign class="size-4" />
|
|
||||||
<span>Pricing</span>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if isActivePath('/blog')}
|
|
||||||
<span class={getMobileNavLinkClasses('/blog')} aria-current="page">
|
|
||||||
<BookOpen class="size-4" />
|
|
||||||
<span>Blog</span>
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<a href="/blog" class={getMobileNavLinkClasses('/blog')}>
|
|
||||||
<BookOpen class="size-4" />
|
|
||||||
<span>Blog</span>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if session}
|
|
||||||
{#if isActivePath('/app/dashboard')}
|
|
||||||
<span class={getMobileNavLinkClasses('/app/dashboard')} aria-current="page">
|
|
||||||
<LayoutDashboard class="size-4" />
|
|
||||||
<span>Dashboard</span>
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<a href="/app/dashboard" class={getMobileNavLinkClasses('/app/dashboard')}>
|
|
||||||
<LayoutDashboard class="size-4" />
|
|
||||||
<span>Dashboard</span>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="min-h-screen">
|
<main class="min-h-screen">
|
||||||
|
|||||||
@@ -16,16 +16,28 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="flex flex-col sm:flex-row gap-4">
|
<div class="flex flex-col sm:flex-row gap-4">
|
||||||
{#if data.session}
|
{#if data.session}
|
||||||
<a href="/app/dashboard" class="btn btn-lg preset-filled-primary-500">
|
<a
|
||||||
<Rocket class="size-5" />
|
href="/app/dashboard"
|
||||||
|
class="btn btn-lg preset-filled-primary-500"
|
||||||
|
aria-label="Go to your dashboard"
|
||||||
|
>
|
||||||
|
<Rocket class="size-5" aria-hidden="true" />
|
||||||
<span>Go to Dashboard</span>
|
<span>Go to Dashboard</span>
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<a href="/auth/signup" class="btn btn-lg preset-filled-primary-500">
|
<a
|
||||||
<Star class="size-5" />
|
href="/auth/signup"
|
||||||
|
class="btn btn-lg preset-filled-primary-500"
|
||||||
|
aria-label="Sign up for free account"
|
||||||
|
>
|
||||||
|
<Star class="size-5" aria-hidden="true" />
|
||||||
<span>Get Started Free</span>
|
<span>Get Started Free</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/auth/login" class="btn btn-lg preset-outlined-primary-500">
|
<a
|
||||||
|
href="/auth/login"
|
||||||
|
class="btn btn-lg preset-outlined-primary-500"
|
||||||
|
aria-label="Sign in to existing account"
|
||||||
|
>
|
||||||
<span>Sign In</span>
|
<span>Sign In</span>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -33,8 +45,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Hero Image/Graphic -->
|
<!-- Hero Image/Graphic -->
|
||||||
<div class="order-1 lg:order-2 flex justify-center">
|
<div class="order-1 lg:order-2 flex justify-center">
|
||||||
<div class="size-64 lg:size-96 bg-gradient-to-br from-primary-500/20 to-secondary-500/20 rounded-full flex items-center justify-center shadow-2xl animate-tilt">
|
<div class="size-64 lg:size-96 bg-gradient-to-br from-primary-500/20 to-secondary-500/20 rounded-full flex items-center justify-center shadow-2xl animate-tilt" role="img" aria-label="SaaS launch illustration">
|
||||||
<Rocket class="size-32 lg:size-64 text-primary-500" />
|
<Rocket class="size-32 lg:size-64 text-primary-500" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -68,7 +80,7 @@
|
|||||||
<!-- Features Section -->
|
<!-- Features Section -->
|
||||||
<section class="grid grid-cols-1 md:grid-cols-3 gap-4 lg:gap-8">
|
<section class="grid grid-cols-1 md:grid-cols-3 gap-4 lg:gap-8">
|
||||||
<div class="card preset-outlined-surface-200-800 p-4 md:p-8 space-y-4">
|
<div class="card preset-outlined-surface-200-800 p-4 md:p-8 space-y-4">
|
||||||
<Database class="stroke-primary-500 size-10" />
|
<Database class="stroke-primary-500 size-10" aria-hidden="true" />
|
||||||
<h3 class="h3">Supabase Ready</h3>
|
<h3 class="h3">Supabase Ready</h3>
|
||||||
<p class="opacity-75">
|
<p class="opacity-75">
|
||||||
Authentication, real-time database, and file storage configured out of the box.
|
Authentication, real-time database, and file storage configured out of the box.
|
||||||
@@ -76,7 +88,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card preset-outlined-surface-200-800 p-4 md:p-8 space-y-4">
|
<div class="card preset-outlined-surface-200-800 p-4 md:p-8 space-y-4">
|
||||||
<Zap class="stroke-primary-500 size-10" />
|
<Zap class="stroke-primary-500 size-10" aria-hidden="true" />
|
||||||
<h3 class="h3">Lightning Fast</h3>
|
<h3 class="h3">Lightning Fast</h3>
|
||||||
<p class="opacity-75">
|
<p class="opacity-75">
|
||||||
Built with SvelteKit for maximum performance. Server-side rendering,
|
Built with SvelteKit for maximum performance. Server-side rendering,
|
||||||
@@ -84,7 +96,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card preset-outlined-surface-200-800 p-4 md:p-8 space-y-4">
|
<div class="card preset-outlined-surface-200-800 p-4 md:p-8 space-y-4">
|
||||||
<GitBranch class="stroke-primary-500 size-10" />
|
<GitBranch class="stroke-primary-500 size-10" aria-hidden="true" />
|
||||||
<h3 class="h3">Developer Experience</h3>
|
<h3 class="h3">Developer Experience</h3>
|
||||||
<p class="opacity-75">
|
<p class="opacity-75">
|
||||||
TypeScript, ESLint, Prettier, and comprehensive documentation.
|
TypeScript, ESLint, Prettier, and comprehensive documentation.
|
||||||
@@ -135,13 +147,21 @@
|
|||||||
<p class="opacity-75">Join thousands of developers who trust our template to kickstart their SaaS projects.</p>
|
<p class="opacity-75">Join thousands of developers who trust our template to kickstart their SaaS projects.</p>
|
||||||
</div>
|
</div>
|
||||||
{#if !data.session}
|
{#if !data.session}
|
||||||
<a href="/auth/signup" class="btn md:btn-lg preset-filled-primary-500">
|
<a
|
||||||
<Users class="size-5" />
|
href="/auth/signup"
|
||||||
|
class="btn md:btn-lg preset-filled-primary-500"
|
||||||
|
aria-label="Start building your SaaS today"
|
||||||
|
>
|
||||||
|
<Users class="size-5" aria-hidden="true" />
|
||||||
<span>Start Building Today</span>
|
<span>Start Building Today</span>
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<a href="/pricing" class="btn md:btn-lg preset-filled-secondary-500">
|
<a
|
||||||
<Star class="size-5" />
|
href="/pricing"
|
||||||
|
class="btn md:btn-lg preset-filled-secondary-500"
|
||||||
|
aria-label="View pricing plans"
|
||||||
|
>
|
||||||
|
<Star class="size-5" aria-hidden="true" />
|
||||||
<span>View Pricing</span>
|
<span>View Pricing</span>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user