Merge pull request #6118 from menloresearch/rp/jan-docs-v2-blog
adding handbook, blog, and changelog to jan docs v2
@ -35,28 +35,28 @@ const socials = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const menus = [
|
const menus = [
|
||||||
{
|
// {
|
||||||
name: 'Product',
|
// name: 'Product',
|
||||||
child: [
|
// child: [
|
||||||
{
|
// {
|
||||||
menu: 'Download',
|
// menu: 'Download',
|
||||||
path: '/download',
|
// path: '/download',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
menu: 'Changelog',
|
// menu: 'Changelog',
|
||||||
path: '/changelog',
|
// path: '/changelog',
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: 'For Developers',
|
// name: 'For Developers',
|
||||||
child: [
|
// child: [
|
||||||
{
|
// {
|
||||||
menu: 'Documentation',
|
// menu: 'Documentation',
|
||||||
path: '/docs',
|
// path: '/docs',
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
name: 'Community',
|
name: 'Community',
|
||||||
child: [
|
child: [
|
||||||
@ -71,7 +71,7 @@ const menus = [
|
|||||||
external: true,
|
external: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
menu: 'Twitter',
|
menu: 'X/Twitter',
|
||||||
path: 'https://twitter.com/jandotai',
|
path: 'https://twitter.com/jandotai',
|
||||||
external: true,
|
external: true,
|
||||||
},
|
},
|
||||||
@ -86,8 +86,8 @@ const menus = [
|
|||||||
name: 'Company',
|
name: 'Company',
|
||||||
child: [
|
child: [
|
||||||
{
|
{
|
||||||
menu: 'About',
|
menu: 'Menlo',
|
||||||
path: '/about',
|
path: 'https://menlo.ai',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
menu: 'Blog',
|
menu: 'Blog',
|
||||||
@ -158,8 +158,8 @@ export default function Footer() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-shrink-0 relative overflow-hidden w-full">
|
<div className="flex-shrink-0 relative overflow-hidden w-full">
|
||||||
<div className="grid grid-cols-2 gap-8 md:grid-cols-2 lg:grid-cols-6">
|
<div className="grid grid-cols-2 gap-8 md:grid-cols-2 lg:grid-cols-12">
|
||||||
<div className="col-span-2">
|
<div className="col-span-2 lg:col-span-3">
|
||||||
<div className="flex items-center space-x-2 mb-3">
|
<div className="flex items-center space-x-2 mb-3">
|
||||||
<LogoMark />
|
<LogoMark />
|
||||||
<h2 className="text-lg font-semibold dark:text-white text-black">
|
<h2 className="text-lg font-semibold dark:text-white text-black">
|
||||||
@ -209,9 +209,10 @@ export default function Footer() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="hidden lg:block lg:col-span-3"></div>
|
||||||
{menus.map((menu, i) => {
|
{menus.map((menu, i) => {
|
||||||
return (
|
return (
|
||||||
<div key={i} className="lg:text-right">
|
<div key={i} className="lg:text-right lg:col-span-3">
|
||||||
<h2 className="mb-2 font-bold dark:text-gray-300 text-black">
|
<h2 className="mb-2 font-bold dark:text-gray-300 text-black">
|
||||||
{menu.name}
|
{menu.name}
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@ -33,10 +33,6 @@
|
|||||||
"layout": "raw"
|
"layout": "raw"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"about": {
|
|
||||||
"type": "page",
|
|
||||||
"title": "About"
|
|
||||||
},
|
|
||||||
"blog": {
|
"blog": {
|
||||||
"type": "page",
|
"type": "page",
|
||||||
"title": "Blog",
|
"title": "Blog",
|
||||||
|
|||||||
@ -1,15 +1,44 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import { defineConfig } from 'astro/config'
|
import { defineConfig } from 'astro/config'
|
||||||
import starlight from '@astrojs/starlight'
|
import starlight from '@astrojs/starlight'
|
||||||
import starlightThemeRapide from 'starlight-theme-rapide'
|
import starlightThemeNext from 'starlight-theme-next'
|
||||||
|
// import starlightThemeRapide from 'starlight-theme-rapide'
|
||||||
import starlightSidebarTopics from 'starlight-sidebar-topics'
|
import starlightSidebarTopics from 'starlight-sidebar-topics'
|
||||||
import mermaid from 'astro-mermaid'
|
import mermaid from 'astro-mermaid'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import path, { dirname } from 'path'
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// Deploy to the new v2 subdomain
|
// Deploy to the new v2 subdomain
|
||||||
site: 'https://v2.jan.ai',
|
site: 'https://v2.jan.ai',
|
||||||
// No 'base' property is needed, as this will be deployed to the root of the subdomain.
|
vite: {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
'@/components': path.resolve(__dirname, './src/components'),
|
||||||
|
'@/layouts': path.resolve(__dirname, './src/layouts'),
|
||||||
|
'@/assets': path.resolve(__dirname, './src/assets'),
|
||||||
|
'@/content': path.resolve(__dirname, './src/content'),
|
||||||
|
'@/styles': path.resolve(__dirname, './src/styles'),
|
||||||
|
'@/utils': path.resolve(__dirname, './src/utils'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
assetsInclude: [
|
||||||
|
'**/*.jpg',
|
||||||
|
'**/*.jpeg',
|
||||||
|
'**/*.png',
|
||||||
|
'**/*.gif',
|
||||||
|
'**/*.svg',
|
||||||
|
'**/*.webp',
|
||||||
|
],
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ['@astrojs/starlight'],
|
||||||
|
},
|
||||||
|
},
|
||||||
integrations: [
|
integrations: [
|
||||||
mermaid({
|
mermaid({
|
||||||
theme: 'default',
|
theme: 'default',
|
||||||
@ -17,14 +46,16 @@ export default defineConfig({
|
|||||||
}),
|
}),
|
||||||
starlight({
|
starlight({
|
||||||
title: '👋 Jan',
|
title: '👋 Jan',
|
||||||
|
|
||||||
favicon: 'jan2.png',
|
favicon: 'jan2.png',
|
||||||
plugins: [
|
plugins: [
|
||||||
starlightThemeRapide(),
|
// starlightThemeRapide(),
|
||||||
|
starlightThemeNext(),
|
||||||
starlightSidebarTopics(
|
starlightSidebarTopics(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
label: 'Jan Desktop',
|
label: 'Jan Desktop',
|
||||||
link: '/',
|
link: '/jan/',
|
||||||
icon: 'rocket',
|
icon: 'rocket',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@ -108,25 +139,10 @@ export default defineConfig({
|
|||||||
{
|
{
|
||||||
label: 'Local Server',
|
label: 'Local Server',
|
||||||
items: [
|
items: [
|
||||||
{ label: 'Introduction', link: '/local-server/' },
|
|
||||||
{ label: 'Server Setup', slug: 'local-server/api-server' },
|
|
||||||
{
|
{
|
||||||
label: 'Jan Data Folder',
|
label: 'All',
|
||||||
slug: 'local-server/data-folder',
|
|
||||||
},
|
|
||||||
{ label: 'Server Settings', slug: 'local-server/settings' },
|
|
||||||
{
|
|
||||||
label: 'Llama.cpp Server',
|
|
||||||
slug: 'local-server/llama-cpp',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Server Troubleshooting',
|
|
||||||
slug: 'local-server/troubleshooting',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Integrations',
|
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
autogenerate: { directory: 'local-server/integrations' },
|
autogenerate: { directory: 'local-server' },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -144,17 +160,100 @@ export default defineConfig({
|
|||||||
{
|
{
|
||||||
label: 'Jan Mobile',
|
label: 'Jan Mobile',
|
||||||
link: '/mobile/',
|
link: '/mobile/',
|
||||||
badge: { text: 'Coming Soon', variant: 'caution' },
|
badge: { text: 'Soon', variant: 'caution' },
|
||||||
icon: 'phone',
|
icon: 'phone',
|
||||||
items: [{ label: 'Overview', slug: 'mobile' }],
|
items: [{ label: 'Overview', slug: 'mobile' }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Jan Server',
|
label: 'Jan Server',
|
||||||
link: '/server/',
|
link: '/server/',
|
||||||
badge: { text: 'Coming Soon', variant: 'caution' },
|
badge: { text: 'Soon', variant: 'caution' },
|
||||||
icon: 'forward-slash',
|
icon: 'forward-slash',
|
||||||
items: [{ label: 'Overview', slug: 'server' }],
|
items: [{ label: 'Overview', slug: 'server' }],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Handbook',
|
||||||
|
link: '/handbook/',
|
||||||
|
icon: 'open-book',
|
||||||
|
items: [
|
||||||
|
{ label: 'Welcome', slug: 'handbook' },
|
||||||
|
{
|
||||||
|
label: 'About Jan',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Why does Jan Exist?',
|
||||||
|
collapsed: true,
|
||||||
|
autogenerate: { directory: 'handbook/why' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'How we make Money',
|
||||||
|
collapsed: true,
|
||||||
|
autogenerate: { directory: 'handbook/money' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Who We Hire',
|
||||||
|
collapsed: true,
|
||||||
|
autogenerate: { directory: 'handbook/who' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Jan's Philosophies",
|
||||||
|
collapsed: true,
|
||||||
|
autogenerate: { directory: 'handbook/philosophy' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Brand & Identity',
|
||||||
|
collapsed: true,
|
||||||
|
autogenerate: { directory: 'handbook/brand' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'How We Work',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Team Roster',
|
||||||
|
collapsed: true,
|
||||||
|
autogenerate: { directory: 'handbook/team' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Jan's Culture",
|
||||||
|
collapsed: true,
|
||||||
|
autogenerate: { directory: 'handbook/culture' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'How We Build',
|
||||||
|
collapsed: true,
|
||||||
|
autogenerate: { directory: 'handbook/how' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'How We Sell',
|
||||||
|
collapsed: true,
|
||||||
|
autogenerate: { directory: 'handbook/sell' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'HR',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'HR Lifecycle',
|
||||||
|
collapsed: true,
|
||||||
|
autogenerate: { directory: 'handbook/lifecycle' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'HR Policies',
|
||||||
|
collapsed: true,
|
||||||
|
autogenerate: { directory: 'handbook/hr' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Compensation',
|
||||||
|
collapsed: true,
|
||||||
|
autogenerate: { directory: 'handbook/comp' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
exclude: [
|
exclude: [
|
||||||
|
|||||||
@ -14,8 +14,10 @@
|
|||||||
"sharp": "^0.34.3",
|
"sharp": "^0.34.3",
|
||||||
"starlight-openapi": "^0.19.1",
|
"starlight-openapi": "^0.19.1",
|
||||||
"starlight-sidebar-topics": "^0.6.0",
|
"starlight-sidebar-topics": "^0.6.0",
|
||||||
|
"starlight-theme-next": "^0.3.2",
|
||||||
"starlight-theme-rapide": "^0.5.1",
|
"starlight-theme-rapide": "^0.5.1",
|
||||||
"starlight-videos": "^0.3.0",
|
"starlight-videos": "^0.3.0",
|
||||||
|
"unist-util-visit": "^5.0.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1076,6 +1078,8 @@
|
|||||||
|
|
||||||
"starlight-sidebar-topics": ["starlight-sidebar-topics@0.6.0", "", { "dependencies": { "picomatch": "^4.0.2" }, "peerDependencies": { "@astrojs/starlight": ">=0.32.0" } }, "sha512-ysmOR7zaHYKtk18/mpW4MbEMDioR/ZBsisu9bdQrq0v9BlHWpW7gAdWlqFWO9zdv1P7l0Mo1WKd0wJ0UtqOVEQ=="],
|
"starlight-sidebar-topics": ["starlight-sidebar-topics@0.6.0", "", { "dependencies": { "picomatch": "^4.0.2" }, "peerDependencies": { "@astrojs/starlight": ">=0.32.0" } }, "sha512-ysmOR7zaHYKtk18/mpW4MbEMDioR/ZBsisu9bdQrq0v9BlHWpW7gAdWlqFWO9zdv1P7l0Mo1WKd0wJ0UtqOVEQ=="],
|
||||||
|
|
||||||
|
"starlight-theme-next": ["starlight-theme-next@0.3.2", "", { "peerDependencies": { "@astrojs/starlight": ">=0.34" } }, "sha512-GQGhZ67nZ09pWVQoecl1N+H/1EUkUOvLVpjqOCHlkSotCblwrWrj4guEsdF9aKkNqiyTi6zzwZ5sxQospvdHOg=="],
|
||||||
|
|
||||||
"starlight-theme-rapide": ["starlight-theme-rapide@0.5.1", "", { "peerDependencies": { "@astrojs/starlight": ">=0.34.0" } }, "sha512-QRF6mzcYHLEX5UpUvOPXVVwISS298siIJLcKextoMLhXcnF12nX+IYJ0LNxFk9XaPbX9uDXIieSBJf5Pztkteg=="],
|
"starlight-theme-rapide": ["starlight-theme-rapide@0.5.1", "", { "peerDependencies": { "@astrojs/starlight": ">=0.34.0" } }, "sha512-QRF6mzcYHLEX5UpUvOPXVVwISS298siIJLcKextoMLhXcnF12nX+IYJ0LNxFk9XaPbX9uDXIieSBJf5Pztkteg=="],
|
||||||
|
|
||||||
"starlight-videos": ["starlight-videos@0.3.0", "", { "dependencies": { "@astro-community/astro-embed-youtube": "^0.5.6", "hastscript": "^9.0.0", "iso8601-duration": "^2.1.2", "srt-parser-2": "^1.2.3", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "@astrojs/starlight": ">=0.34.0" } }, "sha512-1yvFUEn3P+ZjuGr5COswQp14cZdIvsGjg9lqDIyW5clCrZaBiDMSNPLYngyQozaDbrublEp6/V9HbJR6sGnSOA=="],
|
"starlight-videos": ["starlight-videos@0.3.0", "", { "dependencies": { "@astro-community/astro-embed-youtube": "^0.5.6", "hastscript": "^9.0.0", "iso8601-duration": "^2.1.2", "srt-parser-2": "^1.2.3", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "@astrojs/starlight": ">=0.34.0" } }, "sha512-1yvFUEn3P+ZjuGr5COswQp14cZdIvsGjg9lqDIyW5clCrZaBiDMSNPLYngyQozaDbrublEp6/V9HbJR6sGnSOA=="],
|
||||||
|
|||||||
@ -20,8 +20,10 @@
|
|||||||
"sharp": "^0.34.3",
|
"sharp": "^0.34.3",
|
||||||
"starlight-openapi": "^0.19.1",
|
"starlight-openapi": "^0.19.1",
|
||||||
"starlight-sidebar-topics": "^0.6.0",
|
"starlight-sidebar-topics": "^0.6.0",
|
||||||
|
"starlight-theme-next": "^0.3.2",
|
||||||
"starlight-theme-rapide": "^0.5.1",
|
"starlight-theme-rapide": "^0.5.1",
|
||||||
"starlight-videos": "^0.3.0"
|
"starlight-videos": "^0.3.0",
|
||||||
|
"unist-util-visit": "^5.0.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22"
|
"packageManager": "yarn@1.22.22"
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
website/public/assets/images/changelog/changelog0.6.6.gif
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
website/public/assets/images/homepage/app-frame-dark-fixed.webp
Normal file
|
After Width: | Height: | Size: 488 KiB |
BIN
website/public/assets/images/homepage/app-frame-light-fixed.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
website/public/assets/images/homepage/app-frame-light-fixed.webp
Normal file
|
After Width: | Height: | Size: 477 KiB |
BIN
website/public/assets/images/homepage/assistant-dark.png
Normal file
|
After Width: | Height: | Size: 232 KiB |
BIN
website/public/assets/images/homepage/assistant-light.png
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
website/public/assets/images/homepage/extension-dark.png
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
website/public/assets/images/homepage/extension-light.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
website/public/assets/images/homepage/features01.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
website/public/assets/images/homepage/features01.webp
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
website/public/assets/images/homepage/features01dark.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
website/public/assets/images/homepage/features01dark.webp
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
website/public/assets/images/homepage/features02.png
Normal file
|
After Width: | Height: | Size: 368 KiB |
BIN
website/public/assets/images/homepage/features02.webp
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
website/public/assets/images/homepage/features02dark.png
Normal file
|
After Width: | Height: | Size: 395 KiB |
BIN
website/public/assets/images/homepage/features02dark.webp
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
website/public/assets/images/homepage/features03.png
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
website/public/assets/images/homepage/features03.webp
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
website/public/assets/images/homepage/features03dark.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
website/public/assets/images/homepage/features03dark.webp
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
website/public/assets/images/homepage/features04.png
Normal file
|
After Width: | Height: | Size: 325 KiB |
BIN
website/public/assets/images/homepage/features04.webp
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
website/public/assets/images/homepage/features04dark.png
Normal file
|
After Width: | Height: | Size: 322 KiB |
BIN
website/public/assets/images/homepage/features04dark.webp
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
website/public/assets/images/homepage/features05.png
Normal file
|
After Width: | Height: | Size: 333 KiB |
BIN
website/public/assets/images/homepage/features05.webp
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
website/public/assets/images/homepage/features05dark.png
Normal file
|
After Width: | Height: | Size: 350 KiB |
BIN
website/public/assets/images/homepage/features05dark.webp
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
website/public/assets/images/homepage/glow.png
Normal file
|
After Width: | Height: | Size: 7.4 MiB |
BIN
website/public/assets/images/homepage/icon.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
website/public/assets/images/homepage/lifehacker-dark.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
website/public/assets/images/homepage/lifehacker-light.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
10
website/public/assets/images/homepage/mac-system-black.svg
Normal file
|
After Width: | Height: | Size: 77 KiB |
10
website/public/assets/images/homepage/mac-system-white.svg
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
website/public/assets/images/homepage/mapbase-dark.png
Normal file
|
After Width: | Height: | Size: 387 KiB |
BIN
website/public/assets/images/homepage/mapbase-dark.webp
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
website/public/assets/images/homepage/mapbase-light.png
Normal file
|
After Width: | Height: | Size: 392 KiB |
BIN
website/public/assets/images/homepage/mapbase-light.webp
Normal file
|
After Width: | Height: | Size: 42 KiB |
140
website/scripts/fix-blog-images.js
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import { dirname } from 'path'
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
|
const blogDir = path.join(__dirname, '..', 'src', 'content', 'blog')
|
||||||
|
|
||||||
|
// Function to convert filename to a valid JavaScript variable name
|
||||||
|
function toVariableName(filename) {
|
||||||
|
// Remove extension and special characters, convert to camelCase
|
||||||
|
const base = path.basename(filename, path.extname(filename))
|
||||||
|
let varName = base
|
||||||
|
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
|
||||||
|
.replace(/[^a-zA-Z0-9]/g, '')
|
||||||
|
.replace(/^./, (c) => c.toLowerCase())
|
||||||
|
|
||||||
|
// If the variable name starts with a number, prefix with 'img'
|
||||||
|
if (/^[0-9]/.test(varName)) {
|
||||||
|
varName = 'img' + varName.charAt(0).toUpperCase() + varName.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return varName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to process a single MDX file
|
||||||
|
function processMDXFile(filePath) {
|
||||||
|
console.log(`Processing: ${filePath}`)
|
||||||
|
|
||||||
|
let content = fs.readFileSync(filePath, 'utf-8')
|
||||||
|
|
||||||
|
// Find all image references
|
||||||
|
const imageRegex = /!\[([^\]]*)\]\((\.\/_assets\/[^)]+)\)/g
|
||||||
|
const images = []
|
||||||
|
let match
|
||||||
|
|
||||||
|
while ((match = imageRegex.exec(content)) !== null) {
|
||||||
|
const altText = match[1]
|
||||||
|
const imagePath = match[2]
|
||||||
|
const filename = path.basename(imagePath)
|
||||||
|
const varName = toVariableName(filename) + 'Img'
|
||||||
|
|
||||||
|
// Check if we already have this image
|
||||||
|
if (!images.find((img) => img.varName === varName)) {
|
||||||
|
images.push({
|
||||||
|
varName,
|
||||||
|
path: imagePath,
|
||||||
|
altText,
|
||||||
|
originalMatch: match[0],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (images.length === 0) {
|
||||||
|
console.log(` No images found in ${path.basename(filePath)}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` Found ${images.length} images`)
|
||||||
|
|
||||||
|
// Find where to insert imports (after existing imports or frontmatter)
|
||||||
|
const frontmatterEnd = content.indexOf('---', content.indexOf('---') + 3) + 3
|
||||||
|
let importInsertPosition = frontmatterEnd
|
||||||
|
|
||||||
|
// Check if there are already imports
|
||||||
|
const existingImportRegex = /^import\s+.*$/gm
|
||||||
|
const imports = content.match(existingImportRegex)
|
||||||
|
|
||||||
|
if (imports && imports.length > 0) {
|
||||||
|
// Find the last import
|
||||||
|
const lastImport = imports[imports.length - 1]
|
||||||
|
importInsertPosition = content.indexOf(lastImport) + lastImport.length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate import statements
|
||||||
|
const importStatements = images
|
||||||
|
.map((img) => `import ${img.varName} from '${img.path}';`)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
// Insert imports
|
||||||
|
if (imports && imports.length > 0) {
|
||||||
|
// Add to existing imports
|
||||||
|
content =
|
||||||
|
content.slice(0, importInsertPosition) +
|
||||||
|
'\n' +
|
||||||
|
importStatements +
|
||||||
|
content.slice(importInsertPosition)
|
||||||
|
} else {
|
||||||
|
// Add new import section after frontmatter
|
||||||
|
content =
|
||||||
|
content.slice(0, frontmatterEnd) +
|
||||||
|
'\n\n' +
|
||||||
|
importStatements +
|
||||||
|
'\n' +
|
||||||
|
content.slice(frontmatterEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace all image references with JSX img tags
|
||||||
|
images.forEach((img) => {
|
||||||
|
// Create regex for this specific image
|
||||||
|
const specificImageRegex = new RegExp(
|
||||||
|
`!\\[([^\\]]*)\\]\\(${img.path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\)`,
|
||||||
|
'g'
|
||||||
|
)
|
||||||
|
|
||||||
|
content = content.replace(specificImageRegex, (match, altText) => {
|
||||||
|
return `<img src={${img.varName}.src} alt="${altText || img.altText}" />`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Write the updated content back
|
||||||
|
fs.writeFileSync(filePath, content)
|
||||||
|
console.log(` ✓ Updated ${path.basename(filePath)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process all MDX files in the blog directory
|
||||||
|
function processAllBlogPosts() {
|
||||||
|
const files = fs.readdirSync(blogDir)
|
||||||
|
const mdxFiles = files.filter((file) => file.endsWith('.mdx'))
|
||||||
|
|
||||||
|
console.log(`Found ${mdxFiles.length} MDX files in blog directory\n`)
|
||||||
|
|
||||||
|
mdxFiles.forEach((file) => {
|
||||||
|
const filePath = path.join(blogDir, file)
|
||||||
|
try {
|
||||||
|
processMDXFile(filePath)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error processing ${file}:`, error.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('\n✨ All blog posts processed!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the script
|
||||||
|
processAllBlogPosts()
|
||||||
BIN
website/src/assets/blog/3090s.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
website/src/assets/blog/4070s.jpg
Normal file
|
After Width: | Height: | Size: 885 KiB |
BIN
website/src/assets/blog/4090s.png
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
website/src/assets/blog/ai-locally-llama.cpp.jpg
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
website/src/assets/blog/catastrophic-demo.png
Normal file
|
After Width: | Height: | Size: 742 KiB |
BIN
website/src/assets/blog/chat-with-docs-prompt.jpg
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
website/src/assets/blog/chat-with-your-docs-offline-ai.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
website/src/assets/blog/chat-with-your-docs2.jpg
Normal file
|
After Width: | Height: | Size: 415 KiB |
BIN
website/src/assets/blog/deepseek-r1-locally-jan.jpg
Normal file
|
After Width: | Height: | Size: 725 KiB |
BIN
website/src/assets/blog/download-jan.jpg
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
website/src/assets/blog/egpu.jpg
Normal file
|
After Width: | Height: | Size: 795 KiB |
BIN
website/src/assets/blog/gradient-decent.gif
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
website/src/assets/blog/hugging-face-jan-model-download.jpg
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
website/src/assets/blog/jan-hf-model-download.jpg
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
website/src/assets/blog/jan-hub-deepseek-r1.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
website/src/assets/blog/jan-hub-download-deepseek-r1-2.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
website/src/assets/blog/jan-hub-download-deepseek-r1.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
website/src/assets/blog/jan-hub-for-ai-models.jpg
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
website/src/assets/blog/jan-library-deepseek-r1.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
website/src/assets/blog/jan-local-ai.jpg
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
website/src/assets/blog/jan-model-download.jpg
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
website/src/assets/blog/jan-model-selection.jpg
Normal file
|
After Width: | Height: | Size: 586 KiB |
BIN
website/src/assets/blog/jan-runs-deepseek-r1-distills.jpg
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
website/src/assets/blog/jan-system-prompt-deepseek-r1.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
website/src/assets/blog/jan.ai.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
website/src/assets/blog/local-ai-model-parameters.jpg
Normal file
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 899 KiB |
BIN
website/src/assets/blog/offline-chatgpt-alternatives-jan.jpg
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
website/src/assets/blog/og-4090s.webp
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
website/src/assets/blog/open-source-ai-quantization.jpg
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
website/src/assets/blog/openchat-bench-0106.png
Normal file
|
After Width: | Height: | Size: 453 KiB |
BIN
website/src/assets/blog/qwen3-in-jan-hub.jpeg
Normal file
|
After Width: | Height: | Size: 231 KiB |
BIN
website/src/assets/blog/qwen3-settings-in-jan.jpeg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
website/src/assets/blog/qwen3-settings-jan-ai.jpeg
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
website/src/assets/blog/replay.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
website/src/assets/blog/run-ai-locally-with-jan.jpg
Normal file
|
After Width: | Height: | Size: 673 KiB |
BIN
website/src/assets/blog/run-deepseek-r1-locally-in-jan.jpg
Normal file
|
After Width: | Height: | Size: 374 KiB |
BIN
website/src/assets/blog/throughput_Comparison.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
website/src/assets/tom_gauld.png
Normal file
|
After Width: | Height: | Size: 552 KiB |
230
website/src/components/Blog/BlogImage.astro
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
src: string;
|
||||||
|
alt: string;
|
||||||
|
caption?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
loading?: 'lazy' | 'eager';
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
src,
|
||||||
|
alt,
|
||||||
|
caption,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
loading = 'lazy',
|
||||||
|
class: className = ''
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
|
// Handle different image path formats
|
||||||
|
let imageSrc = src;
|
||||||
|
|
||||||
|
// If the path starts with ./ or ../, it's a relative path from the MDX file
|
||||||
|
if (src.startsWith('./') || src.startsWith('../')) {
|
||||||
|
// Remove the leading ./ or ../
|
||||||
|
imageSrc = src.replace(/^\.\.?\//, '');
|
||||||
|
|
||||||
|
// Prepend the blog content path if it doesn't include it
|
||||||
|
if (!imageSrc.includes('/content/blog/')) {
|
||||||
|
imageSrc = `/src/content/blog/${imageSrc}`;
|
||||||
|
}
|
||||||
|
} else if (!src.startsWith('http') && !src.startsWith('/')) {
|
||||||
|
// For paths without ./ prefix, assume they're relative to blog content
|
||||||
|
imageSrc = `/src/content/blog/${src}`;
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<figure class={`blog-image-container ${className}`}>
|
||||||
|
<img
|
||||||
|
src={imageSrc}
|
||||||
|
alt={alt}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
loading={loading}
|
||||||
|
class="blog-image"
|
||||||
|
/>
|
||||||
|
{caption && (
|
||||||
|
<figcaption class="blog-image-caption">{caption}</figcaption>
|
||||||
|
)}
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.blog-image-container {
|
||||||
|
margin: 2rem 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-image {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow:
|
||||||
|
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||||
|
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
|
border: 1px solid var(--sl-color-gray-5);
|
||||||
|
background: var(--sl-color-bg);
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-image:hover {
|
||||||
|
transform: scale(1.01);
|
||||||
|
box-shadow:
|
||||||
|
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||||
|
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-image-caption {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--sl-color-text-muted);
|
||||||
|
font-style: italic;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading state */
|
||||||
|
.blog-image[loading="lazy"] {
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
var(--sl-color-gray-5) 0%,
|
||||||
|
var(--sl-color-gray-4) 50%,
|
||||||
|
var(--sl-color-gray-5) 100%
|
||||||
|
);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: shimmer 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -200% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode adjustments */
|
||||||
|
:global(.dark) .blog-image {
|
||||||
|
box-shadow:
|
||||||
|
0 4px 6px -1px rgba(0, 0, 0, 0.3),
|
||||||
|
0 2px 4px -1px rgba(0, 0, 0, 0.2);
|
||||||
|
border-color: var(--sl-color-gray-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .blog-image:hover {
|
||||||
|
box-shadow:
|
||||||
|
0 10px 15px -3px rgba(0, 0, 0, 0.4),
|
||||||
|
0 4px 6px -2px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.blog-image-container {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-image {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-image-caption {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.blog-image-container {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-image {
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Full-width variant */
|
||||||
|
.blog-image-container.full-width {
|
||||||
|
margin-left: calc(-50vw + 50%);
|
||||||
|
margin-right: calc(-50vw + 50%);
|
||||||
|
max-width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-image-container.full-width .blog-image {
|
||||||
|
border-radius: 0;
|
||||||
|
max-width: min(100%, 1400px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline variant for small icons/images */
|
||||||
|
.blog-image-container.inline {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 0.25rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-image-container.inline .blog-image {
|
||||||
|
display: inline-block;
|
||||||
|
max-height: 1.5em;
|
||||||
|
width: auto;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-image-container.inline .blog-image:hover {
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gallery variant */
|
||||||
|
.blog-image-container.gallery {
|
||||||
|
display: inline-block;
|
||||||
|
width: calc(50% - 0.5rem);
|
||||||
|
margin: 0.5rem 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.blog-image-container.gallery {
|
||||||
|
width: 100%;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error state */
|
||||||
|
.blog-image.error {
|
||||||
|
background: var(--sl-color-gray-5);
|
||||||
|
min-height: 200px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-image.error::after {
|
||||||
|
content: '🖼️ Image not found';
|
||||||
|
position: absolute;
|
||||||
|
color: var(--sl-color-text-muted);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Handle image loading errors
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const images = document.querySelectorAll('.blog-image');
|
||||||
|
|
||||||
|
images.forEach(img => {
|
||||||
|
img.addEventListener('error', function() {
|
||||||
|
this.classList.add('error');
|
||||||
|
this.alt = 'Image could not be loaded';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
87
website/src/components/Blog/CTABlog.astro
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
buttonText?: string;
|
||||||
|
buttonLink?: string;
|
||||||
|
variant?: 'primary' | 'secondary' | 'gradient';
|
||||||
|
align?: 'left' | 'center' | 'right';
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
title = "Ready to get started?",
|
||||||
|
description = "Download Jan and start running AI models locally on your device.",
|
||||||
|
buttonText = "Download Jan",
|
||||||
|
buttonLink = "https://jan.ai",
|
||||||
|
variant = 'primary',
|
||||||
|
align = 'center'
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
|
const variantClasses = {
|
||||||
|
primary: 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800',
|
||||||
|
secondary: 'bg-gray-50 dark:bg-gray-900/20 border-gray-200 dark:border-gray-800',
|
||||||
|
gradient: 'bg-gradient-to-r from-blue-50 to-purple-50 dark:from-blue-900/20 dark:to-purple-900/20 border-purple-200 dark:border-purple-800'
|
||||||
|
};
|
||||||
|
|
||||||
|
const alignClasses = {
|
||||||
|
left: 'text-left',
|
||||||
|
center: 'text-center',
|
||||||
|
right: 'text-right'
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonVariantClasses = {
|
||||||
|
primary: 'bg-blue-600 hover:bg-blue-700 text-white',
|
||||||
|
secondary: 'bg-gray-800 hover:bg-gray-900 dark:bg-gray-200 dark:hover:bg-gray-300 text-white dark:text-gray-900',
|
||||||
|
gradient: 'bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white'
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class={`cta-blog ${variantClasses[variant]} ${alignClasses[align]} border rounded-xl p-6 lg:p-8 my-8`}>
|
||||||
|
<div class="max-w-2xl mx-auto">
|
||||||
|
{title && (
|
||||||
|
<h3 class="text-2xl lg:text-3xl font-bold mb-3 text-gray-900 dark:text-gray-100">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{description && (
|
||||||
|
<p class="text-gray-700 dark:text-gray-300 mb-6 text-lg">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={buttonLink}
|
||||||
|
class={`inline-flex items-center gap-2 px-6 py-3 rounded-lg font-medium transition-all duration-200 ${buttonVariantClasses[variant]} shadow-sm hover:shadow-md`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{buttonText}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cta-blog {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-blog::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
right: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .cta-blog::before {
|
||||||
|
background: radial-gradient(circle, rgba(255,255,255,0.05) 0%, transparent 70%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
85
website/src/components/Callout.astro
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
type?: 'info' | 'warning' | 'error' | 'success' | 'note';
|
||||||
|
emoji?: string;
|
||||||
|
children?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type = 'note', emoji } = Astro.props;
|
||||||
|
|
||||||
|
const typeConfig = {
|
||||||
|
info: {
|
||||||
|
bgColor: 'bg-blue-50 dark:bg-blue-900/20',
|
||||||
|
borderColor: 'border-blue-200 dark:border-blue-800',
|
||||||
|
textColor: 'text-blue-900 dark:text-blue-200',
|
||||||
|
defaultEmoji: 'ℹ️'
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
bgColor: 'bg-yellow-50 dark:bg-yellow-900/20',
|
||||||
|
borderColor: 'border-yellow-200 dark:border-yellow-800',
|
||||||
|
textColor: 'text-yellow-900 dark:text-yellow-200',
|
||||||
|
defaultEmoji: '⚠️'
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
bgColor: 'bg-red-50 dark:bg-red-900/20',
|
||||||
|
borderColor: 'border-red-200 dark:border-red-800',
|
||||||
|
textColor: 'text-red-900 dark:text-red-200',
|
||||||
|
defaultEmoji: '🚨'
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
bgColor: 'bg-green-50 dark:bg-green-900/20',
|
||||||
|
borderColor: 'border-green-200 dark:border-green-800',
|
||||||
|
textColor: 'text-green-900 dark:text-green-200',
|
||||||
|
defaultEmoji: '✅'
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
bgColor: 'bg-gray-50 dark:bg-gray-900/20',
|
||||||
|
borderColor: 'border-gray-200 dark:border-gray-800',
|
||||||
|
textColor: 'text-gray-900 dark:text-gray-200',
|
||||||
|
defaultEmoji: '📝'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = typeConfig[type] || typeConfig.note;
|
||||||
|
const displayEmoji = emoji || config.defaultEmoji;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class={`callout ${config.bgColor} ${config.borderColor} ${config.textColor} border rounded-lg p-4 my-4`}>
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<span class="text-xl flex-shrink-0" aria-hidden="true">{displayEmoji}</span>
|
||||||
|
<div class="callout-content flex-1">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.callout {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout-content :global(p) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout-content :global(p:not(:last-child)) {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout-content :global(a) {
|
||||||
|
text-decoration: underline;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout-content :global(code) {
|
||||||
|
font-size: 0.875em;
|
||||||
|
padding: 0.125rem 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .callout-content :global(code) {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
36
website/src/components/Changelog/ChangelogHeader.astro
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
title: string;
|
||||||
|
date: string;
|
||||||
|
ogImage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, date, ogImage } = Astro.props;
|
||||||
|
|
||||||
|
// Format the date nicely
|
||||||
|
const formattedDate = new Date(date).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
---
|
||||||
|
|
||||||
|
<header class="changelog-header mb-8">
|
||||||
|
{ogImage && (
|
||||||
|
<div class="mt-6">
|
||||||
|
<img
|
||||||
|
src={ogImage}
|
||||||
|
alt={title}
|
||||||
|
class="w-full rounded-lg shadow-lg"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.changelog-header {
|
||||||
|
border-bottom: 1px solid var(--sl-color-gray-5);
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -4,6 +4,17 @@
|
|||||||
import Search from '@astrojs/starlight/components/Search.astro';
|
import Search from '@astrojs/starlight/components/Search.astro';
|
||||||
import ThemeSelect from '@astrojs/starlight/components/ThemeSelect.astro';
|
import ThemeSelect from '@astrojs/starlight/components/ThemeSelect.astro';
|
||||||
import { Icon } from '@astrojs/starlight/components';
|
import { Icon } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
// Determine if we're on a docs page based on the current path
|
||||||
|
const currentPath = Astro.url.pathname;
|
||||||
|
const isDocsPage = currentPath.startsWith('/jan/') ||
|
||||||
|
currentPath.startsWith('/mobile/') ||
|
||||||
|
currentPath.startsWith('/server/') ||
|
||||||
|
currentPath.startsWith('/local-server/') ||
|
||||||
|
currentPath === '/' ||
|
||||||
|
currentPath === '/index' ||
|
||||||
|
currentPath === '/docs' ||
|
||||||
|
currentPath === '/docs/';
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="sl-nav-wrapper">
|
<div class="sl-nav-wrapper">
|
||||||
@ -11,18 +22,24 @@ import { Icon } from '@astrojs/starlight/components';
|
|||||||
<!-- Left side with title and links -->
|
<!-- Left side with title and links -->
|
||||||
<div class="sl-nav__left">
|
<div class="sl-nav__left">
|
||||||
<!-- Site title/logo -->
|
<!-- Site title/logo -->
|
||||||
<a href="/" class="sl-nav__title">
|
<a href="https://jan.ai" class="sl-nav__title">
|
||||||
👋 Jan
|
👋 Jan
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Main navigation links -->
|
<!-- Main navigation links - hide on mobile for docs pages -->
|
||||||
<div class="sl-nav__links">
|
<div class={`sl-nav__links ${isDocsPage ? 'docs-page' : 'custom-page'}`}>
|
||||||
<a href="/products" class="sl-nav__link">
|
<a href="/products" class="sl-nav__link" data-nav-item="products">
|
||||||
Products
|
Products
|
||||||
</a>
|
</a>
|
||||||
<a href="/docs/" class="sl-nav__link">
|
<a href="/jan" class="sl-nav__link" data-nav-item="docs">
|
||||||
Docs
|
Docs
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/handbook" class="sl-nav__link" data-nav-item="handbook">
|
||||||
|
Handbook
|
||||||
|
</a>
|
||||||
|
<a href="/blog" class="sl-nav__link" data-nav-item="blog">
|
||||||
|
Blog
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -33,82 +50,113 @@ import { Icon } from '@astrojs/starlight/components';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right side items (API Reference, theme toggle, social links, etc.) -->
|
<!-- Right side items -->
|
||||||
<div class="sl-nav__end">
|
<div class="sl-nav__end">
|
||||||
<!-- API Reference moved to right -->
|
<!-- Changelog and API Reference links -->
|
||||||
<a href="/api-reference" class="sl-nav__link">
|
<a href="/changelog" class={`sl-nav__link sl-nav__api-link ${isDocsPage ? 'docs-page' : 'custom-page'}`} data-nav-item="changelog">
|
||||||
|
Changelog
|
||||||
|
</a>
|
||||||
|
<a href="/api-reference" class={`sl-nav__link sl-nav__api-link ${isDocsPage ? 'docs-page' : 'custom-page'}`} data-nav-item="api">
|
||||||
API Reference
|
API Reference
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Theme toggle -->
|
<!-- Theme toggle - always visible on desktop -->
|
||||||
<ThemeSelect />
|
<div class="sl-nav__theme">
|
||||||
|
<ThemeSelect />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Social links -->
|
<!-- Social links -->
|
||||||
<div class="sl-nav__social">
|
<div class="sl-nav__social">
|
||||||
<a href="https://github.com/menloresearch/jan" class="sl-nav__social-link" aria-label="GitHub">
|
<a href="https://github.com/menloresearch/jan" class="sl-nav__social-link" aria-label="GitHub">
|
||||||
<Icon name="github"/>
|
<Icon name="github"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
<div class="sl-nav__social">
|
|
||||||
<a href="https://twitter.com/jandotai" class="sl-nav__social-link" aria-label="X">
|
<a href="https://twitter.com/jandotai" class="sl-nav__social-link" aria-label="X">
|
||||||
<Icon name="x.com"/>
|
<Icon name="x.com"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
<div class="sl-nav__social">
|
|
||||||
<a href="https://discord.com/invite/FTk2MvZwJH" class="sl-nav__social-link" aria-label="Discord">
|
<a href="https://discord.com/invite/FTk2MvZwJH" class="sl-nav__social-link" aria-label="Discord">
|
||||||
<Icon name="discord"/>
|
<Icon name="discord"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile hamburger menu - only for custom pages -->
|
||||||
|
{!isDocsPage && (
|
||||||
|
<div class="sl-nav__hamburger">
|
||||||
|
<button id="hamburger-btn" class="hamburger-button" aria-label="Toggle navigation menu">
|
||||||
|
<span class="hamburger-icon">☰</span>
|
||||||
|
</button>
|
||||||
|
<div id="hamburger-menu" class="hamburger-menu">
|
||||||
|
<a href="/products" class="hamburger-link">Products</a>
|
||||||
|
<a href="/jan" class="hamburger-link">Docs</a>
|
||||||
|
<a href="/handbook" class="hamburger-link">Handbook</a>
|
||||||
|
<a href="/blog" class="hamburger-link">Blog</a>
|
||||||
|
<a href="/changelog" class="hamburger-link">Changelog</a>
|
||||||
|
<a href="/api-reference" class="hamburger-link">API Reference</a>
|
||||||
|
<div class="hamburger-social">
|
||||||
|
<a href="https://github.com/menloresearch/jan" class="hamburger-social-link" aria-label="GitHub">
|
||||||
|
<Icon name="github"/>
|
||||||
|
</a>
|
||||||
|
<a href="https://twitter.com/jandotai" class="hamburger-social-link" aria-label="X">
|
||||||
|
<Icon name="x.com"/>
|
||||||
|
</a>
|
||||||
|
<a href="https://discord.com/invite/FTk2MvZwJH" class="hamburger-social-link" aria-label="Discord">
|
||||||
|
<Icon name="discord"/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="hamburger-theme">
|
||||||
|
<ThemeSelect />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/* Base navigation styles */
|
||||||
.sl-nav-wrapper {
|
.sl-nav-wrapper {
|
||||||
background: var(--sl-color-bg-nav);
|
position: sticky;
|
||||||
border-bottom: 1px solid var(--sl-color-hairline-shade);
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
z-index: 999; /* High z-index to stay above ToC and other elements */
|
||||||
right: 0;
|
background: var(--sl-color-bg-nav);
|
||||||
z-index: 1000;
|
border-bottom: 1px solid var(--sl-color-hairline);
|
||||||
padding: 0;
|
backdrop-filter: blur(10px);
|
||||||
margin: 0;
|
-webkit-backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav {
|
.sl-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
width: 100%;
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
height: var(--sl-nav-height, 3.5rem);
|
height: var(--sl-nav-height, 3.5rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__center {
|
.sl-nav__center {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
max-width: 500px;
|
max-width: 600px;
|
||||||
margin: 0 2rem;
|
margin: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__left {
|
.sl-nav__left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__title {
|
.sl-nav__title {
|
||||||
color: var(--sl-color-text);
|
font-size: 1.125rem;
|
||||||
text-decoration: none;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1;
|
color: var(--sl-color-white);
|
||||||
margin: 0;
|
text-decoration: none;
|
||||||
padding: 0;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__title:hover {
|
.sl-nav__title:hover {
|
||||||
@ -118,40 +166,44 @@ import { Icon } from '@astrojs/starlight/components';
|
|||||||
.sl-nav__links {
|
.sl-nav__links {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 1.5rem;
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__link {
|
.sl-nav__link {
|
||||||
color: var(--sl-color-text);
|
color: var(--sl-color-gray-3);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9375rem;
|
||||||
font-weight: 500;
|
transition: color 0.2s ease;
|
||||||
padding: 0.5rem 0.75rem;
|
white-space: nowrap;
|
||||||
border-radius: 0.375rem;
|
|
||||||
transition: all 0.15s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__link:hover {
|
.sl-nav__link:hover {
|
||||||
color: #000 !important;
|
color: var(--sl-color-white);
|
||||||
background: var(--sl-color-bg-accent);
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode hover text should be white for contrast */
|
/* Light theme adjustments - better contrast */
|
||||||
html[data-theme="dark"] .sl-nav__link:hover {
|
html[data-theme="light"] .sl-nav__link {
|
||||||
color: #fff !important;
|
color: #374151; /* Darker gray for better readability */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Active state styling for better contrast */
|
html[data-theme="light"] .sl-nav__link:hover {
|
||||||
|
color: var(--sl-color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="light"] .sl-nav__title {
|
||||||
|
color: #111827; /* Very dark gray, almost black */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active link styling */
|
||||||
.sl-nav__link.active {
|
.sl-nav__link.active {
|
||||||
color: #000 !important;
|
color: var(--sl-color-text-accent);
|
||||||
background: var(--sl-color-bg-accent) !important;
|
font-weight: 500;
|
||||||
font-weight: 600 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode active state */
|
/* Light theme active link */
|
||||||
html[data-theme="dark"] .sl-nav__link.active {
|
html[data-theme="light"] .sl-nav__link.active {
|
||||||
color: #fff !important;
|
color: var(--sl-color-text-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__end {
|
.sl-nav__end {
|
||||||
@ -161,16 +213,39 @@ import { Icon } from '@astrojs/starlight/components';
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__search {
|
.sl-nav__search {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 300px;
|
max-width: 400px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make the actual search input wider */
|
/* Fix search input styling for light mode */
|
||||||
.sl-nav__search :global(input) {
|
.sl-nav__search :global(input) {
|
||||||
min-width: 300px !important;
|
width: 100%;
|
||||||
width: 100% !important;
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix search bar in light mode with more specific selectors */
|
||||||
|
html[data-theme="light"] .sl-nav__search :global(.pagefind-ui__search-input),
|
||||||
|
html[data-theme="light"] .sl-nav__search :global(input[type="search"]),
|
||||||
|
html[data-theme="light"] .sl-nav__search :global(input) {
|
||||||
|
background: #f9fafb !important;
|
||||||
|
background-color: #f9fafb !important;
|
||||||
|
border: 1px solid #d1d5db !important;
|
||||||
|
color: #111827 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="light"] .sl-nav__search :global(.pagefind-ui__search-input:focus),
|
||||||
|
html[data-theme="light"] .sl-nav__search :global(input[type="search"]:focus),
|
||||||
|
html[data-theme="light"] .sl-nav__search :global(input:focus) {
|
||||||
|
background: #ffffff !important;
|
||||||
|
background-color: #ffffff !important;
|
||||||
|
border-color: var(--sl-color-accent) !important;
|
||||||
|
color: #111827 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="light"] .sl-nav__search :global(.pagefind-ui__search-input::placeholder),
|
||||||
|
html[data-theme="light"] .sl-nav__search :global(input::placeholder) {
|
||||||
|
color: #9ca3af !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__social {
|
.sl-nav__social {
|
||||||
@ -180,143 +255,407 @@ import { Icon } from '@astrojs/starlight/components';
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__social-link {
|
.sl-nav__social-link {
|
||||||
color: var(--sl-color-text-muted);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
padding: 0.375rem;
|
||||||
width: 2rem;
|
color: var(--sl-color-gray-3);
|
||||||
height: 2rem;
|
transition: color 0.2s ease;
|
||||||
border-radius: 0.375rem;
|
text-decoration: none;
|
||||||
transition: all 0.15s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__social-link:hover {
|
.sl-nav__social-link:hover {
|
||||||
color: var(--sl-color-text);
|
color: var(--sl-color-white);
|
||||||
background: var(--sl-color-bg-accent);
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide links on mobile, show hamburger menu instead */
|
/* Light theme social links - better contrast */
|
||||||
@media (max-width: 768px) {
|
html[data-theme="light"] .sl-nav__social-link {
|
||||||
|
color: #6b7280; /* Medium gray for icons */
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="light"] .sl-nav__social-link:hover {
|
||||||
|
color: var(--sl-color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme selector container */
|
||||||
|
.sl-nav__theme {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide only the "Select theme" label text, keep dropdown visible */
|
||||||
|
.sl-nav__theme :global(starlight-theme-select) {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide only the screen reader text */
|
||||||
|
.sl-nav__theme :global(starlight-theme-select .sr-only) {
|
||||||
|
position: absolute !important;
|
||||||
|
width: 1px !important;
|
||||||
|
height: 1px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: -1px !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
clip: rect(0, 0, 0, 0) !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the select dropdown with visible text */
|
||||||
|
.sl-nav__theme :global(starlight-theme-select select) {
|
||||||
|
font-size: 0.875rem !important;
|
||||||
|
color: var(--sl-color-text) !important;
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
padding-right: 1.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure options are visible with proper contrast */
|
||||||
|
.sl-nav__theme :global(starlight-theme-select option) {
|
||||||
|
font-size: 0.875rem !important;
|
||||||
|
color: var(--sl-color-black) !important;
|
||||||
|
background: var(--sl-color-white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light mode select text color */
|
||||||
|
html[data-theme="light"] .sl-nav__theme :global(starlight-theme-select select) {
|
||||||
|
color: var(--sl-color-gray-5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the icon to prevent overlap with text */
|
||||||
|
.sl-nav__theme :global(starlight-theme-select svg) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hamburger menu styles */
|
||||||
|
.sl-nav__hamburger {
|
||||||
|
display: none;
|
||||||
|
position: relative; /* Changed from static to contain the menu properly */
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-button {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--sl-color-white);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.375rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix hamburger button visibility in light mode */
|
||||||
|
html[data-theme="light"] .hamburger-button {
|
||||||
|
color: #374151; /* Darker gray for better visibility */
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-button:hover {
|
||||||
|
color: var(--sl-color-text-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="light"] .hamburger-button:hover {
|
||||||
|
color: var(--sl-color-text-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-menu {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 0.5rem); /* Add small gap below navbar */
|
||||||
|
right: 0;
|
||||||
|
background: var(--sl-color-bg-nav);
|
||||||
|
border: 1px solid var(--sl-color-hairline);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
min-width: 200px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 1000; /* Ensure menu stays above other content */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light mode menu background */
|
||||||
|
html[data-theme="light"] .hamburger-menu {
|
||||||
|
background: var(--sl-color-bg);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-menu.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-link {
|
||||||
|
display: block;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
color: var(--sl-color-gray-3);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease, background 0.2s ease;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light mode hamburger links */
|
||||||
|
html[data-theme="light"] .hamburger-link {
|
||||||
|
color: #374151; /* Consistent with nav links */
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-link:hover {
|
||||||
|
color: var(--sl-color-white);
|
||||||
|
background: var(--sl-color-gray-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="light"] .hamburger-link:hover {
|
||||||
|
color: var(--sl-color-accent);
|
||||||
|
background: var(--sl-color-gray-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-social {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
border-top: 1px solid var(--sl-color-hairline);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-social-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.375rem;
|
||||||
|
color: var(--sl-color-gray-3);
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="light"] .hamburger-social-link {
|
||||||
|
color: #6b7280; /* Consistent with nav social links */
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-social-link:hover {
|
||||||
|
color: var(--sl-color-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="light"] .hamburger-social-link:hover {
|
||||||
|
color: var(--sl-color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-theme {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
border-top: 1px solid var(--sl-color-hairline);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search component integration styles */
|
||||||
|
.sl-nav__search :global(.pagefind-ui) {
|
||||||
|
--pagefind-ui-scale: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sl-nav__search :global(.pagefind-ui__search-input) {
|
||||||
|
background: var(--sl-color-gray-6);
|
||||||
|
border: 1px solid var(--sl-color-gray-5);
|
||||||
|
color: var(--sl-color-white);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0.375rem 1rem;
|
||||||
|
transition: border-color 0.2s ease, background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override any Starlight defaults for search in dark mode */
|
||||||
|
html[data-theme="dark"] .sl-nav__search :global(.pagefind-ui__search-input),
|
||||||
|
html[data-theme="dark"] .sl-nav__search :global(input[type="search"]),
|
||||||
|
html[data-theme="dark"] .sl-nav__search :global(input) {
|
||||||
|
background: #1f2937 !important;
|
||||||
|
background-color: #1f2937 !important;
|
||||||
|
border: 1px solid #374151 !important;
|
||||||
|
color: #f3f4f6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sl-nav__search :global(.pagefind-ui__search-input:hover) {
|
||||||
|
border-color: var(--sl-color-gray-4);
|
||||||
|
background: var(--sl-color-gray-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sl-nav__search :global(.pagefind-ui__search-input:focus) {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--sl-color-text-accent);
|
||||||
|
background: var(--sl-color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sl-nav__search :global(.pagefind-ui__search-input::placeholder) {
|
||||||
|
color: var(--sl-color-gray-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive styles */
|
||||||
|
|
||||||
|
/* Tablet view */
|
||||||
|
@media (max-width: 900px) {
|
||||||
.sl-nav__links {
|
.sl-nav__links {
|
||||||
display: none;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__left {
|
.sl-nav__link {
|
||||||
gap: 0.25rem;
|
font-size: 0.875rem;
|
||||||
flex-shrink: 0;
|
}
|
||||||
|
|
||||||
|
.sl-nav__social {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile view - differentiate between docs and custom pages */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sl-nav {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__center {
|
.sl-nav__center {
|
||||||
margin: 0 0.5rem;
|
margin: 0 0.5rem;
|
||||||
max-width: 200px;
|
|
||||||
flex-shrink: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__end {
|
/* For docs pages - hide custom nav items on mobile, let Starlight handle it */
|
||||||
gap: 0.25rem;
|
.sl-nav__links.docs-page {
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sl-nav__search {
|
|
||||||
min-width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sl-nav__search :global(input) {
|
|
||||||
min-width: 150px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide API Reference on very small screens */
|
|
||||||
.sl-nav__end > .sl-nav__link {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make social links smaller on mobile */
|
.sl-nav__api-link.docs-page {
|
||||||
.sl-nav__social-link {
|
display: none;
|
||||||
width: 1.5rem;
|
}
|
||||||
height: 1.5rem;
|
|
||||||
|
/* For custom pages - hide nav items but show hamburger */
|
||||||
|
.sl-nav__links.custom-page {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sl-nav__api-link.custom-page {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sl-nav__theme {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show hamburger only for custom pages */
|
||||||
|
.sl-nav__hamburger {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive adjustments */
|
/* Small mobile */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.sl-nav {
|
.sl-nav {
|
||||||
padding: 0.5rem 0.75rem;
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sl-nav__left {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sl-nav__center {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sl-nav__end {
|
||||||
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__title {
|
.sl-nav__title {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sl-nav__left {
|
.sl-nav__search :global(.pagefind-ui__search-input) {
|
||||||
gap: 0.125rem;
|
padding: 0.25rem 0.75rem;
|
||||||
}
|
font-size: 0.8125rem;
|
||||||
|
|
||||||
.sl-nav__center {
|
|
||||||
margin: 0 0.25rem;
|
|
||||||
max-width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sl-nav__search {
|
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sl-nav__search :global(input) {
|
|
||||||
min-width: 120px !important;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sl-nav__end {
|
|
||||||
gap: 0.125rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
min-width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide some social links on very small screens to make room */
|
|
||||||
.sl-nav__social:nth-child(n+2) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide theme toggle on very small screens */
|
|
||||||
:global(starlight-theme-select) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Make sure content doesn't jump */
|
||||||
/* Ensure page content doesn't hide behind fixed navbar */
|
|
||||||
:global(body) {
|
:global(body) {
|
||||||
padding-top: var(--sl-nav-height, 3.5rem) !important;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Override any conflicting Starlight body padding */
|
|
||||||
:global(.sl-layout) {
|
:global(.sl-layout) {
|
||||||
padding-top: 0 !important;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reduce top margin on main content area */
|
/* Adjust main content positioning */
|
||||||
:global(main.sl-content) {
|
:global(main.sl-content) {
|
||||||
margin-top: 0 !important;
|
margin-top: 0;
|
||||||
padding-top: 0.5rem !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Target the main content wrapper */
|
|
||||||
:global(.sl-content__inner) {
|
:global(.sl-content__inner) {
|
||||||
padding-top: 0 !important;
|
padding-top: 1rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Add active state highlighting based on current page
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const currentPath = window.location.pathname;
|
// Add active state highlighting based on current page
|
||||||
const links = document.querySelectorAll('.sl-nav__link');
|
const currentPath = window.location.pathname;
|
||||||
|
const links = document.querySelectorAll('.sl-nav__link');
|
||||||
|
|
||||||
links.forEach(link => {
|
links.forEach(link => {
|
||||||
const href = link.getAttribute('href');
|
const href = link.getAttribute('href');
|
||||||
if (href && currentPath.startsWith(href)) {
|
if (href) {
|
||||||
// Add active class for CSS styling
|
// Check for exact match or if current path starts with the link href
|
||||||
link.classList.add('active');
|
if (currentPath === href ||
|
||||||
|
(href !== '/' && currentPath.startsWith(href))) {
|
||||||
|
link.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Theme handling for custom pages
|
||||||
|
// Listen for theme changes from Starlight's ThemeSelect
|
||||||
|
const observeThemeChanges = () => {
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') {
|
||||||
|
const newTheme = document.documentElement.getAttribute('data-theme');
|
||||||
|
// Theme is already set by Starlight, we just need to ensure it persists
|
||||||
|
localStorage.setItem('starlight-theme', newTheme === 'dark' ? 'dark' : 'light');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['data-theme']
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
observeThemeChanges();
|
||||||
|
|
||||||
|
// Hamburger menu functionality (only for custom pages)
|
||||||
|
const hamburgerBtn = document.getElementById('hamburger-btn');
|
||||||
|
const hamburgerMenu = document.getElementById('hamburger-menu');
|
||||||
|
|
||||||
|
if (hamburgerBtn && hamburgerMenu) {
|
||||||
|
// Toggle menu on button click
|
||||||
|
hamburgerBtn.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
hamburgerMenu.classList.toggle('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close menu when clicking outside
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (!hamburgerBtn.contains(e.target) && !hamburgerMenu.contains(e.target)) {
|
||||||
|
hamburgerMenu.classList.remove('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close menu when clicking a link
|
||||||
|
const hamburgerLinks = hamburgerMenu.querySelectorAll('.hamburger-link');
|
||||||
|
hamburgerLinks.forEach(link => {
|
||||||
|
link.addEventListener('click', function() {
|
||||||
|
hamburgerMenu.classList.remove('show');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close menu on escape key
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape' && hamburgerMenu.classList.contains('show')) {
|
||||||
|
hamburgerMenu.classList.remove('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
233
website/src/components/DownloadButton.astro
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
class?: string;
|
||||||
|
showStats?: boolean;
|
||||||
|
downloadCount?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { class: className, showStats = false, downloadCount = '3.8M+' } = Astro.props;
|
||||||
|
|
||||||
|
// Download links for different platforms
|
||||||
|
const downloadLinks = {
|
||||||
|
'mac-intel': 'https://github.com/janhq/jan/releases/download/v0.5.14/jan-mac-x64-0.5.14.dmg',
|
||||||
|
'mac-arm': 'https://github.com/janhq/jan/releases/download/v0.5.14/jan-mac-arm64-0.5.14.dmg',
|
||||||
|
'windows': 'https://github.com/janhq/jan/releases/download/v0.5.14/jan-win-x64-0.5.14.exe',
|
||||||
|
'linux-deb': 'https://github.com/janhq/jan/releases/download/v0.5.14/jan-linux-amd64-0.5.14.deb',
|
||||||
|
'linux-appimage': 'https://github.com/janhq/jan/releases/download/v0.5.14/jan-linux-x86_64-0.5.14.AppImage'
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class={`download-container ${className || ''}`}>
|
||||||
|
<div class="download-section inline-flex items-center gap-2">
|
||||||
|
<!-- Main Download Button -->
|
||||||
|
<button
|
||||||
|
id="main-download-btn"
|
||||||
|
class="download-btn inline-flex items-center gap-2 px-5 py-2.5 bg-black dark:bg-white text-white dark:text-black rounded-lg hover:opacity-90 transition-all duration-200 font-medium shadow-sm hover:shadow-md"
|
||||||
|
data-download-url=""
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10" />
|
||||||
|
</svg>
|
||||||
|
<span id="platform-text" class="text-base">Download for your platform</span>
|
||||||
|
<span id="file-size" class="text-xs opacity-70"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Dropdown Toggle -->
|
||||||
|
<details class="download-dropdown relative">
|
||||||
|
<summary class="dropdown-toggle cursor-pointer p-2.5 bg-gray-100 dark:bg-gray-800 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors h-full flex items-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<!-- Dropdown Menu -->
|
||||||
|
<div class="dropdown-menu absolute top-full mt-1 right-0 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-1 min-w-[250px] z-50">
|
||||||
|
<div class="font-semibold text-xs text-gray-500 dark:text-gray-400 px-3 py-1.5 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
All Platforms
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- macOS Options -->
|
||||||
|
<div class="py-1">
|
||||||
|
<div class="text-xs text-gray-500 dark:text-gray-400 px-3 py-0.5 uppercase tracking-wide">macOS</div>
|
||||||
|
<a href={downloadLinks['mac-arm']}
|
||||||
|
class="platform-option block px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors text-sm"
|
||||||
|
data-platform="mac-arm">
|
||||||
|
<span class="font-medium">Apple Silicon</span>
|
||||||
|
<span class="text-sm text-gray-500 dark:text-gray-400 ml-2">(M1/M2/M3)</span>
|
||||||
|
</a>
|
||||||
|
<a href={downloadLinks['mac-intel']}
|
||||||
|
class="platform-option block px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors text-sm"
|
||||||
|
data-platform="mac-intel">
|
||||||
|
<span class="font-medium">Intel</span>
|
||||||
|
<span class="text-sm text-gray-500 dark:text-gray-400 ml-2">(x64)</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Windows -->
|
||||||
|
<div class="py-1 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="text-xs text-gray-500 dark:text-gray-400 px-3 py-0.5 uppercase tracking-wide">Windows</div>
|
||||||
|
<a href={downloadLinks['windows']}
|
||||||
|
class="platform-option block px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors text-sm"
|
||||||
|
data-platform="windows">
|
||||||
|
<span class="font-medium">Windows</span>
|
||||||
|
<span class="text-sm text-gray-500 dark:text-gray-400 ml-2">(x64)</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Linux Options -->
|
||||||
|
<div class="py-1 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="text-xs text-gray-500 dark:text-gray-400 px-3 py-0.5 uppercase tracking-wide">Linux</div>
|
||||||
|
<a href={downloadLinks['linux-deb']}
|
||||||
|
class="platform-option block px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors text-sm"
|
||||||
|
data-platform="linux-deb">
|
||||||
|
<span class="font-medium">Debian/Ubuntu</span>
|
||||||
|
<span class="text-sm text-gray-500 dark:text-gray-400 ml-2">(.deb)</span>
|
||||||
|
</a>
|
||||||
|
<a href={downloadLinks['linux-appimage']}
|
||||||
|
class="platform-option block px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors text-sm"
|
||||||
|
data-platform="linux-appimage">
|
||||||
|
<span class="font-medium">AppImage</span>
|
||||||
|
<span class="text-sm text-gray-500 dark:text-gray-400 ml-2">(Universal)</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- View All Releases -->
|
||||||
|
<div class="pt-1 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<a href="https://github.com/janhq/jan/releases"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="block px-3 py-1.5 text-blue-600 dark:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors text-xs">
|
||||||
|
View all releases →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showStats && (
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 mt-3 text-center text-sm">
|
||||||
|
<span class="text-yellow-600 font-semibold">{downloadCount}</span> downloads | Free & Open Source
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.download-dropdown[open] .dropdown-menu {
|
||||||
|
animation: slideDown 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-dropdown summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-dropdown summary {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close dropdown when clicking outside */
|
||||||
|
.download-dropdown:not([open]) .dropdown-menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Platform detection and download handling
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const platformText = document.getElementById('platform-text');
|
||||||
|
const fileSize = document.getElementById('file-size');
|
||||||
|
const mainDownloadBtn = document.getElementById('main-download-btn');
|
||||||
|
|
||||||
|
// Platform detection
|
||||||
|
function detectPlatform() {
|
||||||
|
const platform = navigator.platform.toLowerCase();
|
||||||
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
|
|
||||||
|
let detectedPlatform = 'linux-deb';
|
||||||
|
let platformName = 'Linux (deb)';
|
||||||
|
let downloadSize = '(69.7 MB)';
|
||||||
|
|
||||||
|
if (platform.includes('mac') || userAgent.includes('mac')) {
|
||||||
|
// Check if Apple Silicon
|
||||||
|
if (userAgent.includes('arm') || userAgent.includes('apple')) {
|
||||||
|
detectedPlatform = 'mac-arm';
|
||||||
|
platformName = 'macOS (Apple Silicon)';
|
||||||
|
downloadSize = '(73.2 MB)';
|
||||||
|
} else {
|
||||||
|
detectedPlatform = 'mac-intel';
|
||||||
|
platformName = 'macOS (Intel)';
|
||||||
|
downloadSize = '(75.8 MB)';
|
||||||
|
}
|
||||||
|
} else if (platform.includes('win') || userAgent.includes('win')) {
|
||||||
|
detectedPlatform = 'windows';
|
||||||
|
platformName = 'Windows';
|
||||||
|
downloadSize = '(71.4 MB)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return { platform: detectedPlatform, name: platformName, size: downloadSize };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set initial platform
|
||||||
|
const detected = detectPlatform();
|
||||||
|
if (platformText) {
|
||||||
|
platformText.textContent = `Download for ${detected.name}`;
|
||||||
|
}
|
||||||
|
if (fileSize) {
|
||||||
|
fileSize.textContent = detected.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get download URL based on platform
|
||||||
|
const downloadUrls = {
|
||||||
|
'mac-intel': 'https://github.com/janhq/jan/releases/download/v0.5.14/jan-mac-x64-0.5.14.dmg',
|
||||||
|
'mac-arm': 'https://github.com/janhq/jan/releases/download/v0.5.14/jan-mac-arm64-0.5.14.dmg',
|
||||||
|
'windows': 'https://github.com/janhq/jan/releases/download/v0.5.14/jan-win-x64-0.5.14.exe',
|
||||||
|
'linux-deb': 'https://github.com/janhq/jan/releases/download/v0.5.14/jan-linux-amd64-0.5.14.deb',
|
||||||
|
'linux-appimage': 'https://github.com/janhq/jan/releases/download/v0.5.14/jan-linux-x86_64-0.5.14.AppImage'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mainDownloadBtn) {
|
||||||
|
mainDownloadBtn.setAttribute('data-download-url', downloadUrls[detected.platform]);
|
||||||
|
|
||||||
|
// Handle main button click
|
||||||
|
mainDownloadBtn.addEventListener('click', () => {
|
||||||
|
const url = mainDownloadBtn.getAttribute('data-download-url');
|
||||||
|
if (url) {
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle dropdown platform clicks
|
||||||
|
document.querySelectorAll('.platform-option').forEach(link => {
|
||||||
|
link.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const url = e.currentTarget.getAttribute('href');
|
||||||
|
if (url) {
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close dropdown when clicking outside
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const dropdown = document.querySelector('.download-dropdown');
|
||||||
|
if (dropdown && !dropdown.contains(e.target)) {
|
||||||
|
dropdown.removeAttribute('open');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
112
website/src/components/Steps.astro
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { class: className } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class={`steps-container ${className || ''}`}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.steps-container {
|
||||||
|
margin: 2rem 0;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background-color: var(--sl-color-gray-6);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid var(--sl-color-gray-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light mode adjustments */
|
||||||
|
:global([data-theme="light"]) .steps-container {
|
||||||
|
background-color: var(--sl-color-gray-7);
|
||||||
|
border-color: var(--sl-color-gray-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the headers inside steps */
|
||||||
|
.steps-container :global(h2),
|
||||||
|
.steps-container :global(h3) {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
color: var(--sl-color-text-accent);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-container :global(h2:first-child),
|
||||||
|
.steps-container :global(h3:first-child) {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style lists inside steps */
|
||||||
|
.steps-container :global(ul) {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-container :global(ul li) {
|
||||||
|
position: relative;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding-left: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom bullet points */
|
||||||
|
.steps-container :global(ul li::before) {
|
||||||
|
content: '→';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: var(--sl-color-text-accent);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style links inside steps */
|
||||||
|
.steps-container :global(a) {
|
||||||
|
color: var(--sl-color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-container :global(a:hover) {
|
||||||
|
color: var(--sl-color-text-accent);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add spacing between step groups */
|
||||||
|
.steps-container :global(h2:not(:first-child)),
|
||||||
|
.steps-container :global(h3:not(:first-child)) {
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid var(--sl-color-hairline);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Numbered steps styling */
|
||||||
|
.steps-container :global(h2) {
|
||||||
|
counter-increment: step-counter;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-container {
|
||||||
|
counter-reset: step-counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-container :global(h2::before) {
|
||||||
|
content: counter(step-counter);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
background-color: var(--sl-color-accent);
|
||||||
|
color: var(--sl-color-white);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
60
website/src/components/YouTube.astro
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
id: string;
|
||||||
|
title?: string;
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, title = 'YouTube video player', class: className } = Astro.props;
|
||||||
|
|
||||||
|
// Extract video ID and handle both formats:
|
||||||
|
// - Simple ID: "4mvHgLy_YV8"
|
||||||
|
// - ID with params: "4mvHgLy_YV8?si=74cmdMmcH3gmpv0R"
|
||||||
|
const videoId = id.split('?')[0];
|
||||||
|
const params = id.includes('?') ? '?' + id.split('?')[1] : '';
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class={`youtube-container ${className || ''}`}>
|
||||||
|
<iframe
|
||||||
|
src={`https://www.youtube.com/embed/${videoId}${params}`}
|
||||||
|
title={title}
|
||||||
|
frameborder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen
|
||||||
|
loading="lazy"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.youtube-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 56.25%; /* 16:9 aspect ratio */
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: var(--sl-color-gray-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.youtube-container iframe {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light mode adjustments */
|
||||||
|
:global([data-theme="light"]) .youtube-container {
|
||||||
|
background-color: var(--sl-color-gray-7);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure proper spacing in content */
|
||||||
|
:global(.sl-markdown-content) .youtube-container {
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,8 +1,38 @@
|
|||||||
import { defineCollection } from 'astro:content';
|
import { defineCollection, z } from 'astro:content';
|
||||||
import { docsLoader } from '@astrojs/starlight/loaders';
|
import { docsLoader } from '@astrojs/starlight/loaders';
|
||||||
import { docsSchema } from '@astrojs/starlight/schema';
|
import { docsSchema } from '@astrojs/starlight/schema';
|
||||||
import { videosSchema } from 'starlight-videos/schemas';
|
import { videosSchema } from 'starlight-videos/schemas';
|
||||||
|
|
||||||
|
const changelogSchema = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
date: z.date(),
|
||||||
|
version: z.string().optional(),
|
||||||
|
image: z.string().optional(),
|
||||||
|
gif: z.string().optional(),
|
||||||
|
video: z.string().optional(),
|
||||||
|
featured: z.boolean().default(false),
|
||||||
|
});
|
||||||
|
|
||||||
|
const blogSchema = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
date: z.date(),
|
||||||
|
tags: z.string().optional(),
|
||||||
|
categories: z.string().optional(),
|
||||||
|
author: z.string().optional(),
|
||||||
|
ogImage: z.string().optional(),
|
||||||
|
featured: z.boolean().default(false),
|
||||||
|
});
|
||||||
|
|
||||||
export const collections = {
|
export const collections = {
|
||||||
docs: defineCollection({ loader: docsLoader(), schema: docsSchema({ extend: videosSchema }) }),
|
docs: defineCollection({ loader: docsLoader(), schema: docsSchema({ extend: videosSchema }) }),
|
||||||
|
changelog: defineCollection({
|
||||||
|
type: 'content',
|
||||||
|
schema: changelogSchema,
|
||||||
|
}),
|
||||||
|
blog: defineCollection({
|
||||||
|
type: 'content',
|
||||||
|
schema: blogSchema,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
BIN
website/src/content/blog/_assets/3090s.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
website/src/content/blog/_assets/4070s.jpg
Normal file
|
After Width: | Height: | Size: 885 KiB |
BIN
website/src/content/blog/_assets/4090s.png
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
website/src/content/blog/_assets/ai-locally-llama.cpp.jpg
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
website/src/content/blog/_assets/catastrophic-demo.png
Normal file
|
After Width: | Height: | Size: 742 KiB |
BIN
website/src/content/blog/_assets/chat-with-docs-prompt.jpg
Normal file
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
BIN
website/src/content/blog/_assets/chat-with-your-docs2.jpg
Normal file
|
After Width: | Height: | Size: 415 KiB |