├── .eslintrc.json ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── assets ├── brand │ ├── folia.svg │ ├── logo-marker-dark.svg │ ├── logo-marker-light.svg │ ├── logo.svg │ ├── paper.svg │ ├── velocity.svg │ └── waterfall.svg ├── data │ └── team.json ├── icons │ ├── cc │ │ ├── by.svg │ │ ├── cc.svg │ │ └── sa.svg │ ├── fontawesome │ │ ├── box-archive.svg │ │ ├── clone-icon.svg │ │ ├── discord-brands.svg │ │ ├── github-brands.svg │ │ └── twitter-brands.svg │ └── heroicons │ │ ├── arrow-top-right-on-square.svg │ │ ├── bolt.svg │ │ ├── chat-bubble-left-right.svg │ │ ├── chevron-down.svg │ │ ├── code-bracket.svg │ │ ├── document-download.svg │ │ ├── globe-americas.svg │ │ ├── heart.svg │ │ └── menu.svg ├── illustrations │ └── undraw │ │ ├── chatting.svg │ │ ├── code-review.svg │ │ ├── knowledge.svg │ │ └── savings.svg └── images │ ├── community.png │ ├── community.webp │ ├── home-1.png │ ├── home-1.webp │ ├── home-2.png │ ├── home-2.webp │ ├── home-3.png │ ├── home-3.webp │ ├── velocity.png │ └── velocity.webp ├── next-env.d.ts ├── next-sitemap.config.js ├── next.config.js ├── package.json ├── pnpm-lock.yaml ├── public ├── .gitignore ├── assets │ └── logo │ │ └── 256x.png ├── favicon.ico └── robots.txt ├── renovate.json ├── src ├── components │ ├── data │ │ ├── FeatureCard.tsx │ │ ├── Skeleton.tsx │ │ ├── SoftwareBuildChanges.tsx │ │ ├── SoftwareBuilds.tsx │ │ ├── SoftwareBuildsTable.tsx │ │ ├── SoftwareDownloadCard.tsx │ │ ├── SoftwarePreview.tsx │ │ └── Terminal.tsx │ ├── input │ │ ├── Button.tsx │ │ ├── IconButton.tsx │ │ ├── SegmentedControlIem.tsx │ │ ├── SegmentedControls.tsx │ │ └── SoftwareDownloadButton.tsx │ ├── layout │ │ ├── DownloadsTree.tsx │ │ ├── Footer.tsx │ │ ├── NavBar.tsx │ │ ├── NavDropDown.tsx │ │ ├── NavDropDownLink.tsx │ │ ├── NavLink.tsx │ │ ├── SoftwareDownload.tsx │ │ └── SoftwareHeader.tsx │ └── util │ │ └── SEO.tsx ├── lib │ ├── context │ │ └── downloads.ts │ ├── service │ │ ├── api.ts │ │ ├── bstats.ts │ │ ├── github.ts │ │ ├── hangar.ts │ │ ├── sponsors.ts │ │ ├── types.ts │ │ └── v2.ts │ └── util │ │ ├── time.ts │ │ └── types.ts └── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── community │ ├── guidelines.tsx │ └── index.tsx │ ├── contribute.tsx │ ├── downloads │ ├── all.tsx │ ├── index.tsx │ ├── paper.tsx │ ├── velocity.tsx │ └── waterfall.tsx │ ├── index.tsx │ ├── javadocs.tsx │ ├── software │ ├── folia │ │ └── index.tsx │ ├── paper │ │ └── index.tsx │ ├── velocity │ │ └── index.tsx │ └── waterfall │ │ └── index.tsx │ ├── sponsors.tsx │ └── team.tsx ├── styles ├── components │ ├── data │ │ ├── SoftwareBuildChanges.module.css │ │ └── SoftwareBuildsTable.module.css │ ├── input │ │ └── SoftwareDownloadButton.module.css │ └── layout │ │ └── Footer.module.css └── globals.css ├── tsconfig.json └── windi.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "plugin:prettier/recommended", 5 | "plugin:@typescript-eslint/recommended", 6 | "plugin:react/jsx-runtime" 7 | ], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "project": "./tsconfig.json" 11 | }, 12 | "rules": { 13 | "import/order": [ 14 | "warn", 15 | { 16 | "alphabetize": { 17 | "order": "asc" 18 | }, 19 | "newlines-between": "always" 20 | } 21 | ], 22 | "@typescript-eslint/consistent-type-imports": "error", 23 | "@typescript-eslint/consistent-type-exports": [ 24 | "error", 25 | { 26 | "fixMixedExportsWithInlineTypeSpecifier": true 27 | } 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # typescript 32 | *.tsbuildinfo 33 | 34 | # intellij 35 | /.idea 36 | *.iml 37 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | enable-pre-post-scripts=true 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 PaperMC Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # papermc.io 2 | 3 | ## Development 4 | 5 | ### Getting started 6 | 7 | First, install the dependencies. We use pnpm as the package manager. 8 | 9 | ```bash 10 | pnpm install 11 | ``` 12 | 13 | Then, run the development server: 14 | 15 | ```bash 16 | pnpm dev 17 | ``` 18 | 19 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 20 | 21 | ### Learn More 22 | 23 | To learn more about technologies used in this project, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | - [Tailwind CSS Documentation](https://tailwindui.com/documentation) - learn about Tailwind CSS utilities. -------------------------------------------------------------------------------- /assets/brand/folia.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 14 | 33 | 37 | 38 | -------------------------------------------------------------------------------- /assets/brand/paper.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/brand/velocity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /assets/brand/waterfall.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/data/team.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "leadership-team", 4 | "name": "Leadership Team", 5 | "description": "Leadership Team members are responsible for the direction of the project, and for making the big decisions. Whether they focus on the code or the community, they're essential to making everything run and have final say in just about anything.", 6 | "members": [ 7 | { 8 | "name": "electronicboy", 9 | "avatar": "https://avatars.githubusercontent.com/u/1228900?v=4", 10 | "discord": ".electronicboy", 11 | "github": "electronicboy" 12 | }, 13 | { 14 | "name": "kashike", 15 | "avatar": "https://avatars.githubusercontent.com/u/5474071?v=4", 16 | "discord": "creamfilled.", 17 | "github": "kashike" 18 | }, 19 | { 20 | "name": "kennytv", 21 | "avatar": "https://avatars.githubusercontent.com/u/28825609?v=4", 22 | "discord": "kennytv", 23 | "github": "kennytv" 24 | }, 25 | { 26 | "name": "MiniDigger", 27 | "avatar": "https://avatars.githubusercontent.com/u/2185527?v=4", 28 | "discord": "minidigger", 29 | "github": "MiniDigger" 30 | } 31 | ] 32 | }, 33 | { 34 | "id": "community-managers", 35 | "name": "Community Managers", 36 | "description": "Community Managers help guide and support the direction that the community goes in. They can help with just about anything, and focus on communications and moderation within PaperMC's community spaces.", 37 | "members": [ 38 | { 39 | "name": "Larry", 40 | "avatar": "https://avatars.githubusercontent.com/u/58414982?v=4", 41 | "discord": "larbl", 42 | "github": "Laarryy" 43 | }, 44 | { 45 | "name": "mbaxter", 46 | "avatar": "https://avatars.githubusercontent.com/u/181668?v=4", 47 | "discord": "mbaxter", 48 | "github": "mbax" 49 | } 50 | ] 51 | }, 52 | { 53 | "id": "paper-maintainers", 54 | "name": "Paper Maintainers", 55 | "description": "Maintainers help decide and implement major codebase decisions, and are often around to guide major version updates. By contributing their time and talents, they ensure that our codebases are nice and shiny, and our performance is outmatched.", 56 | "members": [ 57 | { 58 | "name": "jmp", 59 | "avatar": "https://avatars.githubusercontent.com/u/11360596?v=4", 60 | "discord": "jmp_", 61 | "github": "jpenilla" 62 | }, 63 | { 64 | "name": "Machine Maker", 65 | "avatar": "https://avatars.githubusercontent.com/u/15055071?v=4", 66 | "discord": "machinemaker", 67 | "github": "Machine-Maker" 68 | }, 69 | { 70 | "name": "Spottedleaf", 71 | "avatar": "https://avatars.githubusercontent.com/u/6100722?v=4", 72 | "discord": "spottedleaf", 73 | "github": "Spottedleaf" 74 | }, 75 | { 76 | "name": "lynxplay", 77 | "avatar": "https://avatars.githubusercontent.com/u/32834385?v=4", 78 | "discord": "lynxplay", 79 | "github": "lynxplay" 80 | } 81 | ] 82 | }, 83 | { 84 | "id": "paper-developers", 85 | "name": "Paper Developers", 86 | "description": "Paper Developers help with writing code, reviewing PRs, and keeping the project lively. By contributing new features and helping to polish off old code, they make Paper run.", 87 | "members": [ 88 | { 89 | "name": "Lulu", 90 | "avatar": "https://avatars.githubusercontent.com/u/41980282?v=4", 91 | "discord": "lulu13022002", 92 | "github": "Lulu13022002" 93 | }, 94 | { 95 | "name": "Noah", 96 | "avatar": "https://avatars.githubusercontent.com/u/44026893?v=4", 97 | "discord": "noah.pm", 98 | "github": "NoahvdAa" 99 | }, 100 | { 101 | "name": "Owen", 102 | "avatar": "https://avatars.githubusercontent.com/u/23108066?v=4", 103 | "discord": "owen.zip", 104 | "github": "Owen1212055" 105 | }, 106 | { 107 | "name": "Warrior", 108 | "avatar": "https://avatars.githubusercontent.com/u/50800980?v=4", 109 | "discord": "warriorrr.", 110 | "github": "Warriorrrr" 111 | }, 112 | { 113 | "name": "Tamion", 114 | "avatar": "https://avatars.githubusercontent.com/u/70228790?v=4", 115 | "discord": "tamion", 116 | "github": "notTamion" 117 | } 118 | ] 119 | }, 120 | { 121 | "id": "velocity-developers", 122 | "name": "Velocity Developers", 123 | "description": "Velocity Developers manage and develop the Velocity proxy. From handling PRs and issues to writing the fastest production proxy code there is, they do whatever Velocity needs.", 124 | "members": [ 125 | { 126 | "name": "CoreyShupe", 127 | "avatar": "https://avatars.githubusercontent.com/u/37425956?v=4", 128 | "discord": "fixed", 129 | "github": "CoreyShupe" 130 | }, 131 | { 132 | "name": "Gero", 133 | "avatar": "https://avatars.githubusercontent.com/u/11089941?v=4", 134 | "discord": "gerrygames", 135 | "github": "Gerrygames" 136 | }, 137 | { 138 | "name": "Tux", 139 | "avatar": "https://avatars.githubusercontent.com/u/16436212?v=4", 140 | "discord": "possiblytux", 141 | "github": "astei" 142 | }, 143 | { 144 | "name": "4drian3d", 145 | "avatar": "https://avatars.githubusercontent.com/u/68704415?v=4", 146 | "discord": "4drian3d", 147 | "github": "4drian3d" 148 | } 149 | ] 150 | }, 151 | { 152 | "id": "adventure-developers", 153 | "name": "Adventure Developers", 154 | "description": "Adventure Developers manage and develop the adventure user-interface library for minecraft. They are responsible both for day-to-day project maintenance and leading the project itself, keeping all users and platforms adventure is relevant for in mind.", 155 | "members": [ 156 | { 157 | "name": "Kezz", 158 | "avatar": "https://avatars.githubusercontent.com/u/1526243?v=4", 159 | "discord": "kezz101", 160 | "github": "kezz" 161 | }, 162 | { 163 | "name": "zml", 164 | "avatar": "https://avatars.githubusercontent.com/u/629092?v=4", 165 | "discord": "zml", 166 | "github": "zml2008" 167 | } 168 | ] 169 | }, 170 | { 171 | "id": "community-team", 172 | "name": "Community Team", 173 | "description": "Community Team members keeps our community a safe and vibrant space. By watching chat, helping newcomers with questions, and making sure everyone follows the community guidelines, PaperMC's community spaces are able to be as fantastic as they are!", 174 | "members": [ 175 | { 176 | "name": "Amaranth", 177 | "avatar": "https://avatars.githubusercontent.com/u/341418?v=4", 178 | "discord": "amaranth", 179 | "github": "amaranth" 180 | }, 181 | { 182 | "name": "EterNity", 183 | "avatar": "https://avatars.githubusercontent.com/u/71995692?v=4", 184 | "discord": "eternity._.", 185 | "github": "EterNityCH" 186 | }, 187 | { 188 | "name": "Glare", 189 | "avatar": "https://avatars.githubusercontent.com/u/21014720?v=4", 190 | "discord": "glare", 191 | "github": "darbyjack" 192 | }, 193 | { 194 | "name": "gsand", 195 | "avatar": "https://avatars.githubusercontent.com/u/1694336?v=4", 196 | "discord": "gsand", 197 | "github": "gsand" 198 | }, 199 | { 200 | "name": "JRoy", 201 | "avatar": "https://avatars.githubusercontent.com/u/10731363?v=4", 202 | "discord": "jroy.", 203 | "github": "JRoy" 204 | }, 205 | { 206 | "name": "Me4502", 207 | "avatar": "https://avatars.githubusercontent.com/u/546754?v=4", 208 | "discord": "me4502", 209 | "github": "me4502" 210 | }, 211 | { 212 | "name": "Michael", 213 | "avatar": "https://avatars.githubusercontent.com/u/28601081?v=4", 214 | "discord": "clx_", 215 | "github": "clrxbl" 216 | }, 217 | { 218 | "name": "NotMyFault", 219 | "avatar": "https://avatars.githubusercontent.com/u/13383509?v=4", 220 | "discord": "notmyfault", 221 | "github": "NotMyFault" 222 | }, 223 | { 224 | "name": "ocelotpotpie", 225 | "avatar": "https://avatars.githubusercontent.com/u/482353?v=4", 226 | "discord": "ocelotpotpie", 227 | "github": "ocelotpotpie" 228 | }, 229 | { 230 | "name": "Puremin0rez", 231 | "avatar": "https://avatars.githubusercontent.com/u/734478?v=4", 232 | "discord": "puremin0rez", 233 | "github": "Puremin0rez" 234 | }, 235 | { 236 | "name": "STG-Allen", 237 | "avatar": "https://avatars.githubusercontent.com/u/26695048?v=4", 238 | "discord": "stgallen", 239 | "github": "STG-Allen" 240 | }, 241 | { 242 | "name": "stefvanschie", 243 | "discord": "stefvanschie", 244 | "github": "stefvanschie" 245 | } 246 | ] 247 | }, 248 | { 249 | "id": "triage-team", 250 | "name": "Triage Team", 251 | "description": "The Triage Team helps the project by reviewing, testing, and organizing our GitHub's issues and pull requests. With their efforts, developers and maintainers are able to most efficiently handle the most important tasks.", 252 | "members": [ 253 | { 254 | "name": "_11", 255 | "avatar": "https://avatars.githubusercontent.com/u/55677189?v=4", 256 | "discord": "_11", 257 | "github": "underscore11code" 258 | }, 259 | { 260 | "name": "Camm", 261 | "avatar": "https://avatars.githubusercontent.com/u/41496198?v=4", 262 | "discord": "_camm", 263 | "github": "CodeByCam" 264 | }, 265 | { 266 | "name": "Camotoy", 267 | "avatar": "https://avatars.githubusercontent.com/u/20743703?v=4", 268 | "discord": "camotoy", 269 | "github": "camotoy" 270 | }, 271 | { 272 | "name": "Emily", 273 | "avatar": "https://avatars.githubusercontent.com/u/35617540?v=4", 274 | "discord": "emily.ar", 275 | "github": "emilyy-dev" 276 | }, 277 | { 278 | "name": "Malfrador", 279 | "avatar": "https://avatars.githubusercontent.com/u/6106558?v=4", 280 | "discord": "malfrador", 281 | "github": "Malfrador" 282 | }, 283 | { 284 | "name": "Nacio", 285 | "avatar": "https://avatars.githubusercontent.com/u/41339226?v=4", 286 | "discord": "nacio.", 287 | "github": "Nacioszeczek" 288 | }, 289 | { 290 | "name": "Ollie", 291 | "avatar": "https://avatars.githubusercontent.com/u/69084614?v=4", 292 | "discord": "ollieee_", 293 | "github": "olijeffers0n" 294 | }, 295 | { 296 | "name": "Pop", 297 | "avatar": "https://avatars.githubusercontent.com/u/17698576?v=4", 298 | "discord": "pop4959", 299 | "github": "pop4959" 300 | } 301 | ] 302 | }, 303 | { 304 | "id": "docs-team", 305 | "name": "Docs Team", 306 | "description": "The Docs Team is responsible for maintaining and updating the PaperMC documentation. They ensure that the documentation is up-to-date and accurate, and that it's easy to understand for all users.", 307 | "members": [ 308 | { 309 | "name": "zlataovce", 310 | "avatar": "https://avatars.githubusercontent.com/u/34477304?v=4", 311 | "discord": "zlataovce", 312 | "github": "zlataovce" 313 | } 314 | ] 315 | } 316 | ] 317 | -------------------------------------------------------------------------------- /assets/icons/cc/by.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /assets/icons/cc/cc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /assets/icons/cc/sa.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /assets/icons/fontawesome/box-archive.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/fontawesome/clone-icon.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/fontawesome/discord-brands.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/fontawesome/github-brands.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/fontawesome/twitter-brands.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/heroicons/arrow-top-right-on-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/heroicons/bolt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/heroicons/chat-bubble-left-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/heroicons/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/icons/heroicons/code-bracket.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/heroicons/document-download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/icons/heroicons/globe-americas.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/heroicons/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/heroicons/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/illustrations/undraw/chatting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/illustrations/undraw/code-review.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 7 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 23 | 25 | 27 | 29 | 30 | 31 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | -------------------------------------------------------------------------------- /assets/illustrations/undraw/knowledge.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/community.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperMC/website/40cd26f51120c8e724b365d18f3b8a0d1ec05c7b/assets/images/community.png -------------------------------------------------------------------------------- /assets/images/community.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperMC/website/40cd26f51120c8e724b365d18f3b8a0d1ec05c7b/assets/images/community.webp -------------------------------------------------------------------------------- /assets/images/home-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperMC/website/40cd26f51120c8e724b365d18f3b8a0d1ec05c7b/assets/images/home-1.png -------------------------------------------------------------------------------- /assets/images/home-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperMC/website/40cd26f51120c8e724b365d18f3b8a0d1ec05c7b/assets/images/home-1.webp -------------------------------------------------------------------------------- /assets/images/home-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperMC/website/40cd26f51120c8e724b365d18f3b8a0d1ec05c7b/assets/images/home-2.png -------------------------------------------------------------------------------- /assets/images/home-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperMC/website/40cd26f51120c8e724b365d18f3b8a0d1ec05c7b/assets/images/home-2.webp -------------------------------------------------------------------------------- /assets/images/home-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperMC/website/40cd26f51120c8e724b365d18f3b8a0d1ec05c7b/assets/images/home-3.png -------------------------------------------------------------------------------- /assets/images/home-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperMC/website/40cd26f51120c8e724b365d18f3b8a0d1ec05c7b/assets/images/home-3.webp -------------------------------------------------------------------------------- /assets/images/velocity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperMC/website/40cd26f51120c8e724b365d18f3b8a0d1ec05c7b/assets/images/velocity.png -------------------------------------------------------------------------------- /assets/images/velocity.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperMC/website/40cd26f51120c8e724b365d18f3b8a0d1ec05c7b/assets/images/velocity.webp -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next-sitemap.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next-sitemap').IConfig} */ 2 | 3 | const dev = process.env.NODE_ENV !== 'production'; 4 | 5 | module.exports = { 6 | siteUrl: dev ? 'http://localhost:3000' : 'https://papermc.io', 7 | exclude: ["/downloads/all"], 8 | generateIndexSitemap: false, 9 | }; 10 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const WindiCSSWebpackPlugin = require("windicss-webpack-plugin"); 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | reactStrictMode: true, 6 | webpack: (config) => { 7 | config.plugins.push(new WindiCSSWebpackPlugin()); 8 | config.module.rules.push({ 9 | test: /\.svg$/, 10 | use: [ 11 | { 12 | loader: "@svgr/webpack", 13 | options: { dimensions: false }, 14 | }, 15 | ], 16 | }); 17 | 18 | return config; 19 | }, 20 | }; 21 | 22 | module.exports = nextConfig; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "papermc.io", 3 | "version": "0.1.0", 4 | "author": { 5 | "name": "Cubxity", 6 | "email": "contact@cubxity.dev", 7 | "url": "https://cubxity.dev" 8 | }, 9 | "private": true, 10 | "scripts": { 11 | "dev": "next dev", 12 | "build": "next build", 13 | "postbuild": "next-sitemap", 14 | "start": "next start", 15 | "lint": "next lint" 16 | }, 17 | "dependencies": { 18 | "@fontsource/poppins": "5.0.14", 19 | "@headlessui/react": "2.1.2", 20 | "clsx": "2.1.1", 21 | "next": "13.5.4", 22 | "next-sitemap": "^4.2.3", 23 | "react": "18.3.1", 24 | "react-dom": "18.3.1", 25 | "sharp": "^0.33.5", 26 | "swr": "2.2.5" 27 | }, 28 | "devDependencies": { 29 | "@svgr/webpack": "8.1.0", 30 | "@types/node": "20.14.11", 31 | "@types/react": "18.3.3", 32 | "@types/react-dom": "18.3.0", 33 | "@typescript-eslint/eslint-plugin": "6.12.0", 34 | "@typescript-eslint/parser": "6.12.0", 35 | "eslint": "8.54.0", 36 | "eslint-config-next": "13.5.4", 37 | "eslint-config-prettier": "9.1.0", 38 | "eslint-plugin-prettier": "5.2.1", 39 | "prettier": "3.3.3", 40 | "typescript": "5.5.3", 41 | "windicss": "3.5.6", 42 | "windicss-webpack-plugin": "1.8.0" 43 | }, 44 | "sideEffects": false, 45 | "packageManager": "pnpm@7.33.7+sha256.d1581d46ed10f54ff0cbdd94a2373b1f070202b0fbff29f27c2ce01460427043" 46 | } 47 | -------------------------------------------------------------------------------- /public/.gitignore: -------------------------------------------------------------------------------- 1 | sitemap.xml 2 | -------------------------------------------------------------------------------- /public/assets/logo/256x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperMC/website/40cd26f51120c8e724b365d18f3b8a0d1ec05c7b/public/assets/logo/256x.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperMC/website/40cd26f51120c8e724b365d18f3b8a0d1ec05c7b/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /downloads/all 3 | Disallow: /repository/ 4 | Disallow: /repo/ 5 | Disallow: /api/ 6 | Allow: / 7 | 8 | Sitemap: https://papermc.io/sitemap.xml 9 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "packageRules": [ 7 | { 8 | "matchPackagePatterns": [ 9 | "*" 10 | ], 11 | "matchUpdateTypes": [ 12 | "minor", 13 | "patch" 14 | ], 15 | "groupName": "all non-major dependencies", 16 | "groupSlug": "all-minor-patch" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /src/components/data/FeatureCard.tsx: -------------------------------------------------------------------------------- 1 | import type { FunctionComponent } from "react"; 2 | 3 | export interface FeatureCardProps { 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | icon: FunctionComponent; 6 | label: string; 7 | description: string; 8 | } 9 | 10 | const FeatureCard = ({ icon: Icon, label, description }: FeatureCardProps) => ( 11 |
12 |
13 |
14 | 15 |
16 |

{label}

17 |
18 |

{description}

19 |
20 | ); 21 | 22 | export default FeatureCard; 23 | -------------------------------------------------------------------------------- /src/components/data/Skeleton.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import type { ReactElement } from "react"; 3 | 4 | export interface SkeletonProps { 5 | className?: string; 6 | } 7 | 8 | const Skeleton = ({ className }: SkeletonProps): ReactElement => ( 9 |
15 | ); 16 | 17 | export default Skeleton; 18 | -------------------------------------------------------------------------------- /src/components/data/SoftwareBuildChanges.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactElement } from "react"; 2 | import { Fragment } from "react"; 3 | 4 | import { getProjectRepository } from "@/lib/service/github"; 5 | import type { Build } from "@/lib/service/types"; 6 | import styles from "@/styles/components/data/SoftwareBuildChanges.module.css"; 7 | 8 | export interface SoftwareBuildChangesProps { 9 | project: string; 10 | build: Build; 11 | version: string; 12 | } 13 | 14 | const SoftwareBuildChanges = ({ 15 | project, 16 | build, 17 | version, 18 | }: SoftwareBuildChangesProps): ReactElement => ( 19 | <> 20 | {build.changes.map((change) => ( 21 |

22 | 28 | {change.commit.slice(0, 7)} 29 | 30 | {highlightIssues(change.summary, project, styles.issue)} 31 |

32 | ))} 33 | {build.changes.length === 0 && No changes} 34 | 35 | ); 36 | 37 | export default SoftwareBuildChanges; 38 | 39 | const highlightIssues = ( 40 | summary: string, 41 | project: string, 42 | highlightClass: string, 43 | ): JSX.Element[] => { 44 | return summary.split(/([^&])(#[0-9]+)/gm).map((part: string, i: number) => { 45 | if (!part.match(/#[0-9]+/)) { 46 | return {part}; 47 | } 48 | 49 | return ( 50 | 57 | {part} 58 | 59 | ); 60 | }); 61 | }; 62 | -------------------------------------------------------------------------------- /src/components/data/SoftwareBuilds.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import type { ReactElement } from "react"; 3 | 4 | import DownloadIcon from "@/assets/icons/heroicons/document-download.svg"; 5 | import Skeleton from "@/components/data/Skeleton"; 6 | import SoftwareBuildChanges from "@/components/data/SoftwareBuildChanges"; 7 | import type { Build } from "@/lib/service/types"; 8 | import { getVersionBuildDownloadURL } from "@/lib/service/v2"; 9 | import { formatRelativeDate, formatISODateTime } from "@/lib/util/time"; 10 | 11 | export interface SoftwareBuildsProps { 12 | project: string; 13 | version: string; 14 | builds?: Build[]; 15 | eol?: boolean; 16 | } 17 | 18 | const SoftwareBuilds = ({ 19 | project, 20 | version, 21 | builds, 22 | eol, 23 | }: SoftwareBuildsProps): ReactElement => ( 24 |
25 | {builds && 26 | builds 27 | .slice() 28 | .reverse() 29 | .slice(0, 10) 30 | .map((build) => ( 31 |
35 | {/* eslint-disable-next-line react/jsx-no-target-blank */} 36 | 52 | #{build.build} 53 | 54 |
55 | 60 |
61 |
65 | {formatRelativeDate(new Date(build.time))} 66 |
67 |
68 | ))} 69 | {!builds && 70 | [...Array(5)].map((_, k) => ( 71 |
72 |
73 | 74 |
75 | 76 |
77 | ))} 78 |
79 | ); 80 | 81 | export default SoftwareBuilds; 82 | -------------------------------------------------------------------------------- /src/components/data/SoftwareBuildsTable.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | 3 | import SoftwareDownloadButton from "../input/SoftwareDownloadButton"; 4 | 5 | import SoftwareBuildChanges from "@/components/data/SoftwareBuildChanges"; 6 | import type { Build } from "@/lib/service/types"; 7 | import { formatRelativeDate, formatISODateTime } from "@/lib/util/time"; 8 | import styles from "@/styles/components/data/SoftwareBuildsTable.module.css"; 9 | 10 | export interface SoftwareBuildsTableProps { 11 | project: string; 12 | version: string; 13 | builds: Build[]; 14 | eol?: boolean; 15 | } 16 | 17 | const SoftwareBuildsTable = ({ 18 | project, 19 | version, 20 | builds, 21 | eol, 22 | }: SoftwareBuildsTableProps) => { 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {builds 35 | .slice() 36 | .reverse() 37 | .map((build) => ( 38 | 39 | 51 | 58 | 64 | 74 | 75 | ))} 76 | 77 |
BuildChangelogTimestampDownload
40 | 48 | #{build.build} 49 | 50 | 52 | 57 | 62 | {formatRelativeDate(new Date(build.time))} 63 | 65 | 73 |
78 | ); 79 | }; 80 | 81 | export default SoftwareBuildsTable; 82 | -------------------------------------------------------------------------------- /src/components/data/SoftwareDownloadCard.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | 3 | import { useProject } from "@/lib/service/v2"; 4 | 5 | export interface SoftwareDownloadCardProps { 6 | id: string; 7 | name: string; 8 | selected?: boolean; 9 | onSelect: () => void; 10 | } 11 | 12 | const SoftwareDownloadCard = ({ 13 | id, 14 | name, 15 | selected, 16 | onSelect, 17 | }: SoftwareDownloadCardProps) => { 18 | const { data } = useProject(id); 19 | 20 | return ( 21 |
29 |
30 |
31 |
32 |

{name}

33 |

{data?.versions.length}

34 |
35 |
36 |
37 | ); 38 | }; 39 | 40 | export default SoftwareDownloadCard; 41 | -------------------------------------------------------------------------------- /src/components/data/SoftwarePreview.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import type { FunctionComponent } from "react"; 3 | 4 | import ArchiveIcon from "@/assets/icons/fontawesome/box-archive.svg"; 5 | 6 | export interface SoftwarePreviewProps { 7 | id: string; 8 | name: string; 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | icon: FunctionComponent; 11 | description?: string; 12 | download?: boolean; 13 | javadocs?: string; 14 | eol?: boolean; 15 | } 16 | 17 | const SoftwarePreview = ({ 18 | id, 19 | name, 20 | icon: Icon, 21 | description, 22 | download, 23 | javadocs, 24 | eol, 25 | }: SoftwarePreviewProps) => ( 26 | 35 |
36 |
37 |
38 | 39 |
40 |

41 | {name} {eol && } 42 |

43 |
44 | 45 | {description && ( 46 |

{description}

47 | )} 48 |
49 | 50 | ); 51 | 52 | export default SoftwarePreview; 53 | -------------------------------------------------------------------------------- /src/components/data/Terminal.tsx: -------------------------------------------------------------------------------- 1 | import type { KeyboardEvent } from "react"; 2 | import { type ReactNode, useEffect, useRef, useState } from "react"; 3 | 4 | import type { ProjectProps } from "@/lib/context/downloads"; 5 | import { formatISOFullTime } from "@/lib/util/time"; 6 | 7 | const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 8 | const getNaturalDelay = () => Math.floor(Math.random() * 80) + 40; 9 | 10 | function InfoLog({ children }: { children: ReactNode }) { 11 | return ( 12 |
13 | 14 | [{formatISOFullTime(new Date())} INFO] 15 | 16 | : {children} 17 |
18 | ); 19 | } 20 | 21 | export function Terminal({ project }: ProjectProps) { 22 | const [cmd, setCmd] = useState(""); 23 | const [args, setArgs] = useState(""); 24 | const [loading, setLoading] = useState(""); 25 | const [output, setOutput] = useState(null); 26 | const [success, setSuccess] = useState(null); 27 | const [input, setInput] = useState(null); 28 | const [cmdOutput, _setCmdOutput] = useState(null); 29 | 30 | const cmdOutputRef = useRef(cmdOutput); 31 | function setCmdOutput(data: ReactNode[]) { 32 | cmdOutputRef.current = data; 33 | _setCmdOutput(data); 34 | } 35 | 36 | const handleCommand = (event: KeyboardEvent) => { 37 | if (event.key === "Enter") { 38 | let currentCmdOutput; 39 | switch (event.currentTarget.value) { 40 | case "help": { 41 | currentCmdOutput = 42 | "Existing commands: help, downloads, plugins, docs, forums, team, contribute"; 43 | break; 44 | } 45 | case "downloads": { 46 | window.location.href = "/downloads"; 47 | currentCmdOutput = "Redirecting..."; 48 | break; 49 | } 50 | case "plugins": { 51 | window.location.href = "https://hangar.papermc.io"; 52 | currentCmdOutput = "Redirecting..."; 53 | break; 54 | } 55 | case "docs": { 56 | window.location.href = "https://docs.papermc.io"; 57 | currentCmdOutput = "Redirecting..."; 58 | break; 59 | } 60 | case "forums": { 61 | window.location.href = "https://forums.papermc.io"; 62 | currentCmdOutput = "Redirecting..."; 63 | break; 64 | } 65 | case "team": { 66 | window.location.href = "/team"; 67 | currentCmdOutput = "Redirecting..."; 68 | break; 69 | } 70 | case "contribute": { 71 | window.location.href = "/contribute"; 72 | currentCmdOutput = "Redirecting..."; 73 | break; 74 | } 75 | default: { 76 | currentCmdOutput = 'Unknown command. Type "help" for help.'; 77 | } 78 | } 79 | setCmdOutput([ 80 | cmdOutputRef.current, 81 |
82 | {">"} {event.currentTarget.value} 83 |
, 84 | {currentCmdOutput}, 85 | ]); 86 | event.currentTarget.value = ""; 87 | } 88 | }; 89 | 90 | useEffect(() => { 91 | const outputLines = [ 92 | `Starting minecraft server version ${project.latestStableVersion}`, 93 | 'Preparing level "world"', 94 | "Preparing start region for dimension minecraft:overworld", 95 | "Time elapsed: 363 ms", 96 | "Preparing start region for dimension minecraft:the_nether", 97 | "Time elapsed: 147 ms", 98 | "Preparing start region for dimension minecraft:the_end", 99 | "Time elapsed: 366 ms", 100 | "Running delayed init tasks", 101 | ]; 102 | 103 | (async () => { 104 | let currentCmd = ""; 105 | for (const char of "java") { 106 | currentCmd += char; 107 | setCmd(currentCmd); 108 | await sleep(getNaturalDelay()); 109 | } 110 | 111 | let currentArgs = ""; 112 | for (const char of " -jar paper.jar") { 113 | currentArgs += char; 114 | setArgs(currentArgs); 115 | await sleep(getNaturalDelay()); 116 | } 117 | 118 | for (let i = 0; i < 3; i++) { 119 | setLoading("Loading libraries, please wait" + ".".repeat(i + 1)); 120 | await sleep(500); 121 | } 122 | 123 | let currentOutput: ReactNode[] = []; 124 | for (let i = 0; i < outputLines.length; i++) { 125 | currentOutput = [ 126 | ...currentOutput, 127 | {outputLines[i]}, 128 | ]; 129 | setOutput(currentOutput); 130 | 131 | await sleep(getNaturalDelay()); 132 | } 133 | 134 | setSuccess( 135 | 136 | 137 | Done (2.274s)! For help, type "help" 138 | 139 | , 140 | ); 141 | 142 | setInput( 143 |
144 | {">"}{" "} 145 | handleCommand(event)} 147 | className="w-105 bg-transparent border-none outline-none" 148 | > 149 |
, 150 | ); 151 | })(); 152 | }, [project.latestStableVersion]); 153 | 154 | return ( 155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 | {input} 163 |
{cmdOutput}
164 |
{success}
165 |
{output}
166 |
167 | {loading} 168 |
169 |
170 | $ 171 | {cmd} 172 | {args} 173 |
174 |
175 |
176 | ); 177 | } 178 | -------------------------------------------------------------------------------- /src/components/input/Button.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import Link from "next/link"; 3 | import type { ReactNode } from "react"; 4 | 5 | export interface ButtonProps { 6 | variant: "outlined" | "filled"; 7 | dense?: boolean; 8 | href: string; 9 | external?: boolean; 10 | className?: string; 11 | children: ReactNode; 12 | } 13 | 14 | const Button = ({ 15 | variant, 16 | dense, 17 | href, 18 | external, 19 | className, 20 | children, 21 | }: ButtonProps) => ( 22 | 36 | {children} 37 | 38 | ); 39 | 40 | export default Button; 41 | -------------------------------------------------------------------------------- /src/components/input/IconButton.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import type { FunctionComponent } from "react"; 3 | 4 | export interface IconButtonProps { 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | icon: FunctionComponent; 7 | label: string; 8 | href?: string; 9 | external?: boolean; 10 | onClick?: () => void; 11 | } 12 | 13 | const IconButton = (props: IconButtonProps) => { 14 | const { icon: Icon, label, href, onClick } = props; 15 | 16 | if (href) { 17 | return ( 18 | 26 | 27 | 28 | ); 29 | } 30 | 31 | return ( 32 | 39 | ); 40 | }; 41 | 42 | export default IconButton; 43 | -------------------------------------------------------------------------------- /src/components/input/SegmentedControlIem.tsx: -------------------------------------------------------------------------------- 1 | import type { HTMLAttributes, ReactElement } from "react"; 2 | 3 | const SegmentedControlItem = ( 4 | props: HTMLAttributes, 5 | ): ReactElement => ( 6 | 169 |
170 |
171 | 172 |
173 | )} 174 | 175 | ))} 176 | 177 | 178 | 179 | ); 180 | }; 181 | 182 | export default SoftwareDownloadButton; 183 | -------------------------------------------------------------------------------- /src/components/layout/DownloadsTree.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | 3 | import ArchiveIcon from "@/assets/icons/fontawesome/box-archive.svg"; 4 | import { useProject } from "@/lib/service/v2"; 5 | 6 | interface ProjectSubTreeProps { 7 | id: string; 8 | name: string; 9 | eol?: boolean; 10 | } 11 | 12 | const ProjectSubTree = ({ 13 | id, 14 | name, 15 | selectedProject, 16 | selectedVersion, 17 | onSelect, 18 | eol, 19 | }: ProjectSubTreeProps & DownloadsTreeProps) => { 20 | const { data: project } = useProject(id); 21 | 22 | return ( 23 | <> 24 |
25 | {project?.project_name ?? name}{" "} 26 | {eol && } 27 |
28 | {project?.versions 29 | ?.slice() 30 | ?.reverse() 31 | ?.map((version) => ( 32 | 49 | ))} 50 | 51 | ); 52 | }; 53 | 54 | interface DownloadsTreeProps { 55 | selectedProject: string; 56 | selectedVersion: string; 57 | 58 | onSelect(project: string, version: string): void; 59 | } 60 | 61 | const DownloadsTree = (props: DownloadsTreeProps) => { 62 | return ( 63 | 68 | ); 69 | }; 70 | 71 | export default DownloadsTree; 72 | -------------------------------------------------------------------------------- /src/components/layout/Footer.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import Link from "next/link"; 3 | 4 | import LogoMarkerDark from "@/assets/brand/logo-marker-dark.svg"; 5 | import classes from "@/styles/components/layout/Footer.module.css"; 6 | 7 | const Footer = () => ( 8 |
9 |
10 |
11 |
12 | Getting Started 13 |
    14 |
  • 15 | Downloads 16 |
  • 17 |
  • 18 | {/* eslint-disable-next-line react/jsx-no-target-blank */} 19 | 20 | Documentation 21 | 22 |
  • 23 |
  • 24 | Javadocs 25 |
  • 26 |
27 |
28 |
29 | Community 30 | 68 |
69 |
70 | PaperMC 71 |
    72 |
  • 73 | Our Team 74 |
  • 75 |
  • 76 | Contribute 77 |
  • 78 |
  • 79 | Sponsors 80 |
  • 81 |
  • 82 | {/* eslint-disable-next-line react/jsx-no-target-blank */} 83 | 84 | Hangar 85 | 86 |
  • 87 |
88 |
89 |
90 | Terms 91 |
    92 |
  • 93 | Terms 94 |
  • 95 |
  • 96 | 97 | Privacy Policy 98 | 99 |
  • 100 |
  • 101 | 102 | Legal Notice 103 | 104 |
  • 105 |
  • 106 | Hangar Terms 107 |
  • 108 |
  • 109 | 110 | Hangar Privacy Policy 111 | 112 |
  • 113 |
  • 114 | Community Guidelines 115 |
  • 116 |
117 |
118 |
119 |
120 | 121 |
122 | 123 | © {new Date().getFullYear()} The PaperMC Team 124 | 125 |
126 |
127 | 128 | This website is not an official Minecraft website and is not 129 | associated with Mojang Studios or Microsoft. All product and company 130 | names are trademarks or registered trademarks of their respective 131 | holders. Use of these names does not imply any affiliation or 132 | endorsement by them. 133 | 134 |
135 |
136 |
137 | ); 138 | 139 | export default Footer; 140 | -------------------------------------------------------------------------------- /src/components/layout/NavBar.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import type { NextComponentType, NextPageContext } from "next"; 3 | import Link from "next/link"; 4 | import { useRouter } from "next/router"; 5 | import { useEffect, useState } from "react"; 6 | 7 | import LogoMarkerDark from "@/assets/brand/logo-marker-dark.svg"; 8 | import LogoMarkerLight from "@/assets/brand/logo-marker-light.svg"; 9 | import DiscordIcon from "@/assets/icons/fontawesome/discord-brands.svg"; 10 | import GitHubIcon from "@/assets/icons/fontawesome/github-brands.svg"; 11 | import TwitterIcon from "@/assets/icons/fontawesome/twitter-brands.svg"; 12 | import ExternalUrlIcon from "@/assets/icons/heroicons/arrow-top-right-on-square.svg"; 13 | import MenuIcon from "@/assets/icons/heroicons/menu.svg"; 14 | import IconButton from "@/components/input/IconButton"; 15 | import NavDropDown from "@/components/layout/NavDropDown"; 16 | import NavDropDownLink from "@/components/layout/NavDropDownLink"; 17 | import NavLink from "@/components/layout/NavLink"; 18 | import type { PageSoftwareProps } from "@/lib/util/types"; 19 | 20 | export interface NavBarProps { 21 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 22 | component: NextComponentType; 23 | } 24 | 25 | const NavBar = ({ component }: NavBarProps) => { 26 | const [scroll, setScroll] = useState(false); 27 | const [showMenu, setShowMenu] = useState(false); 28 | const router = useRouter(); 29 | 30 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 31 | const softwareProps: PageSoftwareProps | undefined = (component as any)[ 32 | "softwareProps" 33 | ]; 34 | 35 | useEffect(() => { 36 | const handleScroll = () => { 37 | setScroll(window.scrollY > 64); 38 | }; 39 | 40 | window.addEventListener("scroll", handleScroll); 41 | 42 | return () => window.removeEventListener("scroll", handleScroll); 43 | }, [setScroll]); 44 | 45 | useEffect(() => { 46 | setShowMenu(false); 47 | }, [router.route]); 48 | 49 | return ( 50 | 139 | ); 140 | }; 141 | 142 | export default NavBar; 143 | -------------------------------------------------------------------------------- /src/components/layout/NavDropDown.tsx: -------------------------------------------------------------------------------- 1 | import { Transition } from "@headlessui/react"; 2 | import clsx from "clsx"; 3 | import type { ReactElement, ReactNode } from "react"; 4 | import { Fragment, useState } from "react"; 5 | 6 | import ChevronDownIcon from "@/assets/icons/heroicons/chevron-down.svg"; 7 | 8 | export interface NavDropDownProps { 9 | label: string; 10 | className?: string; 11 | children: ReactNode; 12 | } 13 | 14 | const NavDropDown = ({ 15 | label, 16 | className, 17 | children, 18 | }: NavDropDownProps): ReactElement => { 19 | const [hover, setHover] = useState(false); 20 | 21 | const handleEnter = () => { 22 | setHover(true); 23 | }; 24 | 25 | const handleLeave = () => { 26 | setHover(false); 27 | }; 28 | 29 | return ( 30 |
38 | 39 | {label} 40 | 41 | 42 | 43 | 54 |
    55 | {children} 56 |
57 |
58 |
59 | ); 60 | }; 61 | 62 | export default NavDropDown; 63 | -------------------------------------------------------------------------------- /src/components/layout/NavDropDownLink.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import Link from "next/link"; 3 | import type { ReactElement, ReactNode } from "react"; 4 | 5 | import ArchiveIcon from "@/assets/icons/fontawesome/box-archive.svg"; 6 | 7 | export interface NavDropDownLinkProps { 8 | href: string; 9 | target?: string; 10 | className?: string; 11 | children: ReactNode; 12 | eol?: boolean; 13 | } 14 | 15 | const NavDropDownLink = ({ 16 | href, 17 | target, 18 | className, 19 | children, 20 | eol, 21 | }: NavDropDownLinkProps): ReactElement => ( 22 |
  • 29 | 38 | {children} {eol && } 39 | 40 |
  • 41 | ); 42 | 43 | export default NavDropDownLink; 44 | -------------------------------------------------------------------------------- /src/components/layout/NavLink.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import Link from "next/link"; 3 | import type { ReactNode } from "react"; 4 | 5 | export interface LinkProps { 6 | href: string; 7 | target?: string; 8 | className?: string; 9 | children: ReactNode; 10 | } 11 | 12 | const NavLink = ({ href, target, className, children }: LinkProps) => ( 13 | 22 | {children} 23 | 24 | ); 25 | 26 | export default NavLink; 27 | -------------------------------------------------------------------------------- /src/components/layout/SoftwareDownload.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import Link from "next/link"; 3 | import type { FunctionComponent, ReactElement } from "react"; 4 | import { useState } from "react"; 5 | 6 | import SoftwareBuilds from "@/components/data/SoftwareBuilds"; 7 | import SoftwareDownloadButton from "@/components/input/SoftwareDownloadButton"; 8 | import type { ProjectProps } from "@/lib/context/downloads"; 9 | import { useVersionBuilds } from "@/lib/service/v2"; 10 | 11 | export interface SoftwareDownloadProps { 12 | id: string; 13 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 14 | icon?: FunctionComponent; 15 | description: ReactElement | string; 16 | experimentalWarning?: string; 17 | eol?: boolean; 18 | } 19 | 20 | const SoftwareDownload = ({ 21 | id, 22 | project, 23 | icon: Icon, 24 | description, 25 | experimentalWarning, 26 | eol, 27 | }: SoftwareDownloadProps & ProjectProps): ReactElement => { 28 | const [isStable, setStable] = useState(true); 29 | const version = isStable 30 | ? project.latestStableVersion 31 | : (project.latestExperimentalVersion ?? project.latestStableVersion); 32 | const { data: builds } = useVersionBuilds(id, version); 33 | const latestBuild = builds && builds.builds[builds.builds.length - 1]; 34 | 35 | const toggleStable = () => { 36 | setStable(!isStable); 37 | }; 38 | 39 | return ( 40 | <> 41 |
    42 | {eol && ( 43 |
    44 | {project.name} has reached end of life! It is no longer maintained 45 | or supported. 46 |
    47 | )} 48 |
    49 |
    50 |
    51 | {Icon && } 52 |
    53 |

    Downloads

    54 |
    55 |

    56 | Get {project.name}  57 | 60 | {version} 61 | 62 |

    63 |

    64 | {isStable ? description : (experimentalWarning ?? description)} 65 |

    66 |
    67 | 75 | {project.latestExperimentalVersion && ( 76 | 92 | )} 93 |
    94 |
    95 |
    96 |
    97 |
    98 |

    Older builds

    99 |

    100 | Looking for older builds - or changelogs? We got you!  101 |
    102 | 103 | Even older builds are available in our  104 | 108 | build explorer 109 | 110 | . 111 | 112 |

    113 | 119 |
    120 | 121 | ); 122 | }; 123 | 124 | export default SoftwareDownload; 125 | -------------------------------------------------------------------------------- /src/components/layout/SoftwareHeader.tsx: -------------------------------------------------------------------------------- 1 | import type { FunctionComponent, ReactElement } from "react"; 2 | 3 | import ArchiveIcon from "@/assets/icons/fontawesome/box-archive.svg"; 4 | import Button from "@/components/input/Button"; 5 | 6 | export interface SoftwareHeaderProps { 7 | id: string; 8 | name: string; 9 | versionGroup: string; 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | icon?: FunctionComponent; 12 | header: ReactElement; 13 | description: ReactElement | string; 14 | github?: string; 15 | eol?: boolean; 16 | } 17 | 18 | const SoftwareHeader = ({ 19 | id, 20 | name, 21 | versionGroup, 22 | icon: Icon, 23 | header, 24 | description, 25 | github, 26 | eol, 27 | }: SoftwareHeaderProps): ReactElement => ( 28 |
    29 | {eol && ( 30 |
    31 | {name} has reached end of life! It is no longer maintained or supported. 32 |
    33 | )} 34 |
    35 |
    36 |
    37 | {Icon && } 38 |
    39 |

    40 | {name} {eol && } 41 |

    42 |
    43 |

    44 | {header} 45 |

    46 |

    {description}

    47 |
    48 | 56 | 63 | 71 |
    72 |
    73 |
    74 |
    75 | ); 76 | 77 | export default SoftwareHeader; 78 | -------------------------------------------------------------------------------- /src/components/util/SEO.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import type { ReactElement } from "react"; 3 | 4 | export interface SEOProps { 5 | title: string; 6 | description: string; 7 | keywords: string[]; 8 | canonical: string; 9 | } 10 | 11 | const SEO = ({ 12 | title, 13 | description, 14 | keywords, 15 | canonical, 16 | }: SEOProps): ReactElement => { 17 | return ( 18 | 19 | {title + " | PaperMC"} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | ); 45 | }; 46 | 47 | export default SEO; 48 | -------------------------------------------------------------------------------- /src/lib/context/downloads.ts: -------------------------------------------------------------------------------- 1 | import type { GetStaticProps } from "next"; 2 | import { createContext } from "react"; 3 | 4 | import type { 5 | HangarProjectListPagination, 6 | HangarProjectList, 7 | } from "@/lib/service/hangar"; 8 | import { getHangarProjects } from "@/lib/service/hangar"; 9 | import type { Build } from "@/lib/service/types"; 10 | import { getProject, getVersionBuilds } from "@/lib/service/v2"; 11 | 12 | export interface DownloadsContextProps { 13 | projectId: string; 14 | project?: ProjectDescriptor; 15 | builds?: Build[]; 16 | version: string; 17 | stable: boolean; 18 | } 19 | 20 | export interface ProjectDescriptor { 21 | name: string; 22 | latestStableVersion: string; 23 | latestExperimentalVersion: string | null; 24 | latestVersionGroup: string; 25 | } 26 | 27 | export interface ProjectProps { 28 | project: ProjectDescriptor; 29 | } 30 | 31 | export interface HangarProjectProps extends ProjectProps { 32 | hangarProjectListPagination: HangarProjectListPagination; 33 | } 34 | 35 | export const DownloadsContext = createContext({ 36 | projectId: "paper", 37 | project: undefined, 38 | builds: undefined, 39 | version: "", 40 | stable: true, 41 | }); 42 | 43 | const isVersionStable = async ( 44 | project: string, 45 | version: string, 46 | ): Promise => { 47 | const { builds } = await getVersionBuilds(project, version); 48 | for (let i = builds.length - 1; i >= 0; i--) { 49 | if (builds[i].channel === "default") return true; 50 | } 51 | 52 | return false; 53 | }; 54 | 55 | export const getProjectProps = ( 56 | id: string, 57 | hangarProject: boolean = true, 58 | ): GetStaticProps => { 59 | return async () => { 60 | const { project_name, versions, version_groups } = await getProject(id); 61 | const hangarProjectList: HangarProjectList | null = hangarProject 62 | ? await getHangarProjects(id) 63 | : null; 64 | 65 | let latestStableVersion = versions[versions.length - 1]; 66 | for (let i = versions.length - 1; i >= 0; i--) { 67 | if (await isVersionStable(id, versions[i])) { 68 | latestStableVersion = versions[i]; 69 | break; 70 | } 71 | } 72 | 73 | const latestExperimentalVersion = 74 | latestStableVersion !== versions[versions.length - 1] 75 | ? versions[versions.length - 1] 76 | : null; 77 | 78 | const project: ProjectDescriptor = { 79 | name: project_name, 80 | latestStableVersion, 81 | latestExperimentalVersion, 82 | latestVersionGroup: version_groups[version_groups.length - 1], 83 | }; 84 | 85 | return { 86 | props: { 87 | project, 88 | hangarProjectListPagination: hangarProjectList 89 | ? hangarProjectList.pagination 90 | : null, 91 | }, 92 | revalidate: 600, // 10 minutes 93 | }; 94 | }; 95 | }; 96 | -------------------------------------------------------------------------------- /src/lib/service/api.ts: -------------------------------------------------------------------------------- 1 | export const swrNoAutoUpdateSettings = { 2 | revalidateOnFocus: false, 3 | revalidateOnMount: true, 4 | revalidateOnReconnect: false, 5 | refreshWhenOffline: false, 6 | refreshWhenHidden: false, 7 | refreshInterval: 0, 8 | initialSize: 100, 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/service/bstats.ts: -------------------------------------------------------------------------------- 1 | import useSWR from "swr"; 2 | 3 | import { swrNoAutoUpdateSettings } from "./api"; 4 | 5 | const CHARTS_URL = 6 | "https://bstats.org/api/v1/plugins/580/charts/players/data/?maxElements=1"; 7 | 8 | const fetcher = (url: string) => fetch(url).then((res) => res.json()); 9 | 10 | export const useBstatsPlayers = () => 11 | useSWR(CHARTS_URL, fetcher, swrNoAutoUpdateSettings); 12 | -------------------------------------------------------------------------------- /src/lib/service/github.ts: -------------------------------------------------------------------------------- 1 | import type { SWRInfiniteResponse } from "swr/infinite"; 2 | import useSWRInfinite from "swr/infinite"; 3 | 4 | import { swrNoAutoUpdateSettings } from "./api"; 5 | 6 | export interface Contributor { 7 | login: string; 8 | id: number; 9 | avatar_url: string; 10 | contributions: number; 11 | } 12 | 13 | const CONTRIBUTORS_BASE_URL = 14 | "https://api.github.com/repos/PaperMC/Paper/contributors?per_page=100"; 15 | 16 | const fetcher = (url: string) => fetch(url).then((res) => res.json()); 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 18 | const getURL = (pageIndex: number, previousPageData: any): string | null => { 19 | if (previousPageData && previousPageData.length < 100) return null; 20 | return `${CONTRIBUTORS_BASE_URL}&page=${pageIndex + 1}`; 21 | }; 22 | 23 | export const useGitHubContributors = (): SWRInfiniteResponse => 24 | useSWRInfinite(getURL, fetcher, swrNoAutoUpdateSettings); 25 | 26 | export const getProjectRepository = ( 27 | project: string, 28 | version: string, 29 | ): string => { 30 | if (project !== "paper") return `https://github.com/PaperMC/${project}`; 31 | 32 | const baseVersion = [21, 4]; // 1.21.4 is after the hardfork 33 | const isBelowBaseVersion = version 34 | .replace(/^1\./, "") 35 | .split(".") 36 | .map(Number) 37 | .some((v, i) => v < (baseVersion[i] || 0)); 38 | 39 | return isBelowBaseVersion 40 | ? "https://github.com/PaperMC/Paper-Archive" 41 | : "https://github.com/PaperMC/Paper"; 42 | }; 43 | -------------------------------------------------------------------------------- /src/lib/service/hangar.ts: -------------------------------------------------------------------------------- 1 | export interface HangarProjectList { 2 | pagination: HangarProjectListPagination; 3 | } 4 | 5 | export interface HangarProjectListPagination { 6 | limit: number; 7 | offset: number; 8 | count: number; 9 | } 10 | 11 | export const getHangarProjects = ( 12 | platform: string, 13 | ): Promise => 14 | fetch( 15 | `https://hangar.papermc.io/api/v1/projects?limit=1&offset=0&sort=-stars&platform=${platform.toUpperCase()}`, 16 | ).then((res) => res.json()); 17 | -------------------------------------------------------------------------------- /src/lib/service/sponsors.ts: -------------------------------------------------------------------------------- 1 | import type { SWRResponse } from "swr"; 2 | import useSWR from "swr"; 3 | 4 | import { swrNoAutoUpdateSettings } from "@/lib/service/api"; 5 | 6 | export interface SponsorData { 7 | ocData: OpenCollectiveData; 8 | ghData: GitHubSponsorsData; 9 | } 10 | 11 | export interface OpenCollectiveContributor { 12 | name: string; 13 | image: string; 14 | totalAmountDonated: number; 15 | } 16 | 17 | export interface OpenCollectiveData { 18 | collective: { 19 | name: string; 20 | slug: string; 21 | stats: { 22 | balance: { 23 | valueInCents: number; 24 | }; 25 | monthlySpending: { 26 | valueInCents: number; 27 | }; 28 | }; 29 | contributors: { 30 | totalCount: number; 31 | nodes: OpenCollectiveContributor[]; 32 | }; 33 | }; 34 | } 35 | 36 | export interface GithubSponsorNode { 37 | login: string; 38 | avatarUrl: string; 39 | } 40 | 41 | export interface GitHubSponsorsData { 42 | organization: { 43 | sponsors: { 44 | totalCount: number; 45 | nodes: GithubSponsorNode[]; 46 | }; 47 | }; 48 | } 49 | 50 | const fetcher = (url: string) => fetch(url).then((res) => res.json()); 51 | 52 | export const useSponsors = (): SWRResponse => 53 | useSWR( 54 | "https://raw.githubusercontent.com/PaperMC/papermc.io/data/sponsors.json", 55 | fetcher, 56 | swrNoAutoUpdateSettings, 57 | ); 58 | -------------------------------------------------------------------------------- /src/lib/service/types.ts: -------------------------------------------------------------------------------- 1 | export interface Project { 2 | project_id: string; 3 | project_name: string; 4 | version_groups: string[]; 5 | versions: string[]; 6 | } 7 | 8 | export interface ProjectVersion { 9 | project_id: string; 10 | project_name: string; 11 | version: string; 12 | builds: number[]; 13 | } 14 | 15 | export interface VersionBuilds { 16 | project_id: string; 17 | project_name: string; 18 | version: string; 19 | builds: Build[]; 20 | } 21 | 22 | export interface VersionFamilyBuilds { 23 | project_id: string; 24 | project_name: string; 25 | version_group: string; 26 | versions: string[]; 27 | builds: VersionFamilyBuild[]; 28 | } 29 | 30 | export interface VersionFamilyBuild extends Build { 31 | project_id: string; 32 | project_name: string; 33 | version: string; 34 | } 35 | 36 | export interface Build { 37 | build: number; 38 | time: string; 39 | channel: "default" | "experimental"; 40 | promoted: boolean; 41 | changes: BuildChange[]; 42 | downloads: Record; 43 | } 44 | 45 | export interface BuildChange { 46 | commit: string; 47 | summary: string; 48 | message: string; 49 | } 50 | 51 | export interface BuildDownload { 52 | name: string; 53 | sha256: string; 54 | } 55 | 56 | export interface ProjectsResponse { 57 | projects: string[]; 58 | } 59 | -------------------------------------------------------------------------------- /src/lib/service/v2.ts: -------------------------------------------------------------------------------- 1 | import type { SWRResponse } from "swr"; 2 | import useSWR from "swr"; 3 | 4 | import { swrNoAutoUpdateSettings } from "./api"; 5 | 6 | import type { 7 | Project, 8 | ProjectsResponse, 9 | VersionBuilds, 10 | VersionFamilyBuilds, 11 | } from "@/lib/service/types"; 12 | 13 | const API_ENDPOINT = "https://api.papermc.io/v2"; 14 | 15 | const fetcher = (path: string) => 16 | fetch(API_ENDPOINT + path).then((res) => res.json()); 17 | 18 | export const useProjects = (): SWRResponse => 19 | useSWR("/projects", fetcher, swrNoAutoUpdateSettings); 20 | 21 | export const useProject = (project: string): SWRResponse => 22 | useSWR(`/projects/${project}`, fetcher, swrNoAutoUpdateSettings); 23 | 24 | export const useVersionBuilds = ( 25 | project: string, 26 | version: string, 27 | ): SWRResponse => 28 | useSWR( 29 | `/projects/${project}/versions/${version}/builds`, 30 | fetcher, 31 | swrNoAutoUpdateSettings, 32 | ); 33 | 34 | export const useVersionFamilyBuilds = ( 35 | project: string, 36 | family: string, 37 | ): SWRResponse => 38 | useSWR( 39 | `/projects/${project}/version_group/${family}/builds`, 40 | fetcher, 41 | swrNoAutoUpdateSettings, 42 | ); 43 | 44 | // TODO: Better error handling? 45 | const getJSON = (path: string): Promise => fetcher(path); 46 | 47 | export const getProject = (project: string): Promise => 48 | getJSON(`/projects/${project}`); 49 | 50 | export const getVersionBuilds = ( 51 | project: string, 52 | version: string, 53 | ): Promise => 54 | getJSON(`/projects/${project}/versions/${version}/builds`); 55 | 56 | export const getVersionBuildDownloadURL = ( 57 | project: string, 58 | version: string, 59 | build: number, 60 | file: string, 61 | ): string => 62 | `${API_ENDPOINT}/projects/${project}/versions/${version}/builds/${build}/downloads/${file}`; 63 | -------------------------------------------------------------------------------- /src/lib/util/time.ts: -------------------------------------------------------------------------------- 1 | // TODO: Use INTL instead? 2 | export const formatRelativeDate = (date: Date): string => { 3 | const now = Date.now(); 4 | const secondsPast = (now - date.getTime()) / 1000; 5 | 6 | if (secondsPast < 60) return formatRelativeUnit(secondsPast, "second"); 7 | if (secondsPast < 3600) return formatRelativeUnit(secondsPast / 60, "minute"); 8 | if (secondsPast < 86400) 9 | return formatRelativeUnit(secondsPast / 3600, "hour"); 10 | 11 | const days = secondsPast / 86400; 12 | if (days > 7) return formatISODate(date); 13 | 14 | return formatRelativeUnit(days, "day"); 15 | }; 16 | 17 | const formatRelativeUnit = (count: number, unit: string): string => { 18 | count = Math.round(count); 19 | if (count !== 1) { 20 | return `${count} ${unit}s ago`; 21 | } 22 | 23 | return `${count} ${unit} ago`; 24 | }; 25 | 26 | export const formatISODate = (date: Date): string => { 27 | const month = String(date.getMonth() + 1).padStart(2, "0"); 28 | const day = String(date.getDate()).padStart(2, "0"); 29 | return `${date.getFullYear()}-${month}-${day}`; 30 | }; 31 | 32 | export const formatISODateTime = (date: Date): string => { 33 | const month = String(date.getMonth() + 1).padStart(2, "0"); 34 | const day = String(date.getDate()).padStart(2, "0"); 35 | const hour = String(date.getHours()).padStart(2, "0"); 36 | const minute = String(date.getMinutes()).padStart(2, "0"); 37 | return `${date.getFullYear()}-${month}-${day} ${hour}:${minute}`; 38 | }; 39 | 40 | export const formatISOFullTime = (date: Date): string => { 41 | const hour = String(date.getHours()).padStart(2, "0"); 42 | const minute = String(date.getMinutes()).padStart(2, "0"); 43 | const second = String(date.getSeconds()).padStart(2, "0"); 44 | return `${hour}:${minute}:${second}`; 45 | }; 46 | -------------------------------------------------------------------------------- /src/lib/util/types.ts: -------------------------------------------------------------------------------- 1 | export interface PageSoftwareProps { 2 | github?: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "@fontsource/poppins/400.css"; 2 | import "@fontsource/poppins/500.css"; 3 | import "@fontsource/poppins/600.css"; 4 | import "@fontsource/poppins/700.css"; 5 | 6 | import "windi.css"; 7 | import "@/styles/globals.css"; 8 | 9 | import type { AppProps } from "next/app"; 10 | 11 | import Footer from "@/components/layout/Footer"; 12 | import NavBar from "@/components/layout/NavBar"; 13 | 14 | const MyApp = ({ Component, pageProps, router }: AppProps) => ( 15 | <> 16 | 17 |
    18 | 19 |
    20 | {router.route !== "/downloads/all" &&