Sync dev with dev-web (#6564)

*  feat: Re-arrange docs as needed

* 🔧 chore: re-arrange the folder structure

* Add server docs

Add server docs

* enhancement: migrate handbook and janv2

* Update docs/src/components/ui/dropdown-button.tsx

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* Update docs/src/pages/_meta.json

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore: update feedback #1

* fix: layout ability model

* feat: add azure as first class provider (#6555)

* feat: add azure as first class provider

* fix: deployment url

* Update handbook: restructure content and add new sections

- Add betting-on-open-source.mdx and open-superintelligence.mdx
- Update handbook index with new structure
- Remove outdated handbook sections (growth, happy, history, money, talent, teams, users, why)
- Update handbook _meta.json to reflect new structure

* chore: fix meta data json

* chore: update missing install

* fix: Catch local API server various errors (#6548)

* fix: Catch local API server various errors

* chore: Add tests to cover error catches

* fix: LocalAPI server trusted host should accept asterisk (#6551)

* feat: support .zip archives for manual backend install (#6534)

* feat(llamacpp): support .zip archives for manual backend install

* Update Lock Files

* Merge pull request #6563 from menloresearch/feat/web-minor-ui-tweak-login

feat: tweak login UI

---------

Co-authored-by: LazyYuuki <huy2840@gmail.com>
Co-authored-by: nngostuds <locnguyen1986@gmail.com>
Co-authored-by: Faisal Amir <urmauur@gmail.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Louis <louis@jan.ai>
Co-authored-by: eckartal <emre@jan.ai>
Co-authored-by: Nghia Doan <dhnghia0604@gmail.com>
Co-authored-by: Roushan Kumar Singh <158602016+github-roushan@users.noreply.github.com>
This commit is contained in:
Dinh Long Nguyen 2025-09-23 21:12:08 +07:00 committed by GitHub
commit 685054c5bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
374 changed files with 19837 additions and 14894 deletions

BIN
docs/bun.lockb Executable file

Binary file not shown.

View File

@ -21,11 +21,13 @@
"astro-mermaid": "^1.0.4",
"autoprefixer": "^10.0.1",
"axios": "^1.6.8",
"class-variance-authority": "^0.7.1",
"date-fns": "^3.6.0",
"embla-carousel-auto-height": "^8.0.0",
"embla-carousel-auto-scroll": "^8.0.0",
"embla-carousel-autoplay": "^8.0.0",
"embla-carousel-react": "^8.0.0",
"framer-motion": "^12.23.18",
"fs": "^0.0.1-security",
"gray-matter": "^4.0.3",
"lucide-react": "^0.522.0",
@ -45,7 +47,7 @@
"react-icons": "^5.0.1",
"react-markdown": "^9.0.1",
"react-share": "^5.1.0",
"react-tweet": "^3.2.0",
"react-tweet": "^3.2.2",
"sass": "^1.72.0",
"sharp": "^0.33.3",
"tailwind-merge": "^2.2.2",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 732 KiB

After

Width:  |  Height:  |  Size: 418 KiB

View File

@ -0,0 +1,13 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_872_879)">
<path d="M48.9607 46.4453C29.9352 55.4999 18.1278 47.9242 10.5694 43.3229C10.1017 43.0329 9.30679 43.3907 9.99651 44.1829C12.5146 47.2361 20.7668 54.5952 31.5383 54.5952C42.3172 54.5952 48.7297 48.7137 49.5318 47.6877C50.3286 46.6704 49.7658 46.1092 48.9605 46.4453H48.9607ZM54.304 43.4945C53.7931 42.8292 51.1973 42.7051 49.5637 42.9058C47.9275 43.1007 45.4716 44.1007 45.6852 44.7011C45.7947 44.926 46.0184 44.8251 47.1423 44.724C48.2694 44.6116 51.4267 44.2131 52.0846 45.0731C52.7456 45.9391 51.0776 50.0644 50.773 50.7297C50.4786 51.395 50.8854 51.5666 51.4383 51.1235C51.9835 50.6805 52.9707 49.5334 53.6331 47.9101C54.291 46.2779 54.6923 44.001 54.304 43.4945Z" fill="#FF9900" style="fill:#FF9900;fill:color(display-p3 1.0000 0.6000 0.0000);fill-opacity:1;"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.168 27.9697C36.168 30.3455 36.228 32.3268 35.0272 34.4366C34.058 36.1521 32.5227 37.207 30.8074 37.207C28.4658 37.207 27.1021 35.423 27.1021 32.79C27.1021 27.5922 31.7592 26.6489 36.168 26.6489V27.9697ZM42.3176 42.8336C41.9144 43.1938 41.3312 43.2196 40.8767 42.9793C38.8526 41.2983 38.4923 40.5179 37.3773 38.9139C34.0323 42.3276 31.6651 43.3481 27.3252 43.3481C22.1961 43.3481 18.1992 40.1832 18.1992 33.8449C18.1992 28.8961 20.884 25.5252 24.7005 23.8786C28.0113 22.4203 32.6343 22.1631 36.168 21.7601V20.9709C36.168 19.5214 36.2794 17.806 35.4304 16.5539C34.6841 15.4303 33.2604 14.9671 32.008 14.9671C29.6838 14.9671 27.6081 16.1592 27.1021 18.6293C26.999 19.1784 26.596 19.7188 26.0472 19.7445L20.129 19.1099C19.6316 18.9982 19.0827 18.5952 19.22 17.8317C20.5837 10.6615 27.059 8.5 32.857 8.5C35.8247 8.5 39.7014 9.28915 42.043 11.5364C45.0107 14.3066 44.7276 18.0033 44.7276 22.0259V31.5291C44.7276 34.3853 45.9112 35.6374 47.0261 37.1814C47.4206 37.7303 47.5065 38.3909 47.0089 38.8023C45.7653 39.8401 43.5524 41.77 42.3346 42.8508L42.3174 42.8336" fill="black" style="fill:black;fill-opacity:1;"/>
<path d="M48.9607 46.4453C29.9352 55.4999 18.1278 47.9242 10.5694 43.3229C10.1017 43.0329 9.30679 43.3907 9.99651 44.1829C12.5146 47.2361 20.7668 54.5952 31.5383 54.5952C42.3172 54.5952 48.7297 48.7137 49.5318 47.6877C50.3286 46.6704 49.7658 46.1092 48.9605 46.4453H48.9607ZM54.304 43.4945C53.7931 42.8292 51.1973 42.7051 49.5637 42.9058C47.9275 43.1007 45.4716 44.1007 45.6852 44.7011C45.7947 44.926 46.0184 44.8251 47.1423 44.724C48.2694 44.6116 51.4267 44.2131 52.0846 45.0731C52.7456 45.9391 51.0776 50.0644 50.773 50.7297C50.4786 51.395 50.8854 51.5666 51.4383 51.1235C51.9835 50.6805 52.9707 49.5334 53.6331 47.9101C54.291 46.2779 54.6923 44.001 54.304 43.4945Z" fill="#FF9900" style="fill:#FF9900;fill:color(display-p3 1.0000 0.6000 0.0000);fill-opacity:1;"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.168 27.9697C36.168 30.3455 36.228 32.3268 35.0272 34.4366C34.058 36.1521 32.5227 37.207 30.8074 37.207C28.4658 37.207 27.1021 35.423 27.1021 32.79C27.1021 27.5922 31.7592 26.6489 36.168 26.6489V27.9697ZM42.3176 42.8336C41.9144 43.1938 41.3312 43.2196 40.8767 42.9793C38.8526 41.2983 38.4923 40.5179 37.3773 38.9139C34.0323 42.3276 31.6651 43.3481 27.3252 43.3481C22.1961 43.3481 18.1992 40.1832 18.1992 33.8449C18.1992 28.8961 20.884 25.5252 24.7005 23.8786C28.0113 22.4203 32.6343 22.1631 36.168 21.7601V20.9709C36.168 19.5214 36.2794 17.806 35.4304 16.5539C34.6841 15.4303 33.2604 14.9671 32.008 14.9671C29.6838 14.9671 27.6081 16.1592 27.1021 18.6293C26.999 19.1784 26.596 19.7188 26.0472 19.7445L20.129 19.1099C19.6316 18.9982 19.0827 18.5952 19.22 17.8317C20.5837 10.6615 27.059 8.5 32.857 8.5C35.8247 8.5 39.7014 9.28915 42.043 11.5364C45.0107 14.3066 44.7276 18.0033 44.7276 22.0259V31.5291C44.7276 34.3853 45.9112 35.6374 47.0261 37.1814C47.4206 37.7303 47.5065 38.3909 47.0089 38.8023C45.7653 39.8401 43.5524 41.77 42.3346 42.8508L42.3174 42.8336" fill="black" style="fill:black;fill-opacity:1;"/>
</g>
<defs>
<clipPath id="clip0_872_879">
<rect width="47" height="47" fill="white" style="fill:white;fill-opacity:1;" transform="translate(8.5 8.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M54.5632 27.2833C55.0081 25.9641 55.235 24.5829 55.2353 23.1926C55.2351 20.892 54.614 18.633 53.4359 16.6477C51.0686 12.5818 46.6771 10.0716 41.9244 10.0716C40.9881 10.0716 40.0544 10.1692 39.1388 10.3627C37.9073 8.9937 36.3958 7.89784 34.7037 7.14736C33.0117 6.39688 31.1777 6.00883 29.3225 6.00879H29.2392L29.208 6.00897C23.4516 6.00897 18.3466 9.67396 16.5771 15.077C14.7453 15.4471 13.0149 16.1991 11.5015 17.2826C9.98817 18.3661 8.72687 19.756 7.80203 21.3594C6.62749 23.3567 6.00853 25.6246 6.00781 27.9337C6.00826 31.1789 7.22914 34.3085 9.43403 36.7164C8.98895 38.0356 8.76196 39.4168 8.76177 40.8072C8.76198 43.1077 9.38304 45.3668 10.5611 47.3521C11.9621 49.7588 14.1016 51.6644 16.671 52.7939C19.2404 53.9235 22.1071 54.2186 24.8575 53.6369C26.0891 55.0059 27.6008 56.1017 29.293 56.8522C30.9851 57.6027 32.8193 57.9908 34.6745 57.9909H34.7578L34.7916 57.9908C40.5512 57.9908 45.6544 54.3256 47.424 48.9177C49.2558 48.5474 50.9862 47.7953 52.4996 46.7118C54.0129 45.6283 55.2743 44.2384 56.1992 42.635C57.3725 40.6396 57.9903 38.3735 57.99 36.0666C57.9895 32.8214 56.7686 29.6919 54.5637 27.284L54.5632 27.2833ZM34.7611 54.5926H34.7475C32.4429 54.5918 30.2114 53.794 28.4412 52.338C28.5463 52.2822 28.6502 52.2241 28.7527 52.1638L39.2425 46.1853C39.5043 46.0383 39.722 45.8255 39.8734 45.5686C40.0249 45.3117 40.1047 45.0198 40.1048 44.7226V30.1209L44.5386 32.6469C44.5618 32.6584 44.5819 32.6753 44.5969 32.6962C44.612 32.7172 44.6216 32.7414 44.6248 32.7669V44.8509C44.6187 50.2234 40.2065 54.5817 34.7611 54.5926ZM13.5489 45.6534C12.6824 44.1752 12.2259 42.4978 12.2253 40.7901C12.2253 40.2331 12.2746 39.6747 12.3706 39.1259C12.4486 39.172 12.5847 39.2541 12.6824 39.3094L23.1722 45.2879C23.4337 45.4385 23.7311 45.5179 24.0339 45.5178C24.3368 45.5178 24.6341 45.4383 24.8956 45.2875L37.7025 37.9911V43.0434L37.7027 43.0521C37.7027 43.0764 37.697 43.1004 37.6859 43.1222C37.6749 43.1439 37.6589 43.1629 37.6392 43.1775L27.035 49.2186C25.5346 50.0706 23.8338 50.5193 22.1026 50.5198C20.3696 50.5195 18.6671 50.0698 17.1658 49.2156C15.6645 48.3613 14.4172 47.1327 13.5489 45.6529V45.6534ZM10.7892 23.0581C11.9413 21.0836 13.7605 19.5717 15.9283 18.7871C15.9283 18.8762 15.9232 19.0341 15.9232 19.1437V31.1009L15.923 31.1107C15.9231 31.4076 16.0027 31.6992 16.154 31.9559C16.3052 32.2126 16.5225 32.4252 16.784 32.5722L29.5909 39.8675L25.1573 42.3935C25.1354 42.4077 25.1103 42.4164 25.0843 42.4187C25.0582 42.4211 25.0319 42.417 25.0078 42.4069L14.4025 36.3607C12.9033 35.5038 11.6586 34.2731 10.7933 32.792C9.92805 31.3109 9.47248 29.6315 9.47228 27.9219C9.47295 26.2151 9.92722 24.5382 10.7898 23.0586L10.7892 23.0581ZM47.2171 31.4223L34.4102 24.1261L38.844 21.601C38.8658 21.5867 38.8909 21.5781 38.917 21.5757C38.9431 21.5734 38.9694 21.5774 38.9935 21.5876L49.5986 27.6287C51.0991 28.4843 52.345 29.7143 53.2113 31.1952C54.0777 32.6761 54.5339 34.3559 54.5343 36.0658C54.5343 40.1471 51.9533 43.799 48.0724 45.2086V32.8939C48.0729 32.8894 48.0729 32.8847 48.0729 32.8801C48.0728 32.5843 47.9937 32.2938 47.8434 32.0378C47.6931 31.7819 47.4771 31.5695 47.2171 31.4223ZM51.6301 24.8689C51.527 24.8066 51.4231 24.7455 51.3185 24.6856L40.8287 18.7069C40.5672 18.5566 40.2699 18.4773 39.9672 18.4772C39.6645 18.4773 39.3672 18.5566 39.1057 18.7069L26.2986 26.0033V20.9511L26.2984 20.9424C26.2984 20.893 26.3221 20.8466 26.362 20.817L36.9662 14.781C38.4662 13.9278 40.1671 13.4786 41.8985 13.4785C47.3508 13.4785 51.7724 17.8413 51.7724 23.2211C51.7722 23.7731 51.7246 24.3242 51.6301 24.8683V24.8689ZM23.888 33.8736L19.4533 31.3475C19.4301 31.3361 19.41 31.3191 19.395 31.2982C19.3799 31.2773 19.3703 31.253 19.3671 31.2276V19.1434C19.3695 13.7665 23.7911 9.40736 29.2411 9.40736C31.5493 9.40783 33.7845 10.2057 35.5588 11.6624C35.479 11.7054 35.3398 11.7813 35.2472 11.8366L24.7574 17.8151C24.4957 17.9621 24.2781 18.1747 24.1267 18.4316C23.9752 18.6884 23.8954 18.9802 23.8954 19.2773V19.2869L23.888 33.8736ZM26.2966 28.7498L32.0005 25.4992L37.7045 28.7477V35.2468L32.0005 38.4955L26.2966 35.2468V28.7498Z" fill="black" style="fill:black;fill-opacity:1;"/>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.2143 40.5702L26.4354 34.8348L26.6064 34.3349L26.4354 34.0587H25.9355L24.2255 33.9535L18.3848 33.7956L13.3203 33.5851L8.41366 33.322L7.17713 33.0589L6.01953 31.533L6.13792 30.77L7.17713 30.0729L8.6636 30.2044L11.9522 30.428L16.8852 30.77L20.4632 30.9805L25.7645 31.533H26.6064L26.7248 31.191L26.4354 30.9805L26.2118 30.77L21.1078 27.3104L15.5829 23.6534L12.6889 21.5487L11.1235 20.4832L10.3342 19.4834L9.99221 17.2998L11.4129 15.7344L13.3203 15.8659L13.807 15.9975L15.7408 17.4839L19.8713 20.6805L25.2647 24.6532L26.0539 25.3109L26.3696 25.0873L26.4091 24.9294L26.0539 24.3375L23.1205 19.0362L19.9897 13.6428L18.5953 11.4065L18.227 10.0648C18.0954 9.51227 18.0033 9.05186 18.0033 8.48621L19.6214 6.2894L20.5159 6L22.6732 6.2894L23.5809 7.07867L24.9226 10.1437L27.0931 14.9714L30.4607 21.5355L31.4473 23.4824L31.9735 25.2846L32.1708 25.8371H32.5128V25.5214L32.7891 21.8249L33.3021 17.2866L33.802 11.446L33.973 9.80167L34.7886 7.82848L36.4066 6.76296L37.6694 7.36807L38.7086 8.85454L38.5639 9.81482L37.9457 13.827L36.7354 20.1149L35.9462 24.3243H36.4066L36.9328 23.7981L39.0638 20.9699L42.6418 16.4973L44.2204 14.7215L46.062 12.7614L47.2459 11.8275H49.4822L51.1265 14.2742L50.3899 16.7999L48.0878 19.7202L46.1804 22.1933L43.4443 25.8766L41.7342 28.8232L41.892 29.06L42.2998 29.0205L48.4825 27.705L51.8237 27.0999L55.8096 26.4159L57.6117 27.2578L57.8091 28.1128L57.0987 29.8624L52.8366 30.9147L47.8379 31.9145L40.3924 33.6772L40.3003 33.743L40.4056 33.8745L43.76 34.1902L45.1938 34.2692H48.7061L55.2439 34.7559L56.954 35.8872L57.9801 37.2684L57.8091 38.3208L55.1781 39.6625L51.6264 38.8206L43.339 36.8475L40.4976 36.1371H40.103V36.3739L42.4708 38.6891L46.8118 42.6092L52.2447 47.6605L52.5209 48.9102L51.8237 49.8968L51.0871 49.7915L46.312 46.2004L44.4703 44.5823L40.3003 41.0701H40.0241V41.4384L40.9844 42.8459L46.062 50.4756L46.3251 52.8171L45.9568 53.5801L44.6413 54.0405L43.1943 53.7774L40.2214 49.6074L37.1564 44.9112L34.6833 40.7017L34.3808 40.8728L32.9206 56.5925L32.2366 57.3949L30.658 58L29.3426 57.0002L28.6454 55.3822L29.3426 52.1857L30.1845 48.0157L30.8685 44.7007L31.4868 40.5834L31.8551 39.2153L31.8288 39.1232L31.5262 39.1627L28.4218 43.4247L23.6993 49.8047L19.9634 53.8037L19.0689 54.1589L17.5166 53.3564L17.6613 51.9226L18.5295 50.6466L23.6993 44.0693L26.8169 39.9914L28.8295 37.6367L28.8164 37.2947H28.698L14.9646 46.2135L12.5179 46.5292L11.4655 45.5426L11.5971 43.9246L12.0969 43.3984L16.2275 40.557L16.2143 40.5702Z" fill="#D97757" style="fill:#D97757;fill:color(display-p3 0.8510 0.4667 0.3412);fill-opacity:1;"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,3 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M61.8781 13.5771C61.2527 13.2647 60.9836 13.86 60.618 14.1622C60.4928 14.2596 60.3868 14.3863 60.2813 14.5032C59.3672 15.4981 58.2992 16.1517 56.9041 16.0737C54.8647 15.9568 53.1229 16.6102 51.5836 18.1999C51.2565 16.2395 50.1694 15.0685 48.5146 14.3179C47.6487 13.9278 46.7733 13.5374 46.167 12.6888C45.7437 12.0843 45.6282 11.4112 45.4166 10.7479C45.282 10.3482 45.147 9.93833 44.6948 9.86983C44.2042 9.79178 44.0114 10.211 43.8193 10.5626C43.0496 11.9966 42.7513 13.5766 42.7804 15.1761C42.8479 18.7756 44.339 21.6429 47.3025 23.6818C47.6392 23.916 47.7256 24.15 47.6201 24.4914C47.418 25.1939 47.1775 25.8764 46.9658 26.5789C46.8311 27.0276 46.6287 27.1251 46.1574 26.9302C44.5316 26.2379 43.1266 25.2133 41.8854 23.9748C39.7783 21.897 37.8735 19.6047 35.4969 17.8098C34.9469 17.3953 34.3821 17.0016 33.8036 16.6296C31.379 14.2302 34.1212 12.2596 34.7561 12.0257C35.4199 11.7817 34.9871 10.943 32.8412 10.9527C30.6957 10.9624 28.7328 11.6939 26.2315 12.6694C25.8658 12.816 25.4811 12.9232 25.0865 13.0106C22.8159 12.5716 20.4587 12.4741 17.9956 12.7569C13.358 13.2833 9.65408 15.5173 6.93131 19.3312C3.66024 23.9165 2.8903 29.1249 3.83327 34.5581C4.82422 40.2836 7.69125 45.0243 12.0978 48.7311C16.6679 52.5737 21.9309 54.4565 27.9343 54.0956C31.5806 53.8812 35.641 53.3839 40.2207 49.4331C41.3753 50.0181 42.5874 50.2525 44.5983 50.428C46.1474 50.5746 47.6387 50.35 48.7933 50.1064C50.6022 49.7163 50.4771 48.0091 49.8227 47.6972C44.5218 45.1809 45.6854 46.205 44.6273 45.3759C46.9715 42.5494 50.3507 39.6267 52.2343 31.7096C52.686 29.8112 52.9689 27.6901 52.9689 25.7017C52.9689 25.3034 53.0553 25.1069 53.5366 25.058C54.8642 24.9017 56.1534 24.5315 57.3369 23.8679C60.7716 21.9561 62.1572 18.8153 62.4844 15.0502C62.5323 14.4748 62.4748 13.8792 61.8781 13.5771ZM31.9467 47.4624C26.8088 43.3466 24.317 41.9904 23.2876 42.049C22.3256 42.1076 22.4986 43.2294 22.7102 43.9609C22.9314 44.6828 23.2204 45.1801 23.6243 45.8141C23.9033 46.2337 24.0958 46.8576 23.3454 47.3263C21.6907 48.3696 18.8138 46.975 18.6789 46.9065C15.3308 44.8972 12.531 42.2442 10.5584 38.6158C8.6536 35.1234 7.54698 31.3783 7.36442 27.3789C7.31628 26.4134 7.59544 26.0716 8.53809 25.8963C9.77912 25.662 11.0588 25.6131 12.2999 25.7984C17.5434 26.5789 22.0075 28.9686 25.7503 32.7531C27.8863 34.9085 29.5026 37.484 31.1674 40.0003C32.9376 42.673 34.8426 45.219 37.2672 47.3063C38.1236 48.0378 38.8064 48.5939 39.4609 49.0038C37.4884 49.2279 34.1977 49.2768 31.9467 47.4624ZM34.4317 31.1318C34.5132 30.7956 34.8101 30.5491 35.1698 30.5491C35.2585 30.5494 35.3464 30.5658 35.4294 30.5977C35.5354 30.6369 35.6315 30.6955 35.7085 30.783C35.8431 30.9198 35.9202 31.1148 35.9202 31.3195C35.9202 31.7488 35.5835 32.09 35.1602 32.09C34.9816 32.0917 34.8083 32.0276 34.6723 31.9096C34.5362 31.7915 34.4466 31.6274 34.4197 31.4474C34.403 31.3425 34.4071 31.2352 34.4317 31.1318ZM41.8104 35.4192C41.4031 35.5779 40.9976 35.7024 40.6058 35.7187C39.8744 35.7576 39.0761 35.4552 38.6433 35.0846C37.9699 34.5092 37.4888 34.1876 37.2867 33.1824C37.2001 32.7531 37.2481 32.09 37.3252 31.7096C37.4984 30.8902 37.3058 30.3634 36.7384 29.8855C36.2767 29.4954 35.6895 29.3882 35.0451 29.3882C34.8045 29.3882 34.5833 29.281 34.4197 29.1932C34.1502 29.0569 33.9291 28.7154 34.1407 28.2958C34.2082 28.1594 34.5354 27.8276 34.6124 27.7693C35.4877 27.262 36.4982 27.4278 37.4311 27.8082C38.2971 28.1691 38.9515 28.8323 39.8943 29.7686C40.8563 30.8999 41.0295 31.2123 41.5781 32.0608C42.0112 32.724 42.4056 33.407 42.6751 34.1871C42.8121 34.595 42.6862 34.9421 42.3092 35.187C42.1558 35.2865 41.9795 35.3533 41.8104 35.4192Z" fill="#4D6BFE" style="fill:#4D6BFE;fill:color(display-p3 0.3020 0.4196 0.9961);fill-opacity:1;"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,14 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_872_913)">
<path d="M24.0023 56C28.4174 56 32.0007 52.416 32.0007 48V40H24.0023C19.5872 40 16.0039 43.584 16.0039 48C16.0039 52.416 19.5872 56 24.0023 56Z" fill="#0ACF83" style="fill:#0ACF83;fill:color(display-p3 0.0392 0.8118 0.5137);fill-opacity:1;"/>
<path d="M16.0039 32C16.0039 27.584 19.5872 24 24.0023 24H32.0007V40H24.0023C19.5872 40 16.0039 36.416 16.0039 32Z" fill="#A259FF" style="fill:#A259FF;fill:color(display-p3 0.6353 0.3490 1.0000);fill-opacity:1;"/>
<path d="M16.002 16C16.002 11.584 19.5852 8 24.0004 8H31.9988V24H24.0004C19.5852 24 16.002 20.416 16.002 16Z" fill="#F24E1E" style="fill:#F24E1E;fill:color(display-p3 0.9490 0.3059 0.1176);fill-opacity:1;"/>
<path d="M32 8H39.9984C44.4135 8 47.9968 11.584 47.9968 16C47.9968 20.416 44.4135 24 39.9984 24H32V8Z" fill="#FF7262" style="fill:#FF7262;fill:color(display-p3 1.0000 0.4471 0.3843);fill-opacity:1;"/>
<path d="M47.9968 32C47.9968 36.416 44.4135 40 39.9984 40C35.5833 40 32 36.416 32 32C32 27.584 35.5833 24 39.9984 24C44.4135 24 47.9968 27.584 47.9968 32Z" fill="#1ABCFE" style="fill:#1ABCFE;fill:color(display-p3 0.1020 0.7373 0.9961);fill-opacity:1;"/>
</g>
<defs>
<clipPath id="clip0_872_913">
<rect width="32" height="48" fill="white" style="fill:white;fill-opacity:1;" transform="translate(16 8)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,28 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_872_811" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="4" y="4" width="56" height="56">
<path d="M31.946 4C31.04 19.02 19.02 31.04 4 31.946V32.054C19.02 32.96 31.04 44.98 31.946 60H32.054C32.96 44.98 44.98 32.96 60 32.054V31.946C44.98 31.04 32.96 19.02 32.054 4H31.946Z" fill="url(#paint0_linear_872_811)" style=""/>
</mask>
<g mask="url(#mask0_872_811)">
<path d="M32 0C49.6608 0 64 14.3392 64 32C64 49.6608 49.6608 64 32 64C14.3392 64 0 49.6608 0 32C0 14.3392 14.3392 0 32 0Z" fill="url(#paint1_linear_872_811)" style=""/>
</g>
<defs>
<linearGradient id="paint0_linear_872_811" x1="50.446" y1="15.02" x2="11.076" y2="48.424" gradientUnits="userSpaceOnUse">
<stop stop-color="#217BFE" style="stop-color:#217BFE;stop-color:color(display-p3 0.1294 0.4824 0.9961);stop-opacity:1;"/>
<stop offset="0.14" stop-color="#1485FC" style="stop-color:#1485FC;stop-color:color(display-p3 0.0784 0.5216 0.9882);stop-opacity:1;"/>
<stop offset="0.27" stop-color="#078EFB" style="stop-color:#078EFB;stop-color:color(display-p3 0.0275 0.5569 0.9843);stop-opacity:1;"/>
<stop offset="0.52" stop-color="#548FFD" style="stop-color:#548FFD;stop-color:color(display-p3 0.3294 0.5608 0.9922);stop-opacity:1;"/>
<stop offset="0.78" stop-color="#A190FF" style="stop-color:#A190FF;stop-color:color(display-p3 0.6314 0.5647 1.0000);stop-opacity:1;"/>
<stop offset="0.89" stop-color="#AF94FE" style="stop-color:#AF94FE;stop-color:color(display-p3 0.6863 0.5804 0.9961);stop-opacity:1;"/>
<stop offset="1" stop-color="#BD99FE" style="stop-color:#BD99FE;stop-color:color(display-p3 0.7412 0.6000 0.9961);stop-opacity:1;"/>
</linearGradient>
<linearGradient id="paint1_linear_872_811" x1="23.994" y1="41.26" x2="48.184" y2="16.228" gradientUnits="userSpaceOnUse">
<stop stop-color="#217BFE" style="stop-color:#217BFE;stop-color:color(display-p3 0.1294 0.4824 0.9961);stop-opacity:1;"/>
<stop offset="0.14" stop-color="#1485FC" style="stop-color:#1485FC;stop-color:color(display-p3 0.0784 0.5216 0.9882);stop-opacity:1;"/>
<stop offset="0.27" stop-color="#078EFB" style="stop-color:#078EFB;stop-color:color(display-p3 0.0275 0.5569 0.9843);stop-opacity:1;"/>
<stop offset="0.52" stop-color="#548FFD" style="stop-color:#548FFD;stop-color:color(display-p3 0.3294 0.5608 0.9922);stop-opacity:1;"/>
<stop offset="0.78" stop-color="#A190FF" style="stop-color:#A190FF;stop-color:color(display-p3 0.6314 0.5647 1.0000);stop-opacity:1;"/>
<stop offset="0.89" stop-color="#AF94FE" style="stop-color:#AF94FE;stop-color:color(display-p3 0.6863 0.5804 0.9961);stop-opacity:1;"/>
<stop offset="1" stop-color="#BD99FE" style="stop-color:#BD99FE;stop-color:color(display-p3 0.7412 0.6000 0.9961);stop-opacity:1;"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 167 KiB

View File

@ -0,0 +1,14 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_872_868)">
<path d="M8.68182 52.4999H17.2727V31.3787L5 22.0605V48.7727C5 50.8351 6.65068 52.4999 8.68182 52.4999Z" fill="#4285F4" style="fill:#4285F4;fill:color(display-p3 0.2588 0.5216 0.9569);fill-opacity:1;"/>
<path d="M46.7266 52.4999H55.3175C57.3547 52.4999 58.9993 50.8289 58.9993 48.7727V22.0605L46.7266 31.3787" fill="#34A853" style="fill:#34A853;fill:color(display-p3 0.2039 0.6588 0.3255);fill-opacity:1;"/>
<path d="M46.7266 15.2271V31.3786L58.9993 22.0604V17.0907C58.9993 12.4813 53.8018 9.85362 50.1629 12.618" fill="#FBBC04" style="fill:#FBBC04;fill:color(display-p3 0.9843 0.7373 0.0157);fill-opacity:1;"/>
<path d="M17.2734 31.3791V15.2275L32.0007 26.4094L46.728 15.2275V31.3791L32.0007 42.5609" fill="#EA4335" style="fill:#EA4335;fill:color(display-p3 0.9176 0.2627 0.2078);fill-opacity:1;"/>
<path d="M5 17.0907V22.0604L17.2727 31.3786V15.2271L13.8364 12.618C10.1914 9.85362 5 12.4813 5 17.0907Z" fill="#C5221F" style="fill:#C5221F;fill:color(display-p3 0.7725 0.1333 0.1216);fill-opacity:1;"/>
</g>
<defs>
<clipPath id="clip0_872_868">
<rect width="54" height="41" fill="white" style="fill:white;fill-opacity:1;" transform="translate(5 11.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,15 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_872_954)">
<path d="M10.7801 48.0675L12.9851 51.9041C13.4433 52.7118 14.1019 53.3464 14.8751 53.8079L22.7503 40.0771H7C7 40.9714 7.2291 41.8656 7.68729 42.6733L10.7801 48.0675Z" fill="#0066DA" style="fill:#0066DA;fill:color(display-p3 0.0000 0.4000 0.8549);fill-opacity:1;"/>
<path d="M32 23.9232L24.1249 10.1924C23.3517 10.6539 22.693 11.2885 22.2348 12.0962L7.68729 37.4808C7.23752 38.2711 7.0006 39.1661 7 40.077H22.7503L32 23.9232Z" fill="#00AC47" style="fill:#00AC47;fill:color(display-p3 0.0000 0.6745 0.2784);fill-opacity:1;"/>
<path d="M49.1243 53.8079C49.8975 53.3464 50.5562 52.7118 51.0144 51.9041L51.9307 50.3175L56.3122 42.6733C56.7704 41.8656 56.9995 40.9714 56.9995 40.0771H41.248L44.5997 46.7118L49.1243 53.8079Z" fill="#EA4335" style="fill:#EA4335;fill:color(display-p3 0.9176 0.2627 0.2078);fill-opacity:1;"/>
<path d="M32.0001 23.9231L39.8753 10.1923C39.1021 9.73077 38.2143 9.5 37.298 9.5H26.7023C25.7859 9.5 24.8982 9.75962 24.125 10.1923L32.0001 23.9231Z" fill="#00832D" style="fill:#00832D;fill:color(display-p3 0.0000 0.5137 0.1765);fill-opacity:1;"/>
<path d="M41.2496 40.0771H22.7501L14.875 53.8079C15.6482 54.2695 16.5359 54.5002 17.4523 54.5002H46.5474C47.4638 54.5002 48.3515 54.2406 49.1247 53.8079L41.2496 40.0771Z" fill="#2684FC" style="fill:#2684FC;fill:color(display-p3 0.1490 0.5176 0.9882);fill-opacity:1;"/>
<path d="M49.0389 24.7885L41.7652 12.0962C41.307 11.2885 40.6483 10.6539 39.8751 10.1924L32 23.9232L41.2497 40.077H56.9714C56.9714 39.1828 56.7423 38.2885 56.2841 37.4808L49.0389 24.7885Z" fill="#FFBA00" style="fill:#FFBA00;fill:color(display-p3 1.0000 0.7294 0.0000);fill-opacity:1;"/>
</g>
<defs>
<clipPath id="clip0_872_954">
<rect width="50" height="45" fill="white" style="fill:white;fill-opacity:1;" transform="translate(7 9.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,6 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M53.12 32.5C53.12 30.94 52.98 29.44 52.72 28H32V36.52H43.84C43.32 39.26 41.76 41.58 39.42 43.14V48.68H46.56C50.72 44.84 53.12 39.2 53.12 32.5Z" fill="#4285F4" style="fill:#4285F4;fill:color(display-p3 0.2588 0.5216 0.9569);fill-opacity:1;"/>
<path d="M31.9994 54.0002C37.9394 54.0002 42.9194 52.0402 46.5594 48.6802L39.4194 43.1402C37.4594 44.4602 34.9594 45.2602 31.9994 45.2602C26.2794 45.2602 21.4194 41.4002 19.6794 36.2002H12.3594V41.8802C15.9794 49.0602 23.3994 54.0002 31.9994 54.0002Z" fill="#34A853" style="fill:#34A853;fill:color(display-p3 0.2039 0.6588 0.3255);fill-opacity:1;"/>
<path d="M19.68 36.1801C19.24 34.8601 18.98 33.4601 18.98 32.0001C18.98 30.5401 19.24 29.1401 19.68 27.8201V22.1401H12.36C10.86 25.1001 10 28.4401 10 32.0001C10 35.5601 10.86 38.9001 12.36 41.8601L18.06 37.4201L19.68 36.1801Z" fill="#FBBC05" style="fill:#FBBC05;fill:color(display-p3 0.9843 0.7373 0.0196);fill-opacity:1;"/>
<path d="M31.9994 18.76C35.2394 18.76 38.1194 19.88 40.4194 22.04L46.7194 15.74C42.8994 12.18 37.9394 10 31.9994 10C23.3994 10 15.9794 14.94 12.3594 22.14L19.6794 27.82C21.4194 22.62 26.2794 18.76 31.9994 18.76Z" fill="#EA4335" style="fill:#EA4335;fill:color(display-p3 0.9176 0.2627 0.2078);fill-opacity:1;"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,15 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M53.1487 9.5H30.9043C30.9043 14.8901 35.4046 19.2774 40.9335 19.2774H45.048V23.1006C45.048 28.4907 49.5485 32.8782 55.0774 32.8782V11.3803C55.0774 10.3148 54.2417 9.5 53.1487 9.5Z" fill="#2684FF" style="fill:#2684FF;fill:color(display-p3 0.1490 0.5176 1.0000);fill-opacity:1;"/>
<path d="M42.1586 20.2798H19.9141C19.9141 25.6699 24.4144 30.0572 29.9434 30.0572H34.0579V33.9431C34.0579 39.3332 38.5584 43.7206 44.0873 43.7206V22.16C44.0873 21.1572 43.2515 20.2798 42.1586 20.2798Z" fill="url(#paint0_linear_872_966)" style=""/>
<path d="M31.1684 31.1221H8.92383C8.92383 36.5121 13.4242 40.8994 18.9531 40.8994H23.0677V44.7227C23.0677 50.1128 27.568 54.5 33.097 54.5V33.0022C33.097 31.9368 32.1971 31.1221 31.1684 31.1221Z" fill="url(#paint1_linear_872_966)" style=""/>
<defs>
<linearGradient id="paint0_linear_872_966" x1="43.6141" y1="20.3339" x2="34.4043" y2="30.0761" gradientUnits="userSpaceOnUse">
<stop offset="0.176" stop-color="#0052CC" style="stop-color:#0052CC;stop-color:color(display-p3 0.0000 0.3216 0.8000);stop-opacity:1;"/>
<stop offset="1" stop-color="#2684FF" style="stop-color:#2684FF;stop-color:color(display-p3 0.1490 0.5176 1.0000);stop-opacity:1;"/>
</linearGradient>
<linearGradient id="paint1_linear_872_966" x1="33.2421" y1="31.1941" x2="22.5777" y2="41.8375" gradientUnits="userSpaceOnUse">
<stop offset="0.176" stop-color="#0052CC" style="stop-color:#0052CC;stop-color:color(display-p3 0.0000 0.3216 0.8000);stop-opacity:1;"/>
<stop offset="1" stop-color="#2684FF" style="stop-color:#2684FF;stop-color:color(display-p3 0.1490 0.5176 1.0000);stop-opacity:1;"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 364 KiB

View File

@ -0,0 +1,19 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_872_822)">
<path d="M62.9735 45.2019H36.4121V54.0008H62.9735V45.2019Z" fill="#E10500" style="fill:#E10500;fill:color(display-p3 0.8824 0.0196 0.0000);fill-opacity:1;"/>
<path d="M27.5614 45.2019H1V54.0008H27.5614V45.2019Z" fill="#E10500" style="fill:#E10500;fill:color(display-p3 0.8824 0.0196 0.0000);fill-opacity:1;"/>
<path d="M54.1156 36.3975H45.2637V45.1964H54.1156V36.3975Z" fill="#FA500F" style="fill:#FA500F;fill:color(display-p3 0.9804 0.3137 0.0588);fill-opacity:1;"/>
<path d="M36.4105 36.3975H27.5586V45.1964H36.4105V36.3975Z" fill="#FA500F" style="fill:#FA500F;fill:color(display-p3 0.9804 0.3137 0.0588);fill-opacity:1;"/>
<path d="M18.7035 36.3975H9.85156V45.1964H18.7035V36.3975Z" fill="#FA500F" style="fill:#FA500F;fill:color(display-p3 0.9804 0.3137 0.0588);fill-opacity:1;"/>
<path d="M54.1156 10H45.2637V18.7989H54.1156V10Z" fill="#FFD800" style="fill:#FFD800;fill:color(display-p3 1.0000 0.8471 0.0000);fill-opacity:1;"/>
<path d="M54.116 18.7983H36.4121V27.5972H54.116V18.7983Z" fill="#FFAF00" style="fill:#FFAF00;fill:color(display-p3 1.0000 0.6863 0.0000);fill-opacity:1;"/>
<path d="M18.7035 10H9.85156V18.7989H18.7035V10Z" fill="#FFD800" style="fill:#FFD800;fill:color(display-p3 1.0000 0.8471 0.0000);fill-opacity:1;"/>
<path d="M27.5556 18.7983H9.85156V27.5972H27.5556V18.7983Z" fill="#FFAF00" style="fill:#FFAF00;fill:color(display-p3 1.0000 0.6863 0.0000);fill-opacity:1;"/>
<path d="M54.117 27.5964H9.85156V36.3952H54.117V27.5964Z" fill="#FF8205" style="fill:#FF8205;fill:color(display-p3 1.0000 0.5098 0.0196);fill-opacity:1;"/>
</g>
<defs>
<clipPath id="clip0_872_822">
<rect width="62" height="44" fill="white" style="fill:white;fill-opacity:1;" transform="translate(1 10)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,11 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_872_905)">
<path d="M10.5583 8.70014L38.6674 6.61594C42.1193 6.31844 43.0064 6.51904 45.1759 8.10344L54.1497 14.4495C55.6315 15.5409 56.1249 15.8384 56.1249 17.0267V51.8308C56.1249 54.0119 55.3341 55.3022 52.5732 55.4994L19.9308 57.4833C17.8559 57.5819 16.8708 57.2844 15.7844 55.8955L9.17783 47.2697C7.99 45.6819 7.5 44.4936 7.5 43.1047V12.1681C7.5 10.3848 8.29076 8.89734 10.5583 8.70014Z" fill="white" style="fill:white;fill-opacity:1;"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.6691 6.61594L10.5549 8.70014C8.29076 8.89734 7.5 10.3848 7.5 12.1681V43.1047C7.5 44.4936 7.99169 45.6819 9.17614 47.2697L15.7844 55.8955C16.8708 57.2844 17.8559 57.5819 19.9291 57.4833L52.5749 55.4994C55.3358 55.3022 56.1266 54.0119 56.1266 51.8308V17.0267C56.1266 15.8996 55.6822 15.5749 54.3744 14.6127L54.1514 14.4495L45.1793 8.10344C43.0081 6.51904 42.121 6.31844 38.6674 6.61594H38.6691ZM20.6675 16.4572C18.0029 16.6374 17.3963 16.6782 15.8841 15.4423L12.0367 12.3687C11.643 11.9709 11.8407 11.4745 12.8258 11.3759L39.8518 9.39374C42.1193 9.19484 43.3038 9.98874 44.1909 10.6823L48.8273 14.0534C49.025 14.1537 49.5167 14.747 48.9253 14.747L21.0122 16.4334L20.6675 16.4572ZM17.5602 51.5333V21.9873C17.5602 20.697 17.9556 20.102 19.1383 20.0017L51.1928 18.1181C52.2792 18.0195 52.7726 18.7131 52.7726 20.0017V49.3505C52.7726 50.6408 52.5749 51.7322 50.7991 51.8308L20.1251 53.6158C18.351 53.7144 17.5602 53.1211 17.5602 51.5333ZM47.8422 23.5717C48.0382 24.4642 47.8422 25.3567 46.9518 25.4587L45.475 25.7528V47.5672C44.1909 48.2608 43.0081 48.6569 42.0213 48.6569C40.4432 48.6569 40.0478 48.1605 38.8651 46.6747L29.1986 31.4053V46.1783L32.2568 46.8736C32.2568 46.8736 32.2568 48.6586 29.7899 48.6586L22.9857 49.0547C22.788 48.6569 22.9857 47.6658 23.6751 47.4686L25.4509 46.9739V27.4409L22.9857 27.2403C22.788 26.3478 23.2797 25.0592 24.6618 24.9606L31.9611 24.4642L42.0213 39.9342V26.2492L39.4564 25.9534C39.2604 24.8603 40.0478 24.0681 41.0346 23.9695L47.8422 23.5717Z" fill="black" style="fill:black;fill-opacity:1;"/>
</g>
<defs>
<clipPath id="clip0_872_905">
<rect width="49" height="51" fill="white" style="fill:white;fill-opacity:1;" transform="translate(7.5 6.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,6 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.7684 38.4767C17.7684 41.3246 15.4583 43.6263 12.6 43.6263C9.74174 43.6263 7.43164 41.3246 7.43164 38.4767C7.43164 35.6288 9.74174 33.3271 12.6 33.3271H17.7684V38.4767ZM20.3526 38.4767C20.3526 35.6288 22.6627 33.3271 25.5209 33.3271C28.3792 33.3271 30.6893 35.6288 30.6893 38.4767V51.3506C30.6893 54.1985 28.3792 56.5002 25.5209 56.5002C22.6627 56.5002 20.3526 54.1985 20.3526 51.3506V38.4767Z" fill="#E01E5A" style="fill:#E01E5A;fill:color(display-p3 0.8784 0.1176 0.3529);fill-opacity:1;"/>
<path d="M25.5191 17.7991C22.6608 17.7991 20.3507 15.4974 20.3507 12.6496C20.3507 9.8017 22.6608 7.5 25.5191 7.5C28.3773 7.5 30.6874 9.8017 30.6874 12.6496V17.7991H25.5191ZM25.5191 20.4129C28.3773 20.4129 30.6874 22.7146 30.6874 25.5625C30.6874 28.4104 28.3773 30.7121 25.5191 30.7121H12.559C9.70073 30.7121 7.39062 28.4104 7.39062 25.5625C7.39062 22.7146 9.70073 20.4129 12.559 20.4129H25.5191Z" fill="#36C5F0" style="fill:#36C5F0;fill:color(display-p3 0.2118 0.7725 0.9412);fill-opacity:1;"/>
<path d="M46.2315 25.5625C46.2315 22.7146 48.5416 20.4129 51.3998 20.4129C54.2581 20.4129 56.5682 22.7146 56.5682 25.5625C56.5682 28.4104 54.2581 30.7121 51.3998 30.7121H46.2315V25.5625ZM43.6473 25.5625C43.6473 28.4104 41.3372 30.7121 38.4789 30.7121C35.6206 30.7121 33.3105 28.4104 33.3105 25.5625V12.6496C33.3105 9.8017 35.6206 7.5 38.4789 7.5C41.3372 7.5 43.6473 9.8017 43.6473 12.6496V25.5625Z" fill="#2EB67D" style="fill:#2EB67D;fill:color(display-p3 0.1804 0.7137 0.4902);fill-opacity:1;"/>
<path d="M38.4789 46.2011C41.3372 46.2011 43.6473 48.5028 43.6473 51.3506C43.6473 54.1985 41.3372 56.5002 38.4789 56.5002C35.6206 56.5002 33.3105 54.1985 33.3105 51.3506V46.2011H38.4789ZM38.4789 43.6263C35.6206 43.6263 33.3105 41.3246 33.3105 38.4767C33.3105 35.6288 35.6206 33.3271 38.4789 33.3271H51.439C54.2972 33.3271 56.6073 35.6288 56.6073 38.4767C56.6073 41.3246 54.2972 43.6263 51.439 43.6263H38.4789Z" fill="#ECB22E" style="fill:#ECB22E;fill:color(display-p3 0.9255 0.6980 0.1804);fill-opacity:1;"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,4 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M58.0259 18.9342C57.3999 16.5971 55.5618 14.7606 53.2231 14.135C48.9884 13 31.9997 13 31.9997 13C31.9997 13 15.0113 13 10.7765 14.135C8.43772 14.7606 6.59979 16.5971 5.97374 18.9342C4.83789 23.1658 4.83789 32 4.83789 32C4.83789 32 4.83789 40.8342 5.97374 45.0659C6.59979 47.4029 8.43772 49.2395 10.7765 49.8649C15.0113 51 31.9997 51 31.9997 51C31.9997 51 48.9884 51 53.2231 49.8649C55.5618 49.2395 57.3999 47.4029 58.0259 45.0659C59.1618 40.8342 59.1618 32 59.1618 32C59.1618 32 59.1572 23.1658 58.0259 18.9342Z" fill="#FF0000" style="fill:#FF0000;fill:color(display-p3 1.0000 0.0000 0.0000);fill-opacity:1;"/>
<path d="M26.5664 40.141L40.6795 31.9995L26.5664 23.8579V40.141Z" fill="white" style="fill:white;fill-opacity:1;"/>
</svg>

After

Width:  |  Height:  |  Size: 839 B

View File

@ -0,0 +1,3 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 11.6667C5 7.98477 7.98477 5 11.6667 5H28.3333C32.0152 5 35 7.98477 35 11.6667V28.3333C35 32.0152 32.0152 35 28.3333 35H11.6667C7.98477 35 5 32.0152 5 28.3333V11.6667ZM17.8452 14.6548C18.4961 15.3057 18.4961 16.361 17.8452 17.0118L14.857 20L17.8452 22.9882C18.4961 23.639 18.4961 24.6943 17.8452 25.3452C17.1943 25.9961 16.139 25.9961 15.4882 25.3452L12.5 22.357C11.1983 21.0553 11.1983 18.9447 12.5 17.643L15.4882 14.6548C16.139 14.0039 17.1943 14.0039 17.8452 14.6548ZM24.5118 14.6548C23.861 14.0039 22.8057 14.0039 22.1548 14.6548C21.5039 15.3057 21.5039 16.361 22.1548 17.0118L25.143 20L22.1548 22.9882C21.5039 23.639 21.5039 24.6943 22.1548 25.3452C22.8057 25.9961 23.861 25.9961 24.5118 25.3452L27.5 22.357C28.8017 21.0553 28.8017 18.9447 27.5 17.643L24.5118 14.6548Z" fill="black" style="fill:black;fill-opacity:1;"/>
</svg>

After

Width:  |  Height:  |  Size: 979 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 107 KiB

View File

@ -0,0 +1,3 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.6667 5.00016C21.6667 4.07969 20.9205 3.3335 20 3.3335C19.0795 3.3335 18.3333 4.07969 18.3333 5.00016V6.66683H10.8333C7.15143 6.66683 4.16667 9.6516 4.16667 13.3335V13.7794C3.17033 14.3558 2.5 15.433 2.5 16.6668C2.5 17.9006 3.17033 18.9779 4.16667 19.5542V26.6668C4.16667 30.3487 7.15143 33.3335 10.8333 33.3335H29.1667C32.8486 33.3335 35.8333 30.3487 35.8333 26.6668V19.5542C36.8297 18.9779 37.5 17.9006 37.5 16.6668C37.5 15.433 36.8297 14.3558 35.8333 13.7794V13.3335C35.8333 9.6516 32.8486 6.66683 29.1667 6.66683H21.6667V5.00016ZM14.1667 14.1668C12.786 14.1668 11.6667 15.2861 11.6667 16.6668C11.6667 18.0475 12.786 19.1668 14.1667 19.1668C15.5474 19.1668 16.6667 18.0475 16.6667 16.6668C16.6667 15.2861 15.5474 14.1668 14.1667 14.1668ZM25.8333 14.1668C24.4526 14.1668 23.3333 15.2861 23.3333 16.6668C23.3333 18.0475 24.4526 19.1668 25.8333 19.1668C27.214 19.1668 28.3333 18.0475 28.3333 16.6668C28.3333 15.2861 27.214 14.1668 25.8333 14.1668ZM16.6667 23.3335C15.7462 23.3335 15 24.0797 15 25.0002C15 25.9206 15.7462 26.6668 16.6667 26.6668H23.3333C24.2538 26.6668 25 25.9206 25 25.0002C25 24.0797 24.2538 23.3335 23.3333 23.3335H16.6667Z" fill="black" style="fill:black;fill-opacity:1;"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,3 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28.334 3.3335C24.6521 3.3335 21.6673 6.31826 21.6673 10.0002C21.6673 10.5323 21.73 11.0509 21.8484 11.5485L14.9239 15.5054C13.706 14.1722 11.9521 13.3335 10.0007 13.3335C6.31875 13.3335 3.33398 16.3183 3.33398 20.0002C3.33398 23.6821 6.31875 26.6668 10.0007 26.6668C11.9521 26.6668 13.706 25.8281 14.9239 24.4949L21.8484 28.4518C21.73 28.9494 21.6673 29.4681 21.6673 30.0002C21.6673 33.6821 24.6521 36.6668 28.334 36.6668C32.0159 36.6668 35.0007 33.6821 35.0007 30.0002C35.0007 26.3183 32.0159 23.3335 28.334 23.3335C26.3826 23.3335 24.6286 24.1722 23.4107 25.5054L16.4862 21.5485C16.6047 21.0509 16.6673 20.5323 16.6673 20.0002C16.6673 19.4681 16.6047 18.9494 16.4862 18.4518L23.4107 14.4949C24.6286 15.8282 26.3826 16.6668 28.334 16.6668C32.0159 16.6668 35.0007 13.6821 35.0007 10.0002C35.0007 6.31826 32.0159 3.3335 28.334 3.3335Z" fill="black" style="fill:black;fill-opacity:1;"/>
</svg>

After

Width:  |  Height:  |  Size: 998 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 KiB

View File

@ -1,13 +1,11 @@
import { useData } from 'nextra/data'
import { format } from 'date-fns'
import { useRouter, useSearchParams } from 'next/navigation'
import Link from 'next/link'
import { Cards } from 'nextra/components'
import { twMerge } from 'tailwind-merge'
const Blog = () => {
const blogPost = useData()
const data = useData()
const searchParams = useSearchParams()
const search = searchParams?.get('category')
const router = useRouter()
@ -24,8 +22,8 @@ const Blog = () => {
]
return (
<div className="nextra-wrap-container py-14">
<div className="w-full mx-auto">
<div className="nextra-wrap-container">
<div className="mt-14 text-center">
<h1 className="text-6xl !fqont-normal leading-tight lg:leading-tight mt-2 font-serif">
Blog
</h1>
@ -43,7 +41,7 @@ const Blog = () => {
</div>
<div className="mt-10">
<ul className="flex lg:gap-4 gap-1 whitespace-nowrap overflow-auto lg:overflow-hidden lg:whitespace-normal">
<ul className="flex lg:gap-4 gap-1 whitespace-nowrap overflow-auto lg:overflow-hidden lg:whitespace-normal justify-center">
<li
onClick={() => {
router.push(`blog/`)
@ -76,63 +74,70 @@ const Blog = () => {
</ul>
</div>
<Cards num={4} className="mt-14 gap-8">
{blogPost
.filter((post: BlogPostsThumbnail) => {
if (search) {
return post.categories?.includes(String(search))
} else {
return post
}
})
.map((post: BlogPostsThumbnail, i: number) => {
return (
<Link
href={String(post.url)}
key={i}
className="nextra-card nx-group nx-flex nx-flex-col nx-justify-start nx-overflow-hidden nx-rounded-xl nx-border nx-border-gray-200 nx-text-current nx-no-underline dark:nx-shadow-none hover:nx-shadow-gray-100 dark:hover:nx-shadow-none nx-shadow-gray-100 active:nx-shadow-sm active:nx-shadow-gray-200 nx-transition-all nx-duration-200 hover:nx-border-gray-300 nx-bg-transparent nx-shadow-sm dark:nx-border-neutral-800 hover:nx-shadow-md dark:hover:nx-border-neutral-700"
>
<div
className={twMerge(
'min-h-40 border-b border-gray-200 dark:border-neutral-800',
i % 2 !== 0
? 'bg-gradient-to-r from-cyan-500 to-blue-500'
: 'bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500'
)}
>
<div className="flex w-full h-full items-center px-4 justify-center">
<div className="text-center">
{post.categories?.map((cat, i) => {
return (
<p
className="inline-flex capitalize text-xl font-bold text-white"
key={i}
>
{cat?.replaceAll('-', ' ')}
</p>
)
})}
<p className="font-medium text-white">
{format(String(post.date), 'MMMM do, yyyy')}
</p>
</div>
<div className="w-full lg:w-3/4 mx-auto text-left">
<div className="mt-20">
{data
?.filter((post: any) => {
if (search) {
return post.categories?.includes(String(search))
} else {
return post
}
})
.map((post: any, i: number) => {
return (
<div key={i} className="flex gap-8 items-start">
<div className="w-3/12 -mt-2">
<p className="text-black/60 dark:text-white/60 font-medium">
{format(post?.date, 'MMMM do, yyyy')}
</p>
</div>
<Link
href={post?.url}
className="border-l dark:nx-border-neutral-800 w-full cursor-pointer"
>
<div className="flex gap-8 items-start w-full">
<div className="w-2 h-2 relative -left-1 bg-blue-500 rounded-full flex-shrink-0" />
<div className="pb-14 w-full -mt-2">
<div className="w-full pb-4 px-8 rounded-lg flex flex-col lg:flex-row justify-between">
<div>
<h6 className="text-lg lg:text-2xl font-bold">
{post?.title}
</h6>
{post?.description && (
<p className="mt-2 text-medium">
{post?.description}
</p>
)}
{post?.categories && (
<div className="mt-2 flex flex-wrap gap-2">
{post.categories.map(
(category: string, idx: number) => (
<span
key={idx}
className="px-2 py-1 text-xs rounded-full bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200"
>
{category.replaceAll('-', ' ')}
</span>
)
)}
</div>
)}
{post?.author && (
<p className="mt-2 text-black/60 dark:text-white/60 text-medium">
By {post?.author}
</p>
)}
</div>
</div>
</div>
</div>
</Link>
</div>
<div className="px-4 py-6">
<h6 className="text-lg line-clamp-1 font-bold">
{post.title}
</h6>
<p className="my-2 text-black/60 dark:text-white/60 line-clamp-2 leading-relaxed">
{post.description}
</p>
<p className="dark:text-blue-400 text-blue-600 line-clamp-2 font-medium">
Read more...
</p>
</div>
</Link>
)
})}
</Cards>
)
})}
</div>
</div>
</div>
</div>
)

View File

@ -0,0 +1,258 @@
/* eslint-disable @next/next/no-img-element */
import { Button } from '@/components/ui/button'
import { motion } from 'framer-motion'
import ChatGPTIcon from '@/assets/icons/ChatGPT.svg'
import ClaudeIcon from '@/assets/icons/Claude.svg'
import GeminiIcon from '@/assets/icons/Gemini.svg'
import MetaIcon from '@/assets/icons/Meta.svg'
import MistralIcon from '@/assets/icons/Mistral AI.svg'
import QwenIcon from '@/assets/icons/Qwen.svg'
import DeepSeekIcon from '@/assets/icons/DeepSeek.svg'
import GemmaIcon from '@/assets/icons/Gemma.svg'
import KimiIcon from '@/assets/icons/Kimi.svg'
import GmailIcon from '@/assets/icons/Gmail.svg'
import AmazonIcon from '@/assets/icons/Amazone.svg'
import GoogleIcon from '@/assets/icons/Google.svg'
import NotionIcon from '@/assets/icons/Notion.svg'
import FigmaIcon from '@/assets/icons/Figma.svg'
import YoutubeIcon from '@/assets/icons/Youtube.svg'
import SlackIcon from '@/assets/icons/Slack.svg'
import GoogleDriveIcon from '@/assets/icons/Google-drive.svg'
import JiraIcon from '@/assets/icons/Jira.svg'
import Avatar from '@/assets/landing/avatar.png'
const models = [
{ name: 'ChatGPT', icon: ChatGPTIcon, company: 'OpenAI' },
{ name: 'Claude', icon: ClaudeIcon, company: 'Anthropic' },
{ name: 'Gemini', icon: GeminiIcon, company: 'Google' },
{ name: 'Llama', icon: MetaIcon, company: 'Meta' },
{ name: 'Mistral', icon: MistralIcon, company: 'Mistral AI' },
{ name: 'Qwen', icon: QwenIcon, company: 'Alibaba' },
{ name: 'DeepSeek', icon: DeepSeekIcon, company: 'DeepSeek' },
{ name: 'Gemma', icon: GemmaIcon, company: 'Google' },
{ name: 'Kimi', icon: KimiIcon, company: 'Moonshot AI' },
]
const apps = [
{ name: 'Gmail', icon: GmailIcon, description: 'Organize your inbox' },
{ name: 'Amazon', icon: AmazonIcon, description: 'Shop for products' },
{ name: 'Google', icon: GoogleIcon, description: 'Search the web' },
{ name: 'Notion', icon: NotionIcon, description: 'Write and organize' },
{ name: 'Figma', icon: FigmaIcon, description: 'Design with AI' },
{ name: 'YouTube', icon: YoutubeIcon, description: 'Look for videos' },
{ name: 'Slack', icon: SlackIcon, description: 'Read channel messages' },
{
name: 'Google Drive',
icon: GoogleDriveIcon,
description: 'Find and fetch files',
},
{ name: 'Jira', icon: JiraIcon, description: 'Manage tickets' },
]
const thingsToRemember = [
'Minimalist UI tasted',
'Currently on a portfolio refresh',
'Wants brief, to-the-point answers',
'Frequent Figma/prototyping questions',
'Dark-mode sharer',
'Curious about type trends (Mostly harmless)',
]
export default function FavoriteModels() {
return (
<section className="container mx-auto pt-20 pb-0 md:pb-20">
<div className="max-w-6xl mx-auto px-4">
<motion.h2
className="text-4xl max-w-sm font-bold mb-20 -tracking-[1.2px]"
initial={{ opacity: 0, y: 60 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.3 }}
transition={{ duration: 0.6, delay: 0.2 }}
>
Best of open-source AI in one app
</motion.h2>
{/* Step 1: Use any model you want */}
<motion.div
className="mb-16"
initial={{ opacity: 0, y: 60 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.3 }}
transition={{ duration: 0.6, delay: 0.4 }}
>
<div className="flex items-start gap-8 flex-col lg:flex-row">
<div className="flex-1">
<div className="text-[28px] mb-8 size-12 bg-white text-black rounded-full flex items-center justify-center font-semibold shadow-[0px_4px_0px_0px_rgba(0,0,0,1)] border-2 border-black">
1
</div>
<h3 className="text-[28px] font-bold mb-2 max-w-sm -tracking-[0.7px]">
Models
</h3>
<p className="text-gray-700 mb-4 max-w-sm text-base -tracking-[0.4px]">
Choose from open models or plug in your favorite online models.
</p>
{/* <Button
variant="playful-white"
className="!rounded-[12px] border-2 shadow-[0px_2px_0px_0px_rgba(0,0,0,1)] text-base h-[40px]"
>
Explore models
</Button> */}
</div>
<div className="flex-1 w-full">
<div className="grid grid-cols-3">
{models.map((model, index) => (
<div
key={model.name}
className="flex flex-col items-center border-b py-8"
>
<div className="size-11 mb-2 flex items-center justify-center">
<img src={model.icon.src} alt={model.name} />
</div>
<span className="text-lg font-medium -tracking-[0.4px]">
{model.name}
</span>
{model.company && (
<span className="text-sm text-gray-500 -tracking-[0.4px]">
{model.company}
</span>
)}
</div>
))}
</div>
</div>
</div>
</motion.div>
{/* Step 2*/}
<motion.div
className="mb-16"
initial={{ opacity: 0, y: 60 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.3 }}
transition={{ duration: 0.6, delay: 0.6 }}
>
<div className="flex items-start gap-8 flex-col lg:flex-row">
<div className="flex-1">
<div className="text-[28px] mb-8 size-12 bg-white text-black rounded-full flex items-center justify-center font-semibold shadow-[0px_4px_0px_0px_rgba(0,0,0,1)] border-2 border-black">
2
</div>
<h3 className="text-[28px] font-bold mb-2 max-w-sm -tracking-[0.7px]">
Connectors
</h3>
<p className="text-gray-700 mb-4 max-w-sm text-base -tracking-[0.4px]">
Connect your email, files, notes and calendar. Jan works where
you work.
</p>
{/* <Button
variant="playful-white"
className="!rounded-[12px] border-2 shadow-[0px_2px_0px_0px_rgba(0,0,0,1)] text-base h-[40px]"
>
Explore tools
</Button> */}
</div>
<div className="flex-1 w-full">
<div className="grid grid-cols-3">
{apps.map((app) => (
<div
key={app.name}
className="flex flex-col items-center border-b py-8"
>
<div className="size-11 mb-2 flex items-center justify-center">
<img src={app.icon.src} alt={app.name} />
</div>
<span className="text-lg font-medium -tracking-[0.4px]">
{app.name}
</span>
<span className="text-sm text-gray-500 -tracking-[0.4px]">
{app.description}
</span>
</div>
))}
</div>
</div>
</div>
</motion.div>
{/* Step 3: Cross-platform */}
<motion.div
className="mb-16"
initial={{ opacity: 0, y: 60 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.3 }}
transition={{ duration: 0.6, delay: 0.8 }}
>
<div className="flex items-start gap-8 flex-col lg:flex-row">
<div className="flex-1">
<div className="text-[28px] mb-8 size-12 bg-white text-black rounded-full flex items-center justify-center font-semibold shadow-[0px_4px_0px_0px_rgba(0,0,0,1)] border-2 border-black">
3
</div>
<h3 className="text-[28px] font-bold mb-2 max-w-sm -tracking-[0.7px] inline-flex items-center gap-2">
Memory{' '}
<span className="text-sm bg-gray-200 border border-gray-300 px-2 py-1 rounded-3xl">
Coming Soon
</span>
</h3>
<p className="text-gray-700 mb-4 max-w-sm text-base -tracking-[0.4px]">
Your context carries over, so you dont repeat yourself. Jan
remembers your context and preferences.
</p>
{/* <Button
variant="playful-white"
className="!rounded-[12px] border-2 shadow-[0px_2px_0px_0px_rgba(0,0,0,1)] text-base h-[40px]"
>
Learn more
</Button> */}
</div>
<div className="flex-1 w-full flex justify-center mt-10 px-8 md:px-0">
<div className="relative max-w-xs scale-[70%] md:scale-100 origin-center">
{/* Layered cards background effect */}
<div className="absolute -inset-2 bg-purple-200 rounded-[56px] transform rotate-16 border-2 border-black shadow-[0px_3px_0px_0px_rgba(0,0,0,1)] w-[380px]"></div>
<div className="absolute -inset-1 bg-green-200 rounded-[56px] transform rotate-6 border-2 border-black shadow-[0px_3px_0px_0px_rgba(0,0,0,1)] w-[380px]"></div>
{/* Main card */}
<div className="relative -rotate-3 -ml-10 bg-yellow-200 rounded-[56px] py-8 border-2 border-black shadow-[0px_3px_0px_0px_rgba(0,0,0,1)] z-10 w-[385px]">
{/* User profile section */}
<div className="flex items-center gap-4 mb-6 px-4">
<div className="size-24 flex items-center justify-center overflow-hidden">
<img
src={Avatar.src}
className="w-full h-full object-cover"
alt="Joe's avatar"
/>
</div>
<div>
<h4 className="text-[26px] font-bold text-black">Joe</h4>
<p className="text-gray-700 text-xl -tracking-[0.4px]">
Designer, Singapore
</p>
</div>
</div>
{/* Things to remember section */}
<div className="">
<h5 className="text-xl font-bold text-black mb-4 px-4">
Things Jan keeps in mind
</h5>
<ul className="space-y-2 text-sm text-gray-800">
{thingsToRemember.map((item) => (
<li
key={item}
className="flex items-start gap-2 border-b border-black/10 px-4 text-black/60"
>
<span className="font-bold text-2xl -mt-0.5"></span>
<span className="text-[20px] font-medium">
{item}
</span>
</li>
))}
</ul>
</div>
</div>
</div>
</div>
</div>
</motion.div>
</div>
</section>
)
}

View File

@ -1,136 +1,54 @@
import React, { useEffect, useState } from 'react'
import ThemeImage from '@/components/ThemeImage'
import { AiOutlineGithub } from 'react-icons/ai'
import { RiTwitterXFill } from 'react-icons/ri'
import { BiLogoDiscordAlt } from 'react-icons/bi'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import LogoMark from '@/components/LogoMark'
import { FaLinkedin } from 'react-icons/fa'
import posthog from 'posthog-js'
const socials = [
{
icon: (
<RiTwitterXFill className="text-lg text-black/60 dark:text-white/60" />
),
href: 'https://twitter.com/jandotai',
},
{
icon: (
<BiLogoDiscordAlt className="text-xl text-black/60 dark:text-white/60" />
),
href: 'https://discord.com/invite/FTk2MvZwJH',
},
{
icon: (
<AiOutlineGithub className="text-lg text-black/60 dark:text-white/60" />
),
href: 'https://github.com/menloresearch/jan',
},
{
icon: <FaLinkedin className="text-lg text-black/60 dark:text-white/60" />,
href: 'https://www.linkedin.com/company/homebrewltd',
},
]
type FooterLink = {
name: string
href: string
comingSoon?: boolean
}
const menus = [
// {
// name: 'Product',
// child: [
// {
// menu: 'Download',
// path: '/download',
// },
// {
// menu: 'Changelog',
// path: '/changelog',
// },
// ],
// },
// {
// name: 'For Developers',
// child: [
// {
// menu: 'Documentation',
// path: '/docs',
// },
// ],
// },
type FooterMenu = {
title: string
links: FooterLink[]
}
const FOOTER_MENUS: FooterMenu[] = [
{
name: 'Community',
child: [
{
menu: 'Github',
path: 'https://github.com/menloresearch/jan',
external: true,
},
{
menu: 'Discord',
path: 'https://discord.gg/FTk2MvZwJH',
external: true,
},
{
menu: 'X/Twitter',
path: 'https://twitter.com/jandotai',
external: true,
},
{
menu: 'LinkedIn',
path: 'https://www.linkedin.com/company/menloresearch',
external: true,
},
title: 'Company',
links: [
{ name: 'Vision', href: '/', comingSoon: true },
{ name: 'Handbook', href: '/handbook' },
{ name: 'Community', href: 'https://discord.com/invite/FTk2MvZwJH' },
{ name: 'Careers', href: 'https://menlo.bamboohr.com/careers' },
],
},
{
name: 'Company',
child: [
{
menu: 'Menlo',
path: 'https://menlo.ai',
},
{
menu: 'Blog',
path: '/blog',
},
{
menu: 'Careers',
path: 'https://menlo.bamboohr.com/careers',
external: true,
},
title: 'Resources',
links: [
{ name: 'Blog', href: '/blog' },
{ name: 'Docs', href: '/docs' },
{ name: 'Changelog', href: '/changelog' },
{ name: 'API Reference', href: '/api-reference' },
{ name: 'Jan Exam', href: '/', comingSoon: true },
],
},
{
title: 'Store',
links: [
{ name: 'Model Store', href: '/', comingSoon: true },
{ name: 'MCP Store', href: '/', comingSoon: true },
],
},
]
const getCurrentYear = new Date().getFullYear()
export default function Footer() {
useEffect(() => {
if (typeof window !== 'undefined') {
posthog.init(process.env.POSTHOG_KEY as string, {
api_host: process.env.POSTHOG_HOST,
disable_session_recording: true,
person_profiles: 'always',
persistence: 'localStorage',
})
posthog.capture('web_page_view', { timestamp: new Date() })
}
}, [])
const { register, handleSubmit, reset } = useForm({
defaultValues: {
email: '',
},
})
const [formMessage, setFormMessage] = useState('')
const { register, handleSubmit, reset } = useForm<{ email: string }>()
const onSubmit = (data: { email: string }) => {
const { email } = data
const options = {
method: 'POST',
body: JSON.stringify({
updateEnabled: false,
email,
@ -157,113 +75,97 @@ export default function Footer() {
}
return (
<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-12">
<div className="col-span-2 lg:col-span-3">
<div className="flex items-center space-x-2 mb-3">
<LogoMark />
<h2 className="text-lg font-semibold dark:text-white text-black">
Jan
</h2>
</div>
<div className="w-full lg:w-3/4 mt-2">
<h6 className="text-base text-black dark:text-white">
The Soul of a New Machine
</h6>
<p className="dark:text-gray-400 text-gray-600 mt-2">
Subscribe to our newsletter on AI&nbsp;
<br className="hidden lg:block py-2 h-2 w-full" />
research and building Jan:
</p>
<div className="mt-4">
<form className="relative" onSubmit={handleSubmit(onSubmit)}>
<input
type="email"
className="lg:ml-0.5 w-full h-12 p-4 pr-14 rounded-xl bg-white border dark:border-gray-600 dark:bg-[#252525] border-[#F0F0F0] focus-visible:ring-0"
placeholder="Enter your email"
autoComplete="off"
{...register('email')}
/>
<button
type="submit"
className="absolute flex p-2 bg-black dark:bg-[#3B3B3C] w-8 h-8 border dark:border-gray-600 rounded-lg top-1/2 right-3 -translate-y-1/2"
>
<footer className="py-4 w-full">
<div className="mx-auto">
<div className="grid grid-cols-1 md:grid-cols-6 gap-8">
{/* Jan Logo and Newsletter */}
<div className="md:col-span-2">
<h2 className="text-[52px] font-bold mb-6">Jan</h2>
<div>
<div className="flex items-center gap-2 mb-3">
<span>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6.09026 8.41933L3.72077 7.62985C1.24061 6.80348 0 6.3903 0 5.63033C0 4.87142 1.24061 4.45718 3.72077 3.63081L12.6938 0.639442C14.4393 0.0576106 15.3121 -0.233305 15.7727 0.227312C16.2333 0.687928 15.9424 1.56068 15.3616 3.30512L12.3692 12.2792C11.5428 14.7594 11.1296 16 10.3697 16C9.61076 16 9.19652 14.7594 8.37015 12.2792L7.57962 9.9108L12.1689 5.3215C12.3609 5.1227 12.4672 4.85645 12.4648 4.58008C12.4624 4.30372 12.3515 4.03935 12.1561 3.84392C11.9607 3.64849 11.6963 3.53764 11.4199 3.53524C11.1435 3.53284 10.8773 3.63908 10.6785 3.83108L6.09026 8.41933Z"
fill="white"
d="M2.03057 6.80498C2.04526 6.62516 2.06803 6.44579 2.10662 6.26806L10.4642 11.2488C11.4104 11.8127 12.5896 11.8127 13.5358 11.2488L21.8934 6.26806C21.932 6.44579 21.9547 6.62516 21.9694 6.80498C22 7.17954 22 7.6343 22 8.16144L22 14C22 12.3431 20.6569 11 19 11C17.3431 11 16 12.3431 16 14C14.3432 14 13 15.3431 13 17C13 18.6561 14.3418 19.9987 15.9976 20H6.16136C5.63428 20 5.17951 20 4.80498 19.9694C4.40963 19.9371 4.01641 19.8658 3.63803 19.673C3.07355 19.3854 2.6146 18.9265 2.32698 18.362C2.13419 17.9836 2.06287 17.5904 2.03057 17.195C1.99997 16.8205 1.99999 16.3657 2 15.8386V8.16142C1.99999 7.63432 1.99997 7.17952 2.03057 6.80498Z"
fill="black"
/>
<path
d="M20.362 4.32698C20.5139 4.40441 20.6583 4.49426 20.7936 4.59523L12.5119 9.53079C12.1965 9.71876 11.8035 9.71876 11.4881 9.53079L3.20637 4.59524C3.34175 4.49426 3.48607 4.40441 3.63803 4.32698C4.01641 4.13419 4.40963 4.06287 4.80498 4.03057C5.17953 3.99997 5.6343 3.99998 6.1614 4H17.8386C18.3657 3.99998 18.8205 3.99997 19.195 4.03057C19.5904 4.06287 19.9836 4.13419 20.362 4.32698Z"
fill="black"
/>
<path
d="M19 13C19.5523 13 20 13.4477 20 14V16H22C22.5523 16 23 16.4477 23 17C23 17.5523 22.5523 18 22 18H20V20C20 20.5523 19.5523 21 19 21C18.4477 21 18 20.5523 18 20V18H16C15.4477 18 15 17.5523 15 17C15 16.4477 15.4477 16 16 16H18V14C18 13.4477 18.4477 13 19 13Z"
fill="black"
/>
</svg>
</button>
</span>
<p className="text-base font-bold">
Subscribe to our newsletter
</p>
</div>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="relative">
<input
type="email"
placeholder="Enter your email"
{...register('email', { required: true })}
className="flex-1 px-3 py-2 h-[56px] border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 w-full rounded-xl text-base"
/>
<div className="absolute top-1 right-1 h-full">
<button
type="submit"
className="bg-black text-white px-6 h-[calc(100%-8px)] text-base hover:bg-gray-800 rounded-lg font-medium"
>
Submit
</button>
</div>
</div>
{formMessage && (
<p className="mt-2 text-sm text-green-600">{formMessage}</p>
)}
</form>
{formMessage && <p className="text-left mt-4">{formMessage}</p>}
</div>
</div>
</div>
<div className="hidden lg:block lg:col-span-3"></div>
{menus.map((menu, i) => {
return (
<div key={i} className="lg:text-right lg:col-span-3">
<h2 className="mb-2 font-bold dark:text-gray-300 text-black">
{menu.name}
</h2>
<ul>
{menu.child.map((child, i) => {
return (
<li key={i}>
<a
href={child.path}
target={child.external ? '_blank' : '_self'}
className="inline-block pt-3"
>
{child.menu}
</a>
</li>
)
})}
<div className="md:col-span-1"></div>
{/* Menu Columns */}
{FOOTER_MENUS.map((menu) => (
<div key={menu.title} className="">
<h3 className="text-lg mb-4 font-bold">{menu.title}</h3>
<ul className="space-y-2">
{menu.links.map((link) => (
<li key={link.name}>
<a
href={link.href}
className="text-base text-gray-600 hover:text-gray-900"
target={link.name === 'Discord' ? '_blank' : undefined}
rel={
link.name === 'Discord'
? 'noopener noreferrer'
: undefined
}
>
{link.name}
{link.comingSoon && (
<span className="text-xs ml-2 bg-gray-200 border border-gray-300 px-1 py-0.5 rounded-3xl">
Coming Soon
</span>
)}
</a>
</li>
))}
</ul>
</div>
)
})}
</div>
<div className="mt-10">
<div className="flex w-full justify-between items-center flex-col md:flex-row gap-4">
<div className="flex items-center gap-x-3">
{socials.map((social, i) => {
return (
<a
aria-label={`social-${i}`}
key={i}
href={social.href}
target="_blank"
rel="noopener"
>
{social.icon}
</a>
)
})}
</div>
<span>&copy;{getCurrentYear}&nbsp;Menlo Research</span>
<ThemeImage
source={{
light: '/assets/images/general/menlo.svg',
dark: '/assets/images/general/menlo.svg',
}}
alt="App screenshots"
width={80}
height={200}
/>
))}
</div>
</div>
</div>
</footer>
)
}

View File

@ -1,29 +1,623 @@
import { Fragment } from 'react'
'use client'
/* eslint-disable @next/next/no-img-element */
import { Fragment, useEffect } from 'react'
import { FaDiscord, FaGithub } from 'react-icons/fa'
import HuggingFaceSVG from '@/assets/icons/huggingface.svg'
import CuteRobotBgMountainPNG from '@/assets/landing/cute-robot-bg-mountain.png'
import { Button } from '@/components/ui/button'
import CodeSVG from '@/assets/icons/code.svg'
import { IoMdPeople } from 'react-icons/io'
import CuteBuildingRobotPNG from '@/assets/landing/cute-building-robot.png'
import CuteRobotFlyingPNG from '@/assets/landing/cute-robot-flying.png'
import ShareSVG from '@/assets/icons/share-android.svg'
import RobotSVG from '@/assets/icons/robot.svg'
import LogoJanSVG from '@/assets/icons/logo-jan.svg'
import AppJanPNG from '@/assets/landing/app-jan.png'
import TweetSection from '@/components/TweetSection'
import FavoriteModels from '@/components/FavoriteModels'
import { DropdownButton } from '@/components/ui/dropdown-button'
import Hero from '@/components/Home/Hero'
import BuiltWithLove from '@/components/Home/BuiltWithLove'
import WallOfLove from '@/components/Home/WallOfLove'
import Feature from '@/components/Home/Feature'
import Principles from './Principles'
import CTANewsletter from './CTANewsletter'
import Statistic from './Statistic'
import CTADownload from './CTADownload'
import Customizable from './Customizable'
// import APIStructure from './APIStructure'
import { useData } from 'nextra/data'
import { useDiscordWidget } from '@/hooks/useDiscordWidget'
import { formatCompactNumber, totalDownload } from '@/utils/format'
const Home = () => {
const { lastVersion, lastRelease, stars, release } = useData()
const { data: discordWidget } = useDiscordWidget()
useEffect(() => {
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px',
}
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const element = entry.target as HTMLElement
const delay = element.dataset.delay || '0'
setTimeout(() => {
element.classList.add('animate-in-view')
}, parseInt(delay))
observer.unobserve(element)
}
})
}, observerOptions)
// Observe all scroll-triggered animation elements
const animatedElements = document.querySelectorAll(
'.animate-on-scroll, .animate-on-scroll-left, .animate-on-scroll-right, .animate-on-scroll-scale, .animate-slide-up'
)
animatedElements.forEach((element) => {
observer.observe(element)
})
// Simple parallax effect for robot images
const handleScroll = () => {
const parallaxElements = document.querySelectorAll('.parallax-element')
parallaxElements.forEach((el) => {
const element = el as HTMLElement
const rect = element.getBoundingClientRect()
// Only apply parallax when element is visible
if (rect.top < window.innerHeight && rect.bottom > 0) {
const speed = parseFloat(element.getAttribute('data-speed') || '0.3')
// Simple calculation: how far the element has moved into/through viewport
const progress = Math.min(
1,
Math.max(0, (window.innerHeight - rect.top) / window.innerHeight)
)
// Move from 0 to -40px based on progress
const yPos = Math.round(progress * -100 * speed)
element.style.transform = `translateY(${yPos}px)`
}
})
}
window.addEventListener('scroll', handleScroll)
// Cleanup function
return () => {
observer.disconnect()
window.removeEventListener('scroll', handleScroll)
}
}, [])
return (
<Fragment>
<Hero />
<BuiltWithLove />
<Feature />
{/* Hero */}
<section className="px-3 pt-3">
<div className="bg-[#458edf] relative py-10 h-[760px] md:h-[900px] 2xl:h-[1080px] rounded-2xl overflow-hidden">
<div className="container mx-auto relative z-10">
<div className="flex justify-center items-center mt-14 lg:mt-20 px-4">
<a
href=""
target="_blank"
rel="noopener noreferrer"
className="bg-black/40 px-3 lg:px-4 rounded-full h-10 inline-flex items-center max-w-full animate-fade-in delay-100"
>
<span className="bg-black/20 border border-neutral-700 mr-2 -ml-1 rounded-full px-2 text-white font-medium text-xs lg:text-sm">
NEW
</span>
<span className="text-white text-xs md:text-sm lg:text-base truncate font-medium">
<span className="font-medium">{lastVersion}</span> is now
live on GitHub. Check it out!
</span>
</a>
</div>
<div className="mt-10">
<div className="text-center relative lg:w-1/2 mx-auto">
<div className="flex flex-col lg:flex-row items-center justify-center gap-4 animate-fade-in-up delay-300">
<span>
<img
src={LogoJanSVG.src}
alt="Logo Jan"
className="size-20 animate-wave"
/>
</span>
<h1 className="text-[40px] lg:text-[80px] font-semibold -tracking-[2px] text-white">
Ask Jan
</h1>
</div>
<p className="px-4 lg:px-0 mt-2 text-lg lg:text-2xl font-medium leading-relaxed text-white animate-fade-in-up delay-500 -tracking-[0.6px]">
Jan is the open-source ChatGPT replacement.
</p>
</div>
<div className="flex px-4 flex-col lg:flex-row items-center gap-4 w-full justify-center text-center animate-fade-in-up delay-600 mt-8 lg:mt-10">
<DropdownButton
size="xxl"
className="w-full !rounded-[20px] lg:w-auto"
lastRelease={lastRelease}
/>
<a
href="https://discord.com/invite/FTk2MvZwJH"
target="_blank"
rel="noopener noreferrer"
className="w-full lg:w-auto"
>
<Button
variant="playful-white"
size="xxl"
className="!w-full lg:w-auto !items-center border-2"
>
<FaDiscord className="size-5 text-[#5765F2]" />
Join community
<div className="flex items-center gap-1 ml-3">
<svg
width="19"
height="18"
viewBox="0 0 19 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_1299_2107)">
<path
d="M3.12524 5.25C3.12524 3.59315 4.46839 2.25 6.12524 2.25C7.7821 2.25 9.12524 3.59315 9.12524 5.25C9.12524 6.90685 7.7821 8.25 6.12524 8.25C4.46839 8.25 3.12524 6.90685 3.12524 5.25Z"
fill="black"
fillOpacity={1}
/>
<path
d="M9.87524 5.25C9.87524 3.59315 11.2184 2.25 12.8752 2.25C14.5321 2.25 15.8752 3.59315 15.8752 5.25C15.8752 6.90685 14.5321 8.25 12.8752 8.25C11.2184 8.25 9.87524 6.90685 9.87524 5.25Z"
fill="black"
fillOpacity={1}
/>
<path
d="M6.12504 9C8.72805 9 11.105 11.1426 11.3732 14.9473L11.4298 15.75H0.820312L0.876899 14.9473C1.14509 11.1426 3.52204 9 6.12504 9Z"
fill="black"
fillOpacity={1}
/>
<path
d="M18.1796 15.75H12.9333L12.8693 14.8418C12.7141 12.6398 11.9201 10.8076 10.7219 9.52435C11.3905 9.17864 12.1233 9 12.8749 9C15.4779 9 17.8548 11.1426 18.123 14.9473L18.1796 15.75Z"
fill="black"
fillOpacity={1}
/>
</g>
<defs>
<clipPath id="clip0_1299_2107">
<rect
width="18"
height="18"
fill="white"
fillOpacity={1}
transform="translate(0.5)"
/>
</clipPath>
</defs>
</svg>
<span className="text-sm">
{formatCompactNumber(discordWidget.presence_count)}
</span>
</div>
</Button>
</a>
</div>
</div>
</div>
<div className="absolute w-full bottom-0 left-0 flex justify-center">
<img
className="abs animate-float scale-[175%] md:scale-100"
src={CuteRobotFlyingPNG.src}
alt=""
/>
</div>
</div>
<div className="hidden size-4/5 xl:size-3/5 bg-black rounded-[20px] mx-auto relative -mt-40 lg:flex p-3 animate-scale-in delay-300">
<div className="rounded-md size-full overflow-hidden">
<img
src={AppJanPNG.src}
alt="Jan App Interface"
className="w-full h-full object-fit"
/>
</div>
</div>
<div className="lg:hidden size-full bg-black rounded-2xl mx-auto relative mt-10 flex p-2 animate-scale-in delay-300">
<div className="rounded-lg size-full overflow-hidden">
<img
src={AppJanPNG.src}
alt="Jan App Interface"
className="w-full h-full object-fit"
/>
</div>
</div>
</section>
{/* Statistic and social */}
<section className="pt-20">
<div className="container mx-auto">
<h2 className="text-[24px] lg:text-[52px] font-semibold text-center mb-16 -tracking-[1.3px]">
Over 4 million downloads
</h2>
</div>
<TweetSection />
</section>
{/* Social tech */}
<section className="px-3 mt-20">
<div className="bg-[#C6E09E] px-4 relative py-10 h-[640px] sm:h-[800px] lg:h-[900px] 2xl:h-[1040px] rounded-2xl overflow-hidden">
<div className="container mx-auto relative z-10">
<div className="md:mt-10">
<div className="lg:w-3/5 mx-auto">
<div className="relative text-center md:text-left">
<h1
className="text-4xl lg:text-[50px] font-semibold -tracking-[1.3px] animate-on-scroll leading-tight"
data-delay="200"
>
Towards Open Superintelligence
</h1>
<p
className="-tracking-[0.6px] mt-4 text-xl text-neutral-700 animate-on-scroll lg:max-w-[512px]"
data-delay="400"
>
Jan takes the best of open source AI and packages it into an
easy-to-use product.
</p>
</div>
<div
className="mt-8 flex flex-col md:flex-row gap-4 animate-on-scroll"
data-delay="600"
>
<a
href="https://github.com/menloresearch/jan"
target="_blank"
rel="noopener noreferrer"
>
<Button
variant="playful"
className="bg-[#2C2C2C] py-2 hover:bg-[#1a1a1a] text-white h-18 pl-2 justify-start w-full md:w-60 transition-colors duration-200"
size="xxl"
>
<span className="bg-white text-black flex items-center justify-center w-14 h-14 rounded-lg mr-2">
<FaGithub className="size-8" />
</span>
<span className="flex items-start flex-col">
<span className="font-bold text-lg">Github</span>
<span className="text-sm mt-1">
{formatCompactNumber(stars)} stars
</span>
</span>
</Button>
</a>
<a
href="https://discord.com/invite/FTk2MvZwJH"
target="_blank"
rel="noopener noreferrer"
>
<Button
variant="playful"
className="bg-[#5765F2] py-2 border-2 hover:bg-[#5765F2] text-white h-18 pl-2 justify-start w-full md:w-60 transition-colors duration-200"
size="xxl"
>
<span className="bg-white text-black flex items-center justify-center w-14 h-14 rounded-lg mr-2">
<FaDiscord className="size-8 text-[#5765F2]" />
</span>
<span className="flex items-start flex-col">
<span className="font-bold text-lg">Discord</span>
<span className="text-sm mt-1">
{formatCompactNumber(discordWidget.presence_count)}{' '}
Online
</span>
</span>
</Button>
</a>
<a
href="https://huggingface.co/janhq"
target="_blank"
rel="noopener noreferrer"
>
<Button
variant="playful"
className="bg-[#FFD21E] py-2 border-2 hover:bg-[#e6bd1b] text-black h-18 pl-2 justify-start w-full md:w-60 transition-colors duration-200"
size="xxl"
>
<span className="bg-white text-black flex items-center justify-center w-14 h-14 rounded-lg mr-2">
<img
src={HuggingFaceSVG.src}
alt="Hugging Face"
className="size-8"
/>
</span>
<span className="flex items-start flex-col">
<span className="font-bold text-lg">HuggingFace</span>
<span className="text-sm mt-1">71 models</span>
</span>
</Button>
</a>
</div>
</div>
</div>
</div>
<div className="absolute w-full bottom-0 left-0 flex justify-center">
<img
className="animate-on-scroll"
data-delay="800"
src={CuteRobotBgMountainPNG.src}
alt=""
/>
</div>
</div>
</section>
{/* Favorite Models Section */}
<FavoriteModels />
{/* Developer Community */}
<section className="px-3 pt-3">
<div className="bg-[#93B3EF] lg:h-[1000px] relative pb-16 pt-8 md:pt-16 rounded-2xl overflow-hidden">
<div className="container mx-auto relative z-10">
<div className="text-center text-black my-12 mt-10">
<h2
className="-tracking-[1.3px] text-4xl lg:text-[52px] font-bold mb-4 leading-normal animate-on-scroll"
data-delay="200"
>
Built in Public
</h2>
<p
className="text-[24px] -tracking-[0.6px] max-w-2xl mx-auto animate-on-scroll text-black/70 font-medium"
data-delay="400"
>
Our core team believes that AI should be open,{' '}
<br className="hidden md:block" /> and Jan is built in public.
</p>
</div>
<div className="max-w-4xl mx-auto px-4">
<div
className="bg-white rounded-lg shadow-[0px_4px_0px_0px_rgba(0,0,0,1)] border border-black animate-on-scroll-scale"
data-delay="600"
>
<div className="p-6 space-y-6">
<div className="flex flex-col items-start gap-4 border-b border-neutral-400 pb-4">
<div className="flex justify-between w-full">
<div className="w-8 h-8 rounded flex items-center justify-center flex-shrink-0">
<span className="text-white text-sm">
<img src={CodeSVG.src} alt="" />
</span>
</div>
<a
className="hidden md:block"
href="https://github.com/menloresearch/jan"
target="_blank"
rel="noopener noreferrer"
>
<Button
variant="playful"
className="!rounded-lg border-2 h-[40px]"
>
<span>
<FaGithub className="size-4" />
</span>
<span className="text-base">Go to GitHub</span>
</Button>
</a>
</div>
<div className="flex-1">
<div className="flex items-center justify-between mb-1">
<h3 className="font-semibold text-gray-900 text-[28px] -tracking-[0.7px]">
Develop
</h3>
</div>
<p className="text-gray-500 font-medium text-lg -tracking-[0.6px]">
Submit PRs for UI, tooling, or edge optimizations.
</p>
<a
className="md:hidden mt-4 block w-full"
href="https://github.com/menloresearch/jan"
target="_blank"
rel="noopener noreferrer"
>
<Button
variant="playful"
className="!rounded-lg border-2 h-[40px] w-full"
>
<span>
<FaGithub className="size-6" />
</span>
<span className="text-base">Go to GitHub</span>
</Button>
</a>
</div>
</div>
<div className="flex flex-col items-start gap-4 border-b border-neutral-400 pb-4">
<div className="flex justify-between w-full">
<div className="w-8 h-8 rounded flex items-center justify-center flex-shrink-0">
<span className="text-white text-sm">
<img src={ShareSVG.src} alt="" />
</span>
</div>
<a
className="hidden md:block"
href="https://discord.com/invite/FTk2MvZwJH"
target="_blank"
rel="noopener noreferrer"
>
<Button
variant="playful"
className="!rounded-lg border-2 h-[40px]"
>
<span>
<FaDiscord className="size-4 text-[#5765F2]" />
</span>
<span className="text-base">Join Community</span>
<div className="flex items-center gap-1 ml-3">
<IoMdPeople className="size-5" />
<span className="text-sm">
{formatCompactNumber(
discordWidget.presence_count
)}
</span>
</div>
</Button>
</a>
</div>
<div className="flex-1">
<div className="flex items-center justify-between mb-1">
<h3 className="font-semibold text-gray-900 text-[28px] -tracking-[0.7px]">
Share
</h3>
</div>
<p className="text-gray-500 font-medium text-lg -tracking-[0.6px]">
Spread the word, write tutorials, host meet-ups, post
videos.
</p>
<a
className="md:hidden block mt-4 w-full"
href="https://discord.com/invite/FTk2MvZwJH"
target="_blank"
rel="noopener noreferrer"
>
<Button
variant="playful"
className="!rounded-lg w-full border-2 h-[40px]"
>
<span>
<FaDiscord className="size-6 text-[#5765F2]" />
</span>
<span className="text-base">Join Community</span>
<div className="flex items-center gap-1 ml-3">
<IoMdPeople className="size-5" />
<span className="text-sm">
{formatCompactNumber(
discordWidget.presence_count
)}
</span>
</div>
</Button>
</a>
</div>
</div>
<div className="flex flex-col items-start gap-4 pb-4">
<div className="flex justify-between w-full">
<div className="w-8 h-8 rounded flex items-center justify-center flex-shrink-0">
<span className="text-white text-sm">
<img src={RobotSVG.src} alt="" />
</span>
</div>
<a
className="hidden md:block"
href="https://huggingface.co/janhq"
target="_blank"
rel="noopener noreferrer"
>
<Button
variant="playful"
className="!rounded-lg border-2 h-[40px]"
>
<span>
<img
src={HuggingFaceSVG.src}
alt="Hugging Face"
className="size-4"
/>
</span>
<span className="text-base">Check HuggingFace</span>
</Button>
</a>
</div>
<div className="flex-1">
<div className="flex items-center justify-between mb-1">
<h3 className="font-semibold text-gray-900 text-[28px] -tracking-[0.7px]">
Train
</h3>
</div>
<p className="text-gray-500 font-medium text-lg -tracking-[0.6px]">
Add evals, safety tests, or training recipes.
</p>
<a
className="md:hidden block mt-4 w-full"
href="https://huggingface.co/janhq"
target="_blank"
rel="noopener noreferrer"
>
<Button
variant="playful"
className="!rounded-lg border-2 h-[40px] w-full"
>
<span>
<img
src={HuggingFaceSVG.src}
alt="Hugging Face"
className="size-6"
/>
</span>
<span className="text-base">Check HuggingFace</span>
</Button>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="absolute w-full bottom-0 flex justify-center">
<img
className="abs animate-on-scroll"
data-speed="0.5"
data-delay="800"
src={CuteBuildingRobotPNG.src}
alt=""
/>
</div>
</div>
</section>
{/* Call to action */}
<section className="px-3 pt-3">
<div className="bg-[#458edf] relative py-10 h-[480px] lg:h-[650px] rounded-2xl overflow-hidden">
<div className="w-full lg:w-3/5 mx-auto">
<div className="container relative z-10">
<div className="mt-10 flex flex-col lg:flex-row justify-between items-center gap-8">
<div className="relative animate-on-scroll" data-delay="200">
<h1 className="text-4xl text-center lg:text-left lg:text-[64px] mx-auto font-semibold -tracking-[1.6px] text-white">
Ask Jan,
<br />
<br />
get things done
</h1>
</div>
<div
className="bg-white flex flex-col p-0.5 rounded-2xl pb-2 shadow-[0px_4px_0px_0px_rgba(0,0,0,1)] border-2 border-black relative animate-on-scroll-scale"
data-delay="400"
>
<DropdownButton
size="xl"
className="w-full lg:w-auto"
classNameButton="!shadow-none border-2"
/>
<span className="text-xs font-medium text-center mt-2">
+{totalDownload(release)} downloads, Free & Open source
</span>
</div>
</div>
</div>
</div>
<div className="absolute w-full lg:-bottom-30 bottom-0 flex justify-center">
<img
className="abs animate-float parallax-element scale-[175%] md:scale-100"
data-speed="0.3"
src={CuteRobotFlyingPNG.src}
alt=""
/>
</div>
</div>
</section>
{/* <BuiltWithLove /> */}
{/* <Feature /> */}
{/* <APIStructure /> */}
<Customizable />
<WallOfLove />
<Principles />
<CTANewsletter />
<Statistic />
<CTADownload />
{/* <Customizable /> */}
{/* <WallOfLove /> */}
{/* <Principles /> */}
{/* <CTANewsletter /> */}
{/* <Statistic /> */}
{/* <CTADownload /> */}
</Fragment>
)
}

View File

@ -0,0 +1,271 @@
/* eslint-disable @next/next/no-img-element */
import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { cn } from '@/lib/utils'
import { FaDiscord, FaGithub } from 'react-icons/fa'
import { FiDownload } from 'react-icons/fi'
import { FaXTwitter } from 'react-icons/fa6'
import { Button } from './ui/button'
import LogoJanSVG from '@/assets/icons/logo-jan.svg'
const MENU_ITEMS = [
{ name: 'Docs', href: '/docs' },
{ name: 'Changelog', href: '/changelog' },
{ name: 'Blog', href: '/blog' },
{ name: 'Handbook', href: '/handbook' },
]
const Navbar = ({ noScroll }: { noScroll?: boolean }) => {
const router = useRouter()
const [isScrolled, setIsScrolled] = useState(false)
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const currentPath = router.asPath
const isLanding = currentPath === '/'
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > (isLanding ? 76 : 0))
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [isLanding])
const toggleMobileMenu = () => {
setIsMobileMenuOpen(!isMobileMenuOpen)
}
// Prevent body scroll when mobile menu is open
useEffect(() => {
if (isMobileMenuOpen) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = 'unset'
}
// Cleanup on unmount
return () => {
document.body.style.overflow = 'unset'
}
}, [isMobileMenuOpen])
return (
<div
className={cn(
'h-[100px] w-full top-0 z-50 transition-all duration-300 border-b lg:px-6 left-0',
isLanding ? 'fixed' : 'sticky !border-opacity-100 !top-0',
isScrolled || noScroll
? 'bg-white text-black h-[60px] border-border'
: 'bg-transparent text-white h-[60px] border-gray-100 border-opacity-10 top-4'
)}
id="navbar"
>
<div className="flex nextra-wrap-container w-full mx-auto h-full justify-between items-center">
<div>
<a href="/" className="flex items-center gap-2">
<img src={LogoJanSVG.src} alt="Jan" className="w-6 h-6" />
<span
className={cn('text-xl font-bold', !isLanding && '!text-black')}
>
Jan
</span>
</a>
</div>
{/* Desktop Navigation */}
<nav>
<ul className="lg:flex space-x-8 hidden items-center">
{MENU_ITEMS.map((item) => {
const isActive = currentPath === item.href
return (
<li key={item.name}>
<a
href={item.href}
className={cn(
'hover:opacity-70 transition-opacity',
!isLanding && '!text-black',
isActive && 'text-blue-600 font-semibold'
)}
>
{item.name}
</a>
</li>
)
})}
<li>
<a
href="https://github.com/menloresearch/jan/releases/latest"
target="_blank"
rel="noopener noreferrer"
>
<Button
className={cn(
'text-base',
!isLanding &&
'!bg-black !text-white !hover:bg-black !hover:text-white',
isScrolled || noScroll
? 'bg-black text-white hover:bg-black hover:text-white'
: 'bg-white text-black hover:bg-white hover:text-black'
)}
>
Download Jan
</Button>
</a>
</li>
</ul>
</nav>
{/* Mobile Download Button and Hamburger */}
<div className="lg:hidden flex items-center gap-3">
<a
href="https://github.com/menloresearch/jan/releases/latest"
target="_blank"
rel="noopener noreferrer"
>
<Button
size="sm"
className={cn(
!isLanding &&
'!bg-black !text-white !hover:bg-black !hover:text-white',
isScrolled || noScroll
? 'bg-black text-white hover:bg-gray-800'
: 'bg-white text-black hover:bg-gray-100'
)}
>
Download
</Button>
</a>
<button
className="flex flex-col items-center justify-center w-8 h-8"
onClick={toggleMobileMenu}
aria-label="Toggle mobile menu"
>
<span
className={cn(
'block w-6 h-0.5 bg-current transition-all duration-300 transform',
!isLanding && 'bg-black',
isMobileMenuOpen ? 'rotate-45 translate-y-1.5' : ''
)}
/>
<span
className={cn(
'block w-6 h-0.5 bg-current transition-all duration-300 mt-1',
!isLanding && 'bg-black',
isMobileMenuOpen ? 'opacity-0' : ''
)}
/>
<span
className={cn(
'block w-6 h-0.5 bg-current transition-all duration-300 transform mt-1',
!isLanding && 'bg-black',
isMobileMenuOpen ? '-rotate-45 -translate-y-1.5' : ''
)}
/>
</button>
</div>
</div>
{/* Mobile Menu - Modal Card */}
{isMobileMenuOpen && (
<>
{/* Backdrop */}
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden"
onClick={() => setIsMobileMenuOpen(false)}
/>
{/* Modal Card */}
<div className="fixed bottom-0 left-0 right-0 bg-white rounded-t-3xl z-50 lg:hidden max-h-[80vh] overflow-y-auto">
<div className="p-6">
{/* Header with close button */}
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold text-black">Jan</h2>
<button
className="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center text-gray-500 hover:bg-gray-200"
onClick={() => setIsMobileMenuOpen(false)}
aria-label="Close menu"
>
×
</button>
</div>
{/* Menu Items */}
<nav className="mb-6">
<ul className="space-y-4">
{MENU_ITEMS.map((item) => {
const isActive = currentPath === item.href
return (
<li key={item.name}>
<a
href={item.href}
className={cn(
'block text-lg font-medium text-black hover:text-gray-600 transition-colors py-2',
isActive && 'text-blue-600 font-bold'
)}
onClick={() => setIsMobileMenuOpen(false)}
>
{item.name}
</a>
</li>
)
})}
<li></li>
</ul>
</nav>
{/* Social Icons */}
<div className="flex gap-4 mb-6">
<a
href="https://discord.com/invite/FTk2MvZwJH"
target="_blank"
rel="noopener noreferrer"
className="text-black rounded-lg flex items-center justify-center"
>
<FaDiscord className="size-5" />
</a>
<a
href="https://twitter.com/jandotai"
target="_blank"
rel="noopener noreferrer"
className="text-black rounded-lg flex items-center justify-center"
>
<FaXTwitter className="size-5" />
</a>
<a
href="https://github.com/menloresearch/jan"
target="_blank"
rel="noopener noreferrer"
className="text-black rounded-lg flex items-center justify-center"
>
<FaGithub className="size-5" />
</a>
</div>
{/* Action Buttons */}
<div className="space-y-3">
<Button
variant="playful-green"
size="xl"
className="w-full lg:w-auto text-left justify-start"
asChild
>
<a
href="https://github.com/menloresearch/jan/releases/latest"
target="_blank"
rel="noopener noreferrer"
>
<FiDownload className="size-6 mr-2" />
Download Jan
</a>
</Button>
</div>
</div>
</div>
</>
)}
</div>
)
}
export default Navbar

View File

@ -0,0 +1,135 @@
import { ClientTweetCard } from '@/components/ui/tweet-card'
import { useEffect } from 'react'
const Tweets = [
{ id: '1959360209970700621' },
{ id: '1959018716219277654' },
{ id: '1959410685093523580' },
{ id: '1959003819196785143' },
{ id: '1956547833999560863' },
{ id: '1956616098885079434' },
{ id: '1955283174340128809' },
{ id: '1955680680261652896' },
{ id: '1955624616560566446' },
{ id: '1955633387038966112' },
{ id: '1955326315160043918' },
{ id: '1952305678497747137' },
]
export default function TweetSection() {
useEffect(() => {
const buttons = document.querySelectorAll('.tweet-nav-btn')
const handleClick = (event: Event) => {
const button = event.currentTarget as HTMLButtonElement
const direction = button.dataset.direction
const container = document.querySelector('.tweet-marquee-container')
if (direction === 'left') {
container?.scrollBy({ left: -300, behavior: 'smooth' })
} else {
container?.scrollBy({ left: 300, behavior: 'smooth' })
}
}
buttons.forEach((button) => {
button.addEventListener('click', handleClick)
})
return () => {
buttons.forEach((button) => {
button.removeEventListener('click', handleClick)
})
}
}, [])
return (
<div className="space-y-4 !font-inter">
{/* Scrollable marquee container */}
<div className="tweet-marquee-container overflow-x-auto overflow-y-hidden">
<div className="tweet-marquee flex gap-6 items-start">
{/* Multiple copies for infinite scroll */}
{[...Tweets, ...Tweets, ...Tweets].map((tweet, index) => (
<div key={`${tweet.id}-${index}`} className="flex-shrink-0 w-80">
<ClientTweetCard id={tweet.id} />
</div>
))}
</div>
</div>
{/* Navigation arrows at bottom - will need client-side JS */}
<div className="flex justify-center gap-6">
<button
className="tweet-nav-btn size-12 cursor-pointer rounded-full border border-black shadow-[0px_4px_0px_0px_rgba(0,0,0,1)] flex items-center justify-center bg-white hover:bg-gray-50 transition-all"
data-direction="left"
>
<svg
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.5 18L4.5 12L10.5 6M5.5 12L20.5 12"
stroke="black"
strokeWidth="2"
strokeLinecap="square"
/>
</svg>
</button>
<button
className="tweet-nav-btn size-12 cursor-pointer rounded-full border border-black shadow-[0px_4px_0px_0px_rgba(0,0,0,1)] flex items-center justify-center bg-white hover:bg-gray-50 transition-all"
data-direction="right"
>
<svg
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.5 6L20.5 12L14.5 18M19.5 12H4.5"
stroke="black"
strokeWidth="2"
strokeLinecap="square"
/>
</svg>
</button>
</div>
<style
dangerouslySetInnerHTML={{
__html: `
.tweet-marquee {
animation: marquee 10s linear infinite;
}
.tweet-marquee:hover {
animation-play-state: paused;
}
@keyframes marquee {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-33.33%);
}
}
.tweet-marquee-container::-webkit-scrollbar {
display: none;
}
.tweet-marquee-container {
scrollbar-width: none;
-ms-overflow-style: none;
}
`,
}}
/>
</div>
)
}

View File

@ -0,0 +1,67 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer",
{
variants: {
variant: {
"playful-green":
"bg-[#7EF19D] hover:bg-[#6BD689] hover:shadow-[0px_0px_0px_0px_rgba(0,0,0,1)] hover:translate-y-[2px] active:shadow-[0px_2px_0px_0px_rgba(0,0,0,1)] active:translate-y-[2px] text-black shadow-[0px_4px_0px_0px_rgba(0,0,0,1)] border border-black !rounded-2xl",
"playful-white":
"bg-white text-black hover:bg-gray-200 hover:shadow-[0px_0px_0px_0px_rgba(0,0,0,1)] hover:translate-y-[2px] active:shadow-[0px_2px_0px_0px_rgba(0,0,0,1)] active:translate-y-[2px] shadow-[0px_4px_0px_0px_rgba(0,0,0,1)] border border-black !rounded-2xl",
playful:
"text-black hover:shadow-[0px_0px_0px_0px_rgba(0,0,0,1)] hover:translate-y-[2px] active:shadow-[0px_2px_0px_0px_rgba(0,0,0,1)] active:translate-y-[2px] shadow-[0px_4px_0px_0px_rgba(0,0,0,1)] border border-black !rounded-2xl",
default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
xl: "h-12 rounded-md px-6 has-[>svg]:px-4 text-lg",
xxl: "h-16 !rounded-[20px] px-6 has-[>svg]:px-4 text-xl -tracking-[0.2px]",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) {
const Comp = asChild ? Slot : "button";
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
export { Button, buttonVariants };

View File

@ -0,0 +1,255 @@
import * as React from 'react'
import { Button } from './button'
import { cn } from '@/lib/utils'
import { FaApple, FaWindows, FaLinux } from 'react-icons/fa'
import { formatFileSize } from '@/utils/format'
interface DownloadOption {
id: string
name: string
icon: React.ReactNode
size: string
href: string
isActive?: boolean
}
const downloadOptionsTemplate: DownloadOption[] = [
{
id: 'mac',
name: 'Download for Mac',
icon: <FaApple className="size-5" />,
size: '',
href: '#',
isActive: true,
},
{
id: 'windows',
name: 'Download for Windows',
icon: <FaWindows className="size-5" />,
size: '',
href: '#',
},
{
id: 'linux-appimage',
name: 'Download for Linux (AppImage)',
icon: <FaLinux className="size-5" />,
size: '',
href: '#',
},
{
id: 'linux-deb',
name: 'Download for Linux (Deb)',
icon: <FaLinux className="size-5" />,
size: '',
href: '#',
},
]
const fileFormatMap: { [key: string]: string } = {
'mac': 'Jan_{tag}_universal.dmg',
'windows': 'Jan_{tag}_x64-setup.exe',
'linux-appimage': 'Jan_{tag}_amd64.AppImage',
'linux-deb': 'Jan_{tag}_amd64.deb',
}
interface DropdownButtonProps {
size?: 'default' | 'sm' | 'lg' | 'xl' | 'icon' | 'xxl'
className?: string
classNameButton?: string
lastRelease?: any
}
export function DropdownButton({
size = 'xl',
className,
classNameButton,
lastRelease,
}: DropdownButtonProps) {
const [isOpen, setIsOpen] = React.useState(false)
const [downloadOptions, setDownloadOptions] = React.useState(
downloadOptionsTemplate
)
const [currentOption, setCurrentOption] = React.useState(
downloadOptions.find((opt) => opt.isActive) || downloadOptions[0]
)
const dropdownRef = React.useRef<HTMLDivElement>(null)
const toggleDropdown = () => setIsOpen(!isOpen)
const selectOption = (option: DownloadOption) => {
setCurrentOption(option)
setIsOpen(false)
}
const changeDefaultSystem = React.useCallback((systems: DownloadOption[]) => {
const userAgent = navigator.userAgent
if (userAgent.includes('Windows')) {
// windows user
const windowsOption = systems.find((opt) => opt.id === 'windows')
if (windowsOption) setCurrentOption(windowsOption)
} else if (userAgent.includes('Linux')) {
// linux user - prefer deb package
const linuxOption = systems.find((opt) => opt.id === 'linux-deb')
if (linuxOption) setCurrentOption(linuxOption)
} else if (userAgent.includes('Mac OS')) {
// mac user - always use universal build
const macOption = systems.find((opt) => opt.id === 'mac')
if (macOption) setCurrentOption(macOption)
} else {
// fallback to windows
const windowsOption = systems.find((opt) => opt.id === 'windows')
if (windowsOption) setCurrentOption(windowsOption)
}
}, [])
React.useEffect(() => {
if (lastRelease) {
try {
const tag = lastRelease.tag_name.startsWith('v')
? lastRelease.tag_name.substring(1)
: lastRelease.tag_name
const updatedOptions = downloadOptionsTemplate.map((option) => {
const fileFormat = fileFormatMap[option.id]
const fileName = fileFormat.replace('{tag}', tag)
// Find the corresponding asset to get the file size
const asset = lastRelease.assets.find(
(asset: any) => asset.name === fileName
)
return {
...option,
href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${fileName}`,
size: asset ? formatFileSize(asset.size) : 'N/A',
}
})
setDownloadOptions(updatedOptions)
changeDefaultSystem(updatedOptions)
} catch (error) {
console.error('Failed to update download links:', error)
}
}
}, [lastRelease, changeDefaultSystem])
React.useEffect(() => {
const handleEscapeKey = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isOpen) {
setIsOpen(false)
}
}
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false)
}
}
if (isOpen) {
document.addEventListener('keydown', handleEscapeKey)
document.addEventListener('mousedown', handleClickOutside)
}
return () => {
document.removeEventListener('keydown', handleEscapeKey)
document.removeEventListener('mousedown', handleClickOutside)
}
}, [isOpen])
return (
<div ref={dropdownRef} className={cn('relative', className)}>
<div
className={cn(
'flex w-full group hover:shadow-[0px_0px_0px_0px_rgba(0,0,0,1)] hover:translate-y-[2px] hover:bg-[#6BD689] shadow-[0px_4px_0px_0px_rgba(0,0,0,1)] rounded-2xl transition-all border-2 border-black bg-[#7EF19D]',
classNameButton?.includes('!shadow-none') &&
'!shadow-none hover:!shadow-none !translate-y-0 hover:!translate-y-0',
className
)}
>
{/* Main Button */}
<Button
size={size}
variant="ghost"
className={cn(
'!rounded-r-none !border-0 flex-1 !shadow-none group-hover:!shadow-none !transform-none group-hover:!transform-none !bg-transparent hover:!bg-transparent text-black',
classNameButton
)}
asChild
>
<a
href={currentOption.href}
target="_blank"
rel="noopener noreferrer"
>
{currentOption.icon}
<span className="flex flex-col">
<span>{currentOption.name}</span>
</span>
</a>
</Button>
{/* Dropdown Toggle */}
<Button
size={size}
variant="ghost"
className={cn(
'!rounded-l-none px-3 !border-0 flex-shrink-0 !shadow-none group-hover:!shadow-none !transform-none group-hover:!transform-none !bg-transparent hover:!bg-transparent text-black border-l border-black',
classNameButton
)}
onClick={toggleDropdown}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="lucide lucide-chevrons-up-down-icon lucide-chevrons-up-down"
>
<path d="m7 15 5 5 5-5" />
<path d="m7 9 5-5 5 5" />
</svg>
</Button>
</div>
{/* Dropdown Menu */}
{isOpen && (
<div className="absolute top-full right-0 mt-2 w-full lg:w-[400px] bg-white rounded-[20px] overflow-hidden shadow-[0px_4px_0px_0px_rgba(0,0,0,1)] border-2 border-black z-40">
<div className="m-1 overflow-hidden">
{downloadOptions.map((option) => (
<a
key={option.id}
href={option.href}
target="_blank"
rel="noopener noreferrer"
className={cn(
'w-full flex items-center justify-between px-4 py-3 text-left hover:text-[#0668D5] hover:bg-[#E0EEFE] rounded-2xl transition-all'
)}
onClick={() => {
selectOption(option)
setIsOpen(false)
}}
>
<div className="flex items-center gap-3">
{option.icon}
<span className="font-medium text-base tracking-[-0.16px]">
{option.name}
</span>
</div>
<span className="text-sm font-bold">{option.size}</span>
</a>
))}
</div>
</div>
)}
</div>
)
}

View File

@ -0,0 +1,293 @@
/* eslint-disable @next/next/no-img-element */
import { Suspense } from 'react'
import { FaXTwitter } from 'react-icons/fa6'
import {
enrichTweet,
useTweet,
type EnrichedTweet,
type TweetProps,
type TwitterComponents,
} from 'react-tweet'
import { getTweet, type Tweet } from 'react-tweet/api'
import { cn } from '@/lib/utils'
interface TwitterIconProps {
className?: string
[key: string]: unknown
}
const Twitter = ({ className, ...props }: TwitterIconProps) => (
<FaXTwitter className={cn('!text-black', className)} {...props} />
)
const Verified = ({ className, ...props }: TwitterIconProps) => (
<svg
aria-label="Verified Account"
viewBox="0 0 24 24"
className={className}
{...props}
>
<g fill="currentColor">
<path d="M22.5 12.5c0-1.58-.875-2.95-2.148-3.6.154-.435.238-.905.238-1.4 0-2.21-1.71-3.998-3.818-3.998-.47 0-.92.084-1.336.25C14.818 2.415 13.51 1.5 12 1.5s-2.816.917-3.437 2.25c-.415-.165-.866-.25-1.336-.25-2.11 0-3.818 1.79-3.818 4 0 .494.083.964.237 1.4-1.272.65-2.147 2.018-2.147 3.6 0 1.495.782 2.798 1.942 3.486-.02.17-.032.34-.032.514 0 2.21 1.708 4 3.818 4 .47 0 .92-.086 1.335-.25.62 1.334 1.926 2.25 3.437 2.25 1.512 0 2.818-.916 3.437-2.25.415.163.865.248 1.336.248 2.11 0 3.818-1.79 3.818-4 0-.174-.012-.344-.033-.513 1.158-.687 1.943-1.99 1.943-3.484zm-6.616-3.334l-4.334 6.5c-.145.217-.382.334-.625.334-.143 0-.288-.04-.416-.126l-.115-.094-2.415-2.415c-.293-.293-.293-.768 0-1.06s.768-.294 1.06 0l1.77 1.767 3.825-5.74c.23-.345.696-.436 1.04-.207.346.23.44.696.21 1.04z" />
</g>
</svg>
)
export const truncate = (str: string | null, length: number) => {
if (!str || str.length <= length) return str
return `${str.slice(0, length - 3)}...`
}
const Skeleton = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => {
return (
<div className={cn('rounded-md bg-primary/10', className)} {...props} />
)
}
export const TweetSkeleton = ({
className,
...props
}: {
className?: string
[key: string]: unknown
}) => (
<div
className={cn(
'flex size-full max-h-max min-w-72 flex-col gap-2 rounded-lg border p-4',
className
)}
{...props}
>
<div className="flex flex-row gap-2">
<Skeleton className="size-10 shrink-0 rounded-full" />
<Skeleton className="h-10 w-full" />
</div>
<Skeleton className="h-20 w-full" />
</div>
)
export const TweetNotFound = ({
className,
...props
}: {
className?: string
[key: string]: unknown
}) => (
<div
className={cn(
'flex size-full flex-col items-center justify-center gap-2 rounded-lg border p-4',
className
)}
{...props}
>
<h3>Tweet not found</h3>
</div>
)
export const TweetHeader = ({ tweet }: { tweet: EnrichedTweet }) => (
<div className="flex flex-row justify-between tracking-tight">
<div className="flex items-center space-x-2">
<a href={tweet.user.url} target="_blank" rel="noreferrer">
<img
title={`Profile picture of ${tweet.user.name}`}
alt={tweet.user.screen_name}
height={48}
width={48}
src={tweet.user.profile_image_url_https}
className="overflow-hidden rounded-md border border-transparent"
/>
</a>
<div>
<a
href={tweet.user.url}
target="_blank"
rel="noreferrer"
className="flex items-center whitespace-nowrap font-semibold"
>
{truncate(tweet.user.name, 20)}
{tweet.user.verified ||
(tweet.user.is_blue_verified && (
<Verified className="ml-1 inline size-4 text-yellow-500" />
))}
</a>
<div className="flex items-center space-x-1">
<a
href={tweet.user.url}
target="_blank"
rel="noreferrer"
className="text-sm text-gray-500 transition-all duration-75"
>
@{truncate(tweet.user.screen_name, 16)}
</a>
</div>
</div>
</div>
<a href={tweet.url} target="_blank" rel="noreferrer">
<span className="sr-only">Link to tweet</span>
<Twitter className="size-5 items-start text-[#3BA9EE] transition-all ease-in-out hover:scale-105 border-none" />
</a>
</div>
)
export const TweetBody = ({ tweet }: { tweet: EnrichedTweet }) => (
<div className="break-words leading-normal tracking-tighter">
{tweet.entities.map((entity, idx) => {
switch (entity.type) {
case 'url':
case 'symbol':
case 'hashtag':
case 'mention':
return (
<a
key={idx}
href={entity.href}
target="_blank"
rel="noopener noreferrer"
className="text-sm tracking-normal font-normal text-gray-500"
>
<span>{entity.text}</span>
</a>
)
case 'text':
return (
<span
key={idx}
className="text-sm tracking-normal font-normal"
dangerouslySetInnerHTML={{ __html: entity.text }}
/>
)
}
})}
</div>
)
export const TweetMedia = ({ tweet }: { tweet: EnrichedTweet }) => {
if (!tweet.video && !tweet.photos) return null
return (
<div className="flex flex-1 items-center justify-center">
{tweet.video && (
<video
poster={tweet.video.poster}
autoPlay
loop
muted
playsInline
className="rounded-xl border shadow-sm"
>
<source src={tweet.video.variants[0].src} type="video/mp4" />
Your browser does not support the video tag.
</video>
)}
{tweet.photos && (
<div className="relative flex transform-gpu snap-x snap-mandatory gap-4 overflow-x-auto">
<div className="shrink-0 snap-center sm:w-2" />
{tweet.photos.map((photo) => (
<img
key={photo.url}
src={photo.url}
title={'Photo by ' + tweet.user.name}
alt={tweet.text}
className="h-64 w-5/6 shrink-0 snap-center snap-always rounded-xl border object-cover shadow-sm"
/>
))}
<div className="shrink-0 snap-center sm:w-2" />
</div>
)}
{!tweet.video &&
!tweet.photos &&
// @ts-ignore
tweet?.card?.binding_values?.thumbnail_image_large?.image_value.url && (
<img
src={
// @ts-ignore
tweet.card.binding_values.thumbnail_image_large.image_value.url
}
className="h-64 rounded-xl border object-cover shadow-sm"
alt={tweet.text}
/>
)}
</div>
)
}
export const MagicTweet = ({
tweet,
components,
className,
...props
}: {
tweet: Tweet
components?: TwitterComponents
className?: string
}) => {
const enrichedTweet = enrichTweet(tweet)
return (
<div
className={cn(
'relative flex w-full max-w-lg flex-col gap-2 overflow-hidden rounded-lg border p-4 backdrop-blur-md font-inter !tracking-normal hover:bg-neutral-50',
className
)}
{...props}
>
<TweetHeader tweet={enrichedTweet} />
<TweetBody tweet={enrichedTweet} />
{/* <TweetMedia tweet={enrichedTweet} /> */}
</div>
)
}
/**
* TweetCard (Server Side Only)
*/
export const TweetCard = async ({
id,
components,
fallback = <TweetSkeleton />,
onError,
...props
}: TweetProps & {
className?: string
}) => {
const tweet = id
? await getTweet(id).catch((err) => {
if (onError) {
onError(err)
} else {
console.error(err)
}
})
: undefined
if (!tweet) {
const NotFound = components?.TweetNotFound || TweetNotFound
return <NotFound {...props} />
}
return (
<Suspense fallback={fallback}>
<MagicTweet tweet={tweet} {...props} />
</Suspense>
)
}
export const ClientTweetCard = ({
id,
apiUrl,
fallback = <TweetSkeleton />,
components,
fetchOptions,
onError,
...props
}: TweetProps & { className?: string }) => {
const { data, error, isLoading } = useTweet(id, apiUrl, fetchOptions)
if (isLoading) return fallback
if (error || !data) {
const NotFound = components?.TweetNotFound || TweetNotFound
return <NotFound error={onError ? onError(error) : error} />
}
return <MagicTweet tweet={data} components={components} {...props} />
}

6
docs/src/lib/utils.ts Normal file
View File

@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@ -21,6 +21,16 @@
"title": "Integrations",
"display": "hidden"
},
"api-reference": {
"type": "page",
"title": "API reference",
"display": "hidden"
},
"handbook": {
"type": "page",
"title": "Handbook",
"display": "hidden"
},
"changelog": {
"type": "page",
"title": "Changelog",
@ -40,7 +50,6 @@
"title": "Post Categories",
"display": "hidden"
},
"download": {
"type": "page",
"theme": {

View File

@ -0,0 +1,20 @@
{
"get-started-separator": {
"title": "Get started",
"type": "separator"
},
"index": "Overview",
"installation": "Installation",
"configuration": "Configuration",
"core-concepts-separator": {
"title": "Core concepts",
"type": "separator"
},
"api-reference": "API Reference",
"resource-separator": {
"title": "Resources",
"type": "separator"
},
"architecture": "Architecture",
"development": "Development"
}

View File

@ -0,0 +1,378 @@
---
title: API Reference
description: Complete API documentation for Jan Server endpoints and OpenAI compatibility.
---
## Base URL
All API endpoints are available at the API gateway base URL:
```
http://localhost:8080/api/v1
```
The API gateway automatically forwards port 8080 when using the standard deployment scripts.
## Authentication
Jan Server supports multiple authentication methods:
### JWT Token Authentication
Include JWT token in the Authorization header:
```bash
curl -H "Authorization: Bearer <jwt_token>" \
http://localhost:8080/api/v1/protected-endpoint
```
### API Key Authentication
Include API key in the Authorization header:
```bash
curl -H "Authorization: Bearer <api_key>" \
http://localhost:8080/api/v1/protected-endpoint
```
## OpenAI-Compatible Endpoints
Jan Server implements OpenAI-compatible endpoints for seamless integration with existing tools.
### Chat Completions
**Endpoint**: `POST /api/v1/chat/completions`
Standard OpenAI chat completions API for conversational AI.
```bash
curl -X POST http://localhost:8080/api/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"model": "jan-v1-4b",
"messages": [
{"role": "user", "content": "Hello, how are you?"}
],
"max_tokens": 100,
"temperature": 0.7
}'
```
**Parameters:**
- `model` (string): Model identifier (`jan-v1-4b`)
- `messages` (array): Conversation history
- `max_tokens` (integer): Maximum response tokens
- `temperature` (float): Response randomness (0.0 to 2.0)
- `stream` (boolean): Enable streaming responses
### Model Information
**Endpoint**: `GET /api/v1/models`
List available models:
```bash
curl http://localhost:8080/api/v1/models
```
**Response:**
```json
{
"object": "list",
"data": [
{
"id": "jan-v1-4b",
"object": "model",
"created": 1234567890,
"owned_by": "jan"
}
]
}
```
### Completions (Text Generation)
**Endpoint**: `POST /api/v1/completions`
Text completion endpoint:
```bash
curl -X POST http://localhost:8080/api/v1/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"model": "jan-v1-4b",
"prompt": "The meaning of life is",
"max_tokens": 50
}'
```
## Authentication Endpoints
### OAuth2 Google Login
**Endpoint**: `GET /auth/google`
Redirects to Google OAuth2 authorization:
```bash
curl http://localhost:8080/auth/google
```
### OAuth2 Callback
**Endpoint**: `GET /auth/google/callback`
Handles OAuth2 callback and issues JWT token:
```
http://localhost:8080/auth/google/callback?code=<auth_code>&state=<state>
```
### Token Refresh
**Endpoint**: `POST /api/v1/auth/refresh`
Refresh expired JWT tokens:
```bash
curl -X POST http://localhost:8080/api/v1/auth/refresh \
-H "Authorization: Bearer <expired_token>"
```
## User Management
### User Profile
**Endpoint**: `GET /api/v1/user/profile`
Get current user profile:
```bash
curl -H "Authorization: Bearer <token>" \
http://localhost:8080/api/v1/user/profile
```
### API Keys
**Endpoint**: `POST /api/v1/user/api-keys`
Generate new API key:
```bash
curl -X POST http://localhost:8080/api/v1/user/api-keys \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Development Key",
"permissions": ["read", "write"]
}'
```
## Conversation Management
### Create Conversation
**Endpoint**: `POST /api/v1/conversations`
Create new conversation:
```bash
curl -X POST http://localhost:8080/api/v1/conversations \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"title": "My Conversation",
"model": "jan-v1-4b"
}'
```
### List Conversations
**Endpoint**: `GET /api/v1/conversations`
Get user's conversations:
```bash
curl -H "Authorization: Bearer <token>" \
http://localhost:8080/api/v1/conversations
```
### Get Conversation
**Endpoint**: `GET /api/v1/conversations/{id}`
Get specific conversation with message history:
```bash
curl -H "Authorization: Bearer <token>" \
http://localhost:8080/api/v1/conversations/123
```
## Health and Status
### Health Check
**Endpoint**: `GET /health`
Basic health check:
```bash
curl http://localhost:8080/health
```
**Response:**
```json
{
"status": "ok",
"timestamp": "2024-01-01T12:00:00Z"
}
```
### System Status
**Endpoint**: `GET /api/v1/status`
Detailed system status:
```bash
curl -H "Authorization: Bearer <token>" \
http://localhost:8080/api/v1/status
```
**Response:**
```json
{
"api_gateway": "healthy",
"inference_model": "healthy",
"database": "healthy",
"external_apis": {
"serper": "healthy"
}
}
```
## Error Responses
Jan Server returns standard HTTP status codes and JSON error responses:
```json
{
"error": {
"message": "Invalid request format",
"type": "invalid_request_error",
"code": "invalid_json"
}
}
```
### Common Error Codes
| Status Code | Description |
|-------------|-------------|
| `400` | Bad Request - Invalid request format |
| `401` | Unauthorized - Invalid or missing authentication |
| `403` | Forbidden - Insufficient permissions |
| `404` | Not Found - Resource not found |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Internal Server Error - Server error |
| `503` | Service Unavailable - Service temporarily unavailable |
## Interactive Documentation
Jan Server provides interactive Swagger documentation at:
```
http://localhost:8080/api/swagger/index.html#/
```
This interface allows you to:
- Browse all available endpoints
- Test API calls directly from the browser
- View request/response schemas
- Generate code samples
The Swagger documentation is auto-generated from Go code annotations and provides the most up-to-date API reference.
## Rate Limiting
API endpoints implement rate limiting to prevent abuse:
- **Authenticated requests**: 1000 requests per hour per user
- **Unauthenticated requests**: 100 requests per hour per IP
- **Model inference**: 60 requests per minute per user
Rate limit headers are included in responses:
```
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1609459200
```
## SDK and Client Libraries
### JavaScript/Node.js
Use the OpenAI JavaScript SDK with Jan Server:
```javascript
import OpenAI from 'openai';
const openai = new OpenAI({
baseURL: 'http://localhost:8080/api/v1',
apiKey: 'your-jwt-token'
});
const completion = await openai.chat.completions.create({
model: 'jan-v1-4b',
messages: [
{ role: 'user', content: 'Hello!' }
]
});
```
### Python
Use the OpenAI Python SDK:
```python
import openai
openai.api_base = "http://localhost:8080/api/v1"
openai.api_key = "your-jwt-token"
response = openai.ChatCompletion.create(
model="jan-v1-4b",
messages=[
{"role": "user", "content": "Hello!"}
]
)
```
### cURL Examples
Complete cURL examples for common operations:
```bash
# Get models
curl http://localhost:8080/api/v1/models
# Chat completion
curl -X POST http://localhost:8080/api/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "jan-v1-4b",
"messages": [{"role": "user", "content": "Hello"}]
}'
# Streaming chat completion
curl -X POST http://localhost:8080/api/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "jan-v1-4b",
"messages": [{"role": "user", "content": "Tell me a story"}],
"stream": true
}' \
--no-buffer
```

View File

@ -0,0 +1,191 @@
---
title: Architecture
description: Technical architecture and system design of Jan Server components.
---
## System Overview
Jan Server implements a microservices architecture on Kubernetes with three core components communicating over HTTP and managed by Helm charts.
```mermaid
graph TD
Client[Client/Browser] --> Gateway[jan-api-gateway:8080]
Gateway --> Model[jan-inference-model:8101]
Gateway --> DB[(PostgreSQL:5432)]
Gateway --> Serper[Serper API]
Gateway --> OAuth[Google OAuth2]
```
## Components
### API Gateway (`jan-api-gateway`)
**Technology Stack:**
- **Language**: Go 1.24.6
- **Framework**: Gin web framework
- **ORM**: GORM with PostgreSQL driver
- **DI**: Google Wire for dependency injection
- **Documentation**: Swagger/OpenAPI auto-generated
**Responsibilities:**
- HTTP request routing and middleware
- User authentication via JWT and OAuth2
- Database operations and data persistence
- External API integration (Serper, Google OAuth)
- OpenAI-compatible API endpoints
- Request forwarding to inference service
**Key Directories:**
```
application/
├── cmd/server/ # Main entry point and DI wiring
├── app/ # Core business logic
├── config/ # Environment variables and settings
└── docs/ # Auto-generated Swagger docs
```
### Inference Model (`jan-inference-model`)
**Technology Stack:**
- **Base Image**: VLLM OpenAI v0.10.0
- **Model**: Jan-v1-4B (downloaded from Hugging Face)
- **Protocol**: OpenAI-compatible HTTP API
- **Features**: Tool calling, reasoning parsing
**Configuration:**
- **Model Path**: `/models/Jan-v1-4B`
- **Served Name**: `jan-v1-4b`
- **Port**: 8101
- **Batch Tokens**: 1024 max
- **Tool Parser**: Hermes
- **Reasoning Parser**: Qwen3
**Capabilities:**
- Text generation and completion
- Tool calling and function execution
- Multi-turn conversations
- Reasoning and chain-of-thought
### Database (PostgreSQL)
**Configuration:**
- **Database**: `jan`
- **User**: `jan-user`
- **Password**: `jan-password`
- **Port**: 5432
**Schema:**
- User accounts and authentication
- Conversation history
- Project and organization management
- API keys and access control
## Data Flow
### Request Processing
1. **Client Request**: HTTP request to API gateway on port 8080
2. **Authentication**: JWT token validation or OAuth2 flow
3. **Request Routing**: Gateway routes to appropriate handler
4. **Database Operations**: GORM queries for user data/state
5. **Inference Call**: HTTP request to model service on port 8101
6. **Response Assembly**: Gateway combines results and returns to client
### Authentication Flow
**JWT Authentication:**
1. User provides credentials
2. Gateway validates against database
3. JWT token issued with HMAC-SHA256 signing
4. Subsequent requests include JWT in Authorization header
**OAuth2 Flow:**
1. Client redirected to Google OAuth2
2. Authorization code returned to redirect URL
3. Gateway exchanges code for access token
4. User profile retrieved from Google
5. Local JWT token issued
## Deployment Architecture
### Kubernetes Resources
**Deployments:**
- `jan-api-gateway`: Single replica Go application
- `jan-inference-model`: Single replica VLLM server
- `postgresql`: StatefulSet with persistent storage
**Services:**
- `jan-api-gateway`: ClusterIP exposing port 8080
- `jan-inference-model`: ClusterIP exposing port 8101
- `postgresql`: ClusterIP exposing port 5432
**Configuration:**
- Environment variables via Helm values
- Secrets for sensitive data (JWT keys, OAuth credentials)
- ConfigMaps for application settings
### Helm Chart Structure
```
charts/
├── umbrella-chart/ # Main deployment chart
│ ├── Chart.yaml
│ ├── values.yaml # Configuration values
│ └── Chart.lock
└── apps-charts/ # Individual service charts
├── jan-api-gateway/
└── jan-inference-model/
```
## Security Architecture
### Authentication Methods
- **JWT Tokens**: HMAC-SHA256 signed tokens for API access
- **OAuth2**: Google OAuth2 integration for user login
- **API Keys**: HMAC-SHA256 signed keys for service access
### Network Security
- **Internal Communication**: Services communicate over Kubernetes cluster network
- **External Access**: Only API gateway exposed via port forwarding or ingress
- **Database Access**: PostgreSQL accessible only within cluster
### Data Security
- **Secrets Management**: Kubernetes secrets for sensitive configuration
- **Environment Variables**: Non-sensitive config via environment variables
- **Database Encryption**: Standard PostgreSQL encryption at rest
Production deployments should implement additional security measures including TLS termination, network policies, and secret rotation.
## Scalability Considerations
**Current Limitations:**
- Single replica deployments
- No horizontal pod autoscaling
- Local storage for database
**Future Enhancements:**
- Multi-replica API gateway with load balancing
- Horizontal pod autoscaling based on CPU/memory
- External database with clustering
- Redis caching layer
- Message queue for async processing
## Development Architecture
### Code Generation
- **Swagger**: API documentation generated from Go annotations
- **Wire**: Dependency injection code generated from providers
- **GORM Gen**: Database model generation from schema
### Build Process
1. **API Gateway**: Multi-stage Docker build with Go compilation
2. **Inference Model**: Base VLLM image with model download
3. **Helm Charts**: Dependency management and templating
4. **Documentation**: Auto-generation during development
### Local Development
- **Hot Reload**: Source code changes reflected without full rebuild
- **Database Migrations**: Automated schema updates
- **API Testing**: Swagger UI for interactive testing
- **Logging**: Structured logging with configurable levels

View File

@ -0,0 +1,263 @@
---
title: Configuration
description: Configure Jan Server environment variables, authentication, and external integrations.
---
## Environment Variables
Jan Server configuration is managed through environment variables defined in the Helm values file at `charts/umbrella-chart/values.yaml`.
### API Gateway Configuration
#### Core Settings
| Variable | Default | Description |
|----------|---------|-------------|
| `JAN_INFERENCE_MODEL_URL` | `http://jan-server-jan-inference-model:8101` | Internal URL for inference service |
#### Authentication
| Variable | Purpose | Format |
|----------|---------|--------|
| `JWT_SECRET` | JWT token signing | Base64 encoded HMAC-SHA256 key |
| `APIKEY_SECRET` | API key signing | Base64 encoded HMAC-SHA256 key |
The default JWT and API key secrets are for development only. Generate new secrets for production deployments.
#### OAuth2 Integration
| Variable | Description |
|----------|-------------|
| `OAUTH2_GOOGLE_CLIENT_ID` | Google OAuth2 application client ID |
| `OAUTH2_GOOGLE_CLIENT_SECRET` | Google OAuth2 application secret |
| `OAUTH2_GOOGLE_REDIRECT_URL` | Callback URL for OAuth2 flow |
#### External APIs
| Variable | Provider | Purpose |
|----------|----------|---------|
| `SERPER_API_KEY` | Serper | Web search integration |
#### Database Connection
| Variable | Default | Description |
|----------|---------|-------------|
| `DB_POSTGRESQL_WRITE_DSN` | `host=jan-server-postgresql user=jan-user password=jan-password dbname=jan port=5432 sslmode=disable` | Write database connection |
| `DB_POSTGRESQL_READ1_DSN` | `host=jan-server-postgresql user=jan-user password=jan-password dbname=jan port=5432 sslmode=disable` | Read database connection |
## Helm Configuration
### Updating Values
Edit the configuration in `charts/umbrella-chart/values.yaml`:
```yaml
jan-api-gateway:
env:
- name: SERPER_API_KEY
value: your_serper_api_key
- name: OAUTH2_GOOGLE_CLIENT_ID
value: your_google_client_id
- name: OAUTH2_GOOGLE_CLIENT_SECRET
value: your_google_client_secret
```
### Applying Changes
After modifying values, redeploy the application:
```bash
helm upgrade jan-server ./charts/umbrella-chart
```
## Authentication Setup
### JWT Tokens
Generate a secure JWT signing key:
```bash
# Generate 256-bit key for HMAC-SHA256
openssl rand -base64 32
```
Update the `JWT_SECRET` value in your Helm configuration.
### API Keys
Generate a secure API key signing secret:
```bash
# Generate 256-bit key for HMAC-SHA256
openssl rand -base64 32
```
Update the `APIKEY_SECRET` value in your Helm configuration.
### Google OAuth2
1. **Create Google Cloud Project**
- Go to [Google Cloud Console](https://console.cloud.google.com)
- Create a new project or select existing
2. **Enable OAuth2**
- Navigate to "APIs & Services" > "Credentials"
- Create OAuth2 client ID credentials
- Set application type to "Web application"
3. **Configure Redirect URI**
```
http://localhost:8080/auth/google/callback
```
4. **Update Configuration**
- Set `OAUTH2_GOOGLE_CLIENT_ID` to your client ID
- Set `OAUTH2_GOOGLE_CLIENT_SECRET` to your client secret
- Set `OAUTH2_GOOGLE_REDIRECT_URL` to your callback URL
## External Integrations
### Serper API
Jan Server integrates with Serper for web search capabilities.
1. **Get API Key**
- Register at [serper.dev](https://serper.dev)
- Generate API key from dashboard
2. **Configure**
- Set `SERPER_API_KEY` in Helm values
- Redeploy the application
### Adding New Integrations
To add new external API integrations:
1. **Update Helm Values**
```yaml
jan-api-gateway:
env:
- name: YOUR_API_KEY
value: your_api_key_value
```
2. **Update Go Configuration**
Add to `config/environment_variables/env.go`:
```go
YourAPIKey string `env:"YOUR_API_KEY"`
```
3. **Redeploy**
```bash
helm upgrade jan-server ./charts/umbrella-chart
```
## Database Configuration
### Connection Settings
The default PostgreSQL configuration uses:
- **Host**: `jan-server-postgresql` (Kubernetes service name)
- **Database**: `jan`
- **User**: `jan-user`
- **Password**: `jan-password`
- **Port**: `5432`
- **SSL**: Disabled (development only)
### Production Database
For production deployments:
1. **External Database**
- Use managed PostgreSQL service (AWS RDS, Google Cloud SQL)
- Update DSN variables with external connection details
2. **SSL/TLS**
- Enable `sslmode=require` in connection strings
- Configure certificate validation
3. **Connection Pooling**
- Consider using connection pooler (PgBouncer, pgpool-II)
- Configure appropriate pool sizes
## Model Configuration
The inference model service is configured via Docker CMD parameters:
```dockerfile
CMD ["--model", "/models/Jan-v1-4B", \
"--served-model-name", "jan-v1-4b", \
"--host", "0.0.0.0", \
"--port", "8101", \
"--max-num-batched-tokens", "1024", \
"--enable-auto-tool-choice", \
"--tool-call-parser", "hermes", \
"--reasoning-parser", "qwen3"]
```
### Model Parameters
| Parameter | Value | Description |
|-----------|-------|-------------|
| `--model` | `/models/Jan-v1-4B` | Path to model files |
| `--served-model-name` | `jan-v1-4b` | API model identifier |
| `--max-num-batched-tokens` | `1024` | Maximum tokens per batch |
| `--tool-call-parser` | `hermes` | Tool calling format |
| `--reasoning-parser` | `qwen3` | Reasoning output format |
Model configuration changes require rebuilding the inference Docker image. This will be configurable via environment variables in future releases.
## Resource Configuration
### Kubernetes Resources
Current deployments use default resource limits. For production:
```yaml
jan-api-gateway:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
jan-inference-model:
resources:
requests:
cpu: 1000m
memory: 4Gi
limits:
cpu: 4000m
memory: 8Gi
```
### Storage
PostgreSQL uses default Kubernetes storage. For production:
```yaml
postgresql:
persistence:
enabled: true
size: 20Gi
storageClass: fast-ssd
```
## Logging Configuration
Configure logging levels via environment variables:
```yaml
jan-api-gateway:
env:
- name: LOG_LEVEL
value: info
- name: LOG_FORMAT
value: json
```
Available log levels: `debug`, `info`, `warn`, `error`
Available formats: `text`, `json`

View File

@ -0,0 +1,445 @@
---
title: Development
description: Development setup, workflow, and contribution guidelines for Jan Server.
---
## Development Setup
### Prerequisites
- **Go**: 1.24.6 or later
- **Docker**: For containerization
- **minikube**: Local Kubernetes development
- **Helm**: Package management
- **Make**: Build automation
### Initial Setup
1. **Clone Repository**
```bash
git clone https://github.com/menloresearch/jan-server
cd jan-server
```
2. **Install Development Tools**
```bash
cd apps/jan-api-gateway/application
make install
```
3. **Generate Code**
```bash
make setup
```
4. **Start Development Environment**
```bash
# From project root
./scripts/run.sh
```
## API Gateway Development
### Project Structure
```
apps/jan-api-gateway/application/
├── cmd/server/ # Entry point and dependency injection
│ ├── server.go # Main server setup
│ ├── wire.go # DI configuration
│ └── wire_gen.go # Generated DI code
├── app/ # Core application logic
│ ├── domain/ # Business entities
│ ├── repository/ # Data access layer
│ ├── service/ # Business logic
│ └── handler/ # HTTP handlers
├── config/ # Configuration management
└── docs/ # Generated API documentation
```
### Build Commands
```bash
# Install development dependencies
make install
# Generate API documentation
make doc
# Generate dependency injection code
make wire
# Complete setup (doc + wire)
make setup
# Build application
go build -o jan-api-gateway ./cmd/server
```
### Code Generation
Jan Server uses code generation for several components:
**Swagger Documentation:**
```bash
# Generates docs/swagger.json and docs/swagger.yaml
swag init --parseDependency -g cmd/server/server.go -o docs
```
**Dependency Injection:**
```bash
# Generates wire_gen.go from wire.go providers
wire ./cmd/server
```
**Database Models:**
```bash
# Generate GORM models (when schema changes)
go run cmd/codegen/gorm/gorm.go
```
### Local Development
#### Running API Gateway Locally
```bash
cd apps/jan-api-gateway/application
# Set environment variables
export JAN_INFERENCE_MODEL_URL=http://localhost:8101
export JWT_SECRET=your-jwt-secret
export DB_POSTGRESQL_WRITE_DSN="host=localhost user=jan-user password=jan-password dbname=jan port=5432 sslmode=disable"
# Run the server
go run ./cmd/server
```
#### Database Setup
For local development, you can run PostgreSQL directly:
```bash
# Using Docker
docker run -d \
--name jan-postgres \
-e POSTGRES_DB=jan \
-e POSTGRES_USER=jan-user \
-e POSTGRES_PASSWORD=jan-password \
-p 5432:5432 \
postgres:14
```
## Testing
### Running Tests
```bash
# Run all tests
go test ./...
# Run tests with coverage
go test -cover ./...
# Run specific test package
go test ./app/service/...
```
### Test Structure
```
app/
├── service/
│ ├── auth_service.go
│ ├── auth_service_test.go
│ ├── conversation_service.go
│ └── conversation_service_test.go
└── handler/
├── auth_handler.go
├── auth_handler_test.go
├── chat_handler.go
└── chat_handler_test.go
```
### Writing Tests
Example service test:
```go
func TestAuthService_ValidateToken(t *testing.T) {
// Setup
service := NewAuthService(mockRepo, mockConfig)
// Test cases
tests := []struct {
name string
token string
expectValid bool
expectError bool
}{
{"valid token", "valid.jwt.token", true, false},
{"invalid token", "invalid.token", false, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
valid, err := service.ValidateToken(tt.token)
assert.Equal(t, tt.expectValid, valid)
assert.Equal(t, tt.expectError, err != nil)
})
}
}
```
## Docker Development
### Building Images
```bash
# Build API gateway
docker build -t jan-api-gateway:dev ./apps/jan-api-gateway
# Build inference model
docker build -t jan-inference-model:dev ./apps/jan-inference-model
```
### Development Compose
For local development without Kubernetes:
```yaml
# docker-compose.dev.yml
version: '3.8'
services:
postgres:
image: postgres:14
environment:
POSTGRES_DB: jan
POSTGRES_USER: jan-user
POSTGRES_PASSWORD: jan-password
ports:
- "5432:5432"
api-gateway:
build: ./apps/jan-api-gateway
ports:
- "8080:8080"
environment:
- JAN_INFERENCE_MODEL_URL=http://inference-model:8101
- DB_POSTGRESQL_WRITE_DSN=host=postgres user=jan-user password=jan-password dbname=jan port=5432 sslmode=disable
depends_on:
- postgres
inference-model:
build: ./apps/jan-inference-model
ports:
- "8101:8101"
```
## Debugging
### Go Debugging
For VS Code debugging, add to `.vscode/launch.json`:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Jan API Gateway",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/apps/jan-api-gateway/application/cmd/server",
"env": {
"JAN_INFERENCE_MODEL_URL": "http://localhost:8101",
"JWT_SECRET": "development-secret"
}
}
]
}
```
### Application Logs
```bash
# View API gateway logs
kubectl logs deployment/jan-server-jan-api-gateway -f
# View inference model logs
kubectl logs deployment/jan-server-jan-inference-model -f
# View PostgreSQL logs
kubectl logs statefulset/jan-server-postgresql -f
```
### Log Levels
Set log level via environment variable:
```bash
export LOG_LEVEL=debug # debug, info, warn, error
```
## Code Style and Standards
### Go Standards
- Follow [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments)
- Use `gofmt` for formatting
- Run `go vet` for static analysis
- Use meaningful variable and function names
### API Standards
- RESTful endpoint design
- OpenAPI/Swagger annotations for all endpoints
- Consistent error response format
- Proper HTTP status codes
### Git Workflow
```bash
# Create feature branch
git checkout -b feature/your-feature-name
# Make changes and commit
git add .
git commit -m "feat: add new authentication endpoint"
# Push and create PR
git push origin feature/your-feature-name
```
### Commit Message Format
Follow conventional commits:
```
feat: add new feature
fix: resolve bug in authentication
docs: update API documentation
test: add unit tests for service layer
refactor: improve error handling
```
## Performance Testing
### Load Testing
Use [k6](https://k6.io) for API load testing:
```javascript
// load-test.js
import http from 'k6/http';
export default function () {
const response = http.post('http://localhost:8080/api/v1/chat/completions', {
model: 'jan-v1-4b',
messages: [
{ role: 'user', content: 'Hello!' }
]
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token'
}
});
check(response, {
'status is 200': (r) => r.status === 200,
'response time < 5000ms': (r) => r.timings.duration < 5000,
});
}
```
Run load test:
```bash
k6 run --vus 10 --duration 30s load-test.js
```
### Memory Profiling
Enable Go profiling endpoints:
```go
import _ "net/http/pprof"
// In main.go
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
```
Profile memory usage:
```bash
go tool pprof http://localhost:6060/debug/pprof/heap
```
## Contributing
### Pull Request Process
1. **Fork the repository**
2. **Create feature branch** from `main`
3. **Make changes** following code standards
4. **Add tests** for new functionality
5. **Update documentation** if needed
6. **Submit pull request** with clear description
### Code Review Checklist
- [ ] Code follows Go standards
- [ ] Tests added for new features
- [ ] Documentation updated
- [ ] API endpoints have Swagger annotations
- [ ] No breaking changes without version bump
- [ ] Security considerations addressed
### Issues and Bug Reports
When reporting bugs, include:
- **Environment**: OS, Go version, minikube version
- **Steps to reproduce**: Clear, minimal reproduction steps
- **Expected behavior**: What should happen
- **Actual behavior**: What actually happens
- **Logs**: Relevant error messages or logs
For security issues, please report privately to the maintainers instead of creating public issues.
## Release Process
### Version Management
Jan Server uses semantic versioning (semver):
- **Major**: Breaking changes
- **Minor**: New features, backward compatible
- **Patch**: Bug fixes, backward compatible
### Building Releases
```bash
# Tag release
git tag -a v1.2.3 -m "Release v1.2.3"
# Build release images
docker build -t jan-api-gateway:v1.2.3 ./apps/jan-api-gateway
docker build -t jan-inference-model:v1.2.3 ./apps/jan-inference-model
# Push tags
git push origin v1.2.3
```
### Deployment
Production deployments follow the same Helm chart structure:
```bash
# Deploy specific version
helm install jan-server ./charts/umbrella-chart \
--set jan-api-gateway.image.tag=v1.2.3 \
--set jan-inference-model.image.tag=v1.2.3
```

View File

@ -0,0 +1,39 @@
---
title: Jan Server
description: Self-hosted AI infrastructure running the Jan platform on Kubernetes.
keywords:
[
Jan Server,
self-hosted AI,
Kubernetes deployment,
Docker containers,
AI inference,
local LLM server,
VLLM,
Go API gateway,
Jan-v1 model
]
---
## Self-Hosted Jan Platform
Jan Server deploys the Jan AI platform on your own infrastructure using Kubernetes. It provides a complete AI inference stack with API gateway, model serving, and data persistence.
Jan Server is in early development. APIs and deployment methods may change.
## Architecture Overview
Jan Server consists of two main components:
- **API Gateway**: Go application handling authentication, web requests, and external integrations
- **Inference Model**: VLLM server running the Jan-v1-4B model for AI inference
- **PostgreSQL**: Database for user data, conversations, and system state
## Key Features
- **Kubernetes Native**: Deploys via Helm charts with minikube support
- **Jan-v1 Model**: 4B parameter model optimized for reasoning and tool use
- **OpenAI Compatible API**: Standard endpoints for integration
- **Authentication**: JWT tokens and OAuth2 Google integration
- **External Integrations**: Serper API for web search capabilities
- **Development Ready**: Local development environment with hot reload

View File

@ -0,0 +1,151 @@
---
title: Installation
description: Install and deploy Jan Server on Kubernetes using minikube and Helm.
---
## Prerequisites
Jan Server requires the following tools installed on your system:
- **Docker**: For building container images
- **minikube**: Local Kubernetes cluster for development
- **Helm**: Package manager for Kubernetes applications
- **kubectl**: Kubernetes command-line tool (installed with minikube)
Jan Server currently supports minikube for local development. Production Kubernetes deployments are planned for future releases.
## Quick Start
1. **Clone the repository**
```bash
git clone https://github.com/menloresearch/jan-server
cd jan-server
```
2. **Start minikube**
```bash
minikube start
```
3. **Configure Docker environment**
```bash
eval $(minikube docker-env)
alias kubectl="minikube kubectl --"
```
4. **Deploy Jan Server**
```bash
./scripts/run.sh
```
5. **Access the API**
The script automatically forwards port 8080. Access the Swagger UI at:
```
http://localhost:8080/api/swagger/index.html#/
```
## Manual Installation
### Build Docker Images
Build both required Docker images:
```bash
# Build API Gateway
docker build -t jan-api-gateway:latest ./apps/jan-api-gateway
# Build Inference Model
docker build -t jan-inference-model:latest ./apps/jan-inference-model
```
The inference model image downloads the Jan-v1-4B model from Hugging Face during build. This requires an internet connection and several GB of download.
### Deploy with Helm
Install the Helm chart:
```bash
# Update Helm dependencies
helm dependency update ./charts/umbrella-chart
# Install Jan Server
helm install jan-server ./charts/umbrella-chart
```
### Port Forwarding
Forward the API gateway port to access from your local machine:
```bash
kubectl port-forward svc/jan-server-jan-api-gateway 8080:8080
```
## Verify Installation
Check that all pods are running:
```bash
kubectl get pods
```
Expected output:
```
NAME READY STATUS RESTARTS
jan-server-jan-api-gateway-xxx 1/1 Running 0
jan-server-jan-inference-model-xxx 1/1 Running 0
jan-server-postgresql-0 1/1 Running 0
```
Test the API gateway:
```bash
curl http://localhost:8080/health
```
## Uninstalling
To remove Jan Server:
```bash
helm uninstall jan-server
```
To stop minikube:
```bash
minikube stop
```
## Troubleshooting
### Common Issues
**Pods in `ImagePullBackOff` state**
- Ensure Docker images were built in the minikube environment
- Run `eval $(minikube docker-env)` before building images
**Port forwarding connection refused**
- Verify the service is running: `kubectl get svc`
- Check pod status: `kubectl get pods`
- Review logs: `kubectl logs deployment/jan-server-jan-api-gateway`
**Inference model download fails**
- Ensure internet connectivity during Docker build
- The Jan-v1-4B model is approximately 2.4GB
### Resource Requirements
**Minimum System Requirements:**
- 8GB RAM
- 20GB free disk space
- 4 CPU cores
**Recommended System Requirements:**
- 16GB RAM
- 50GB free disk space
- 8 CPU cores
- GPU support (for faster inference)
The inference model requires significant memory. Ensure your minikube cluster has adequate resources allocated.

View File

@ -3,53 +3,16 @@
"type": "separator",
"title": "Switcher"
},
"index": "Overview",
"getting-started-separator": {
"title": "GETTING STARTED",
"type": "separator"
},
"quickstart": "QuickStart",
"desktop": "Install 👋 Jan",
"jan-models": "Models",
"assistants": "Create Assistants",
"remote-models": "Cloud Providers",
"mcp-examples": "Tutorials",
"explanation-separator": {
"title": "EXPLANATION",
"type": "separator"
},
"llama-cpp": "Local AI Engine",
"model-parameters": "Model Parameters",
"privacy-policy": {
"index": {
"type": "page",
"display": "hidden",
"title": "Privacy Policy"
"title": "Jan Overview"
},
"advanced-separator": {
"title": "ADVANCED",
"type": "separator"
"desktop": {
"type": "page",
"title": "Jan Desktop & Mobile"
},
"manage-models": "Manage Models",
"mcp": "Model Context Protocol",
"localserver": {
"title": "LOCAL SERVER",
"type": "separator"
},
"api-server": "Server Setup",
"llama-cpp-server": "LlamaCpp Server",
"server-settings": "Server Settings",
"server-troubleshooting": "Server Troubleshooting",
"server-examples": "Integrations",
"reference-separator": {
"title": "REFERENCE",
"type": "separator"
},
"settings": "Settings",
"data-folder": "Jan Data Folder",
"troubleshooting": "Troubleshooting",
"privacy": "Privacy"
"server": {
"type": "page",
"title": "Jan Server"
}
}

View File

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 163 KiB

View File

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 149 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 562 KiB

After

Width:  |  Height:  |  Size: 562 KiB

View File

Before

Width:  |  Height:  |  Size: 598 KiB

After

Width:  |  Height:  |  Size: 598 KiB

View File

Before

Width:  |  Height:  |  Size: 306 KiB

After

Width:  |  Height:  |  Size: 306 KiB

View File

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View File

Before

Width:  |  Height:  |  Size: 450 KiB

After

Width:  |  Height:  |  Size: 450 KiB

View File

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

View File

Before

Width:  |  Height:  |  Size: 453 KiB

After

Width:  |  Height:  |  Size: 453 KiB

View File

Before

Width:  |  Height:  |  Size: 714 KiB

After

Width:  |  Height:  |  Size: 714 KiB

View File

Before

Width:  |  Height:  |  Size: 554 KiB

After

Width:  |  Height:  |  Size: 554 KiB

View File

Before

Width:  |  Height:  |  Size: 377 KiB

After

Width:  |  Height:  |  Size: 377 KiB

View File

Before

Width:  |  Height:  |  Size: 453 KiB

After

Width:  |  Height:  |  Size: 453 KiB

View File

Before

Width:  |  Height:  |  Size: 616 KiB

After

Width:  |  Height:  |  Size: 616 KiB

View File

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 742 KiB

View File

Before

Width:  |  Height:  |  Size: 544 KiB

After

Width:  |  Height:  |  Size: 544 KiB

View File

Before

Width:  |  Height:  |  Size: 404 KiB

After

Width:  |  Height:  |  Size: 404 KiB

View File

Before

Width:  |  Height:  |  Size: 432 KiB

After

Width:  |  Height:  |  Size: 432 KiB

View File

Before

Width:  |  Height:  |  Size: 499 KiB

After

Width:  |  Height:  |  Size: 499 KiB

View File

Before

Width:  |  Height:  |  Size: 514 KiB

After

Width:  |  Height:  |  Size: 514 KiB

View File

Before

Width:  |  Height:  |  Size: 986 KiB

After

Width:  |  Height:  |  Size: 986 KiB

View File

Before

Width:  |  Height:  |  Size: 718 KiB

After

Width:  |  Height:  |  Size: 718 KiB

View File

Before

Width:  |  Height:  |  Size: 685 KiB

After

Width:  |  Height:  |  Size: 685 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

Before

Width:  |  Height:  |  Size: 428 KiB

After

Width:  |  Height:  |  Size: 428 KiB

View File

Before

Width:  |  Height:  |  Size: 524 KiB

After

Width:  |  Height:  |  Size: 524 KiB

View File

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

View File

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 147 KiB

View File

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 353 KiB

View File

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 266 KiB

View File

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 247 KiB

View File

Before

Width:  |  Height:  |  Size: 314 KiB

After

Width:  |  Height:  |  Size: 314 KiB

View File

Before

Width:  |  Height:  |  Size: 308 KiB

After

Width:  |  Height:  |  Size: 308 KiB

View File

Before

Width:  |  Height:  |  Size: 500 KiB

After

Width:  |  Height:  |  Size: 500 KiB

View File

Before

Width:  |  Height:  |  Size: 314 KiB

After

Width:  |  Height:  |  Size: 314 KiB

View File

Before

Width:  |  Height:  |  Size: 558 KiB

After

Width:  |  Height:  |  Size: 558 KiB

View File

Before

Width:  |  Height:  |  Size: 649 KiB

After

Width:  |  Height:  |  Size: 649 KiB

View File

Before

Width:  |  Height:  |  Size: 474 KiB

After

Width:  |  Height:  |  Size: 474 KiB

View File

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View File

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Some files were not shown because too many files have changed in this diff Show More