├── .devcontainer └── devcontainer.json ├── .gitignore ├── .nvmrc ├── LICENSE ├── LICENSE-ai-robots-txt ├── README.md ├── TODOS.md ├── algs ├── CP.ts ├── EPLL.ts ├── OCLL.ts ├── PLL.ts └── types.ts ├── components ├── AlgSheet.module.css ├── AlgSheet.tsx ├── Blog.tsx ├── CubeMp4Embed.module.css ├── CubeMp4Embed.tsx ├── PetGallery.module.css ├── PetGallery.tsx ├── ReconCollection │ ├── ReconCollection.module.css │ ├── ReconCollection.tsx │ └── index.ts ├── ReconViewer.module.css ├── ReconViewer.tsx ├── TwistyPlayer.tsx ├── YouTubeEmbed.module.css └── YouTubeEmbed.tsx ├── lib ├── index.ts └── simplifyRotations.ts ├── middleware.ts ├── next-env.d.ts ├── next.config.ts ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── en │ ├── _meta.tsx │ ├── about.mdx │ ├── blog.mdx │ ├── blog │ │ ├── _meta.tsx │ │ ├── story.mdx │ │ └── zz-example-solve-library.mdx │ ├── example-solves.mdx │ ├── example-solves │ │ ├── _meta.tsx │ │ ├── gen-11.68-oh-ao5.mdx │ │ ├── gen-8.99-ao5.mdx │ │ ├── jouda-11.26-oh-ao5.mdx │ │ ├── luna-9.00-ao25.mdx │ │ ├── rouxzzcfop-7.64-ao12.mdx │ │ ├── yoruba-5.86-ao5.mdx │ │ ├── yoruba-6.65-ao25.mdx │ │ ├── yoruba-7.19-ao50.mdx │ │ └── yoruba-advanced-example-25.mdx │ ├── improvement-guide.mdx │ ├── improvement-guide │ │ ├── _meta.tsx │ │ ├── eocross.mdx │ │ ├── eocross │ │ │ ├── _meta.tsx │ │ │ ├── color-neutrality.mdx │ │ │ ├── efficiency.mdx │ │ │ ├── eocross-full-guide.mdx │ │ │ ├── inspection.mdx │ │ │ ├── practicing.mdx │ │ │ └── xeocross.mdx │ │ ├── ll.mdx │ │ ├── ll │ │ │ ├── 2lll.mdx │ │ │ ├── _meta.tsx │ │ │ ├── zbll-algs.mdx │ │ │ ├── zbll-guide.mdx │ │ │ ├── zbll-practice.mdx │ │ │ └── zbll-recog.mdx │ │ ├── zzf2l.mdx │ │ └── zzf2l │ │ │ ├── _meta.tsx │ │ │ ├── deduction.mdx │ │ │ ├── lookahead.mdx │ │ │ ├── multislotting.mdx │ │ │ ├── pair-choice.mdx │ │ │ ├── pair-solutions.mdx │ │ │ └── zzf2l-full-guide.mdx │ ├── index.mdx │ ├── tutorial.mdx │ └── tutorial │ │ ├── _meta.tsx │ │ ├── eo.mdx │ │ ├── eocross.mdx │ │ ├── ll.mdx │ │ ├── ll.module.css │ │ └── zzf2l.mdx ├── fr │ ├── _meta.tsx │ ├── about.mdx │ ├── blog.mdx │ ├── blog │ │ ├── _meta.tsx │ │ ├── story.mdx │ │ └── zz-example-solve-library.mdx │ ├── example-solves.mdx │ ├── example-solves │ │ ├── _meta.tsx │ │ ├── gen-11.68-oh-ao5.mdx │ │ ├── gen-8.99-ao5.mdx │ │ ├── jouda-11.26-oh-ao5.mdx │ │ ├── luna-9.00-ao25.mdx │ │ ├── rouxzzcfop-7.64-ao12.mdx │ │ ├── yoruba-5.86-ao5.mdx │ │ ├── yoruba-6.65-ao25.mdx │ │ ├── yoruba-7.19-ao50.mdx │ │ └── yoruba-advanced-example-25.mdx │ ├── improvement-guide.mdx │ ├── improvement-guide │ │ ├── _meta.tsx │ │ ├── eocross.mdx │ │ ├── eocross │ │ │ ├── _meta.tsx │ │ │ ├── color-neutrality.mdx │ │ │ ├── efficiency.mdx │ │ │ ├── eocross-full-guide.mdx │ │ │ ├── inspection.mdx │ │ │ ├── practicing.mdx │ │ │ └── xeocross.mdx │ │ ├── ll.mdx │ │ ├── ll │ │ │ ├── 2lll.mdx │ │ │ ├── _meta.tsx │ │ │ ├── zbll-algs.mdx │ │ │ ├── zbll-guide.mdx │ │ │ ├── zbll-practice.mdx │ │ │ └── zbll-recog.mdx │ │ ├── zzf2l.mdx │ │ └── zzf2l │ │ │ ├── _meta.tsx │ │ │ ├── deduction.mdx │ │ │ ├── lookahead.mdx │ │ │ ├── multislotting.mdx │ │ │ ├── pair-choice.mdx │ │ │ ├── pair-solutions.mdx │ │ │ └── zzf2l-full-guide.mdx │ ├── index.mdx │ ├── tutorial.mdx │ └── tutorial │ │ ├── _meta.tsx │ │ ├── eo.mdx │ │ ├── eocross.mdx │ │ ├── ll.mdx │ │ ├── ll.module.css │ │ └── zzf2l.mdx ├── he │ ├── _meta.tsx │ ├── about.mdx │ ├── blog.mdx │ ├── blog │ │ ├── _meta.tsx │ │ ├── story.mdx │ │ └── zz-example-solve-library.mdx │ ├── example-solves.mdx │ ├── example-solves │ │ ├── _meta.tsx │ │ ├── gen-11.68-oh-ao5.mdx │ │ ├── gen-8.99-ao5.mdx │ │ ├── jouda-11.26-oh-ao5.mdx │ │ ├── luna-9.00-ao25.mdx │ │ ├── rouxzzcfop-7.64-ao12.mdx │ │ ├── yoruba-5.86-ao5.mdx │ │ ├── yoruba-6.65-ao25.mdx │ │ ├── yoruba-7.19-ao50.mdx │ │ └── yoruba-advanced-example-25.mdx │ ├── improvement-guide.mdx │ ├── improvement-guide │ │ ├── _meta.tsx │ │ ├── eocross.mdx │ │ ├── eocross │ │ │ ├── _meta.tsx │ │ │ ├── color-neutrality.mdx │ │ │ ├── efficiency.mdx │ │ │ ├── eocross-full-guide.mdx │ │ │ ├── inspection.mdx │ │ │ ├── practicing.mdx │ │ │ └── xeocross.mdx │ │ ├── ll.mdx │ │ ├── ll │ │ │ ├── 2lll.mdx │ │ │ ├── _meta.tsx │ │ │ ├── zbll-algs.mdx │ │ │ ├── zbll-guide.mdx │ │ │ ├── zbll-practice.mdx │ │ │ └── zbll-recog.mdx │ │ ├── zzf2l.mdx │ │ └── zzf2l │ │ │ ├── _meta.tsx │ │ │ ├── deduction.mdx │ │ │ ├── lookahead.mdx │ │ │ ├── multislotting.mdx │ │ │ ├── pair-choice.mdx │ │ │ ├── pair-solutions.mdx │ │ │ └── zzf2l-full-guide.mdx │ ├── index.mdx │ ├── tutorial.mdx │ └── tutorial │ │ ├── _meta.tsx │ │ ├── eo.mdx │ │ ├── eocross.mdx │ │ ├── ll.mdx │ │ ├── ll.module.css │ │ └── zzf2l.mdx ├── styles.css └── zh │ ├── _meta.tsx │ ├── about.mdx │ ├── blog.mdx │ ├── blog │ ├── _meta.tsx │ ├── story.mdx │ └── zz-example-solve-library.mdx │ ├── example-solves.mdx │ ├── example-solves │ ├── _meta.tsx │ ├── gen-11.68-oh-ao5.mdx │ ├── gen-8.99-ao5.mdx │ ├── jouda-11.26-oh-ao5.mdx │ ├── luna-9.00-ao25.mdx │ ├── rouxzzcfop-7.64-ao12.mdx │ ├── yoruba-5.86-ao5.mdx │ ├── yoruba-6.65-ao25.mdx │ ├── yoruba-7.19-ao50.mdx │ └── yoruba-advanced-example-25.mdx │ ├── improvement-guide.mdx │ ├── improvement-guide │ ├── _meta.tsx │ ├── eocross.mdx │ ├── eocross │ │ ├── _meta.tsx │ │ ├── color-neutrality.mdx │ │ ├── efficiency.mdx │ │ ├── eocross-full-guide.mdx │ │ ├── inspection.mdx │ │ ├── practicing.mdx │ │ └── xeocross.mdx │ ├── ll.mdx │ ├── ll │ │ ├── 2lll.mdx │ │ ├── _meta.tsx │ │ ├── zbll-algs.mdx │ │ ├── zbll-guide.mdx │ │ ├── zbll-practice.mdx │ │ └── zbll-recog.mdx │ ├── zzf2l.mdx │ └── zzf2l │ │ ├── _meta.tsx │ │ ├── deduction.mdx │ │ ├── lookahead.mdx │ │ ├── multislotting.mdx │ │ ├── pair-choice.mdx │ │ ├── pair-solutions.mdx │ │ └── zzf2l-full-guide.mdx │ ├── index.mdx │ ├── tutorial.mdx │ └── tutorial │ ├── _meta.tsx │ ├── eo.mdx │ ├── eocross.mdx │ ├── ll.mdx │ ├── ll.module.css │ └── zzf2l.mdx ├── public ├── assets │ ├── FR-edge.png │ ├── deduction-easy-hint.png │ ├── deduction-easy.png │ ├── deduction-example-1.png │ ├── deduction-example-2.png │ ├── deduction-hard-hint.png │ ├── deduction-hard.png │ ├── deduction-medium-hint.png │ ├── deduction-medium.png │ ├── eo-example-1-1.png │ ├── eo-example-1-2.png │ ├── eo-example-1-3.png │ ├── eo-example-1-4.png │ ├── eo-example-1-5-1.png │ ├── eo-example-1-5-2.mp4 │ ├── eo-example-1-5-2.png │ ├── eo-example-1-5-3.mp4 │ ├── eo-example-1-5-3.png │ ├── eo-example-1-5-4.mp4 │ ├── eo-example-1-5-4.png │ ├── minus-2.mp4 │ ├── minus-4-after.png │ ├── minus-4.mp4 │ ├── minus-4.png │ ├── orbits.png │ ├── pets │ │ ├── artemis.jpg │ │ ├── cosmo.jpg │ │ ├── eva.jpg │ │ ├── finn.jpg │ │ ├── fufreda.jpg │ │ ├── harley.jpg │ │ ├── io.jpg │ │ ├── sir_harold_iii.jpg │ │ └── spumoni.jpg │ ├── plus-2-after.png │ ├── plus-2.mp4 │ └── plus-2.png ├── favicon.png ├── favicon.svg ├── logo-eocross.svg ├── logo.svg └── robots.txt ├── reconstructions ├── en │ ├── gen-11.68-oh-ao5.ts │ ├── gen-8.99-ao5.ts │ ├── jouda-11.26-oh-ao5.ts │ ├── luna-9.00-ao25.ts │ ├── rouxzzcfop-7.64-ao12.ts │ ├── yoruba-5.86-ao5.ts │ ├── yoruba-6.65-ao25.ts │ ├── yoruba-7.19-ao50.ts │ └── yoruba-advanced-example-25.ts ├── fr │ ├── gen-11.68-oh-ao5.ts │ ├── gen-8.99-ao5.ts │ ├── jouda-11.26-oh-ao5.ts │ ├── luna-9.00-ao25.ts │ ├── rouxzzcfop-7.64-ao12.ts │ ├── yoruba-5.86-ao5.ts │ ├── yoruba-6.65-ao25.ts │ ├── yoruba-7.19-ao50.ts │ └── yoruba-advanced-example-25.ts ├── he │ ├── gen-11.68-oh-ao5.ts │ ├── gen-8.99-ao5.ts │ ├── jouda-11.26-oh-ao5.ts │ ├── luna-9.00-ao25.ts │ ├── rouxzzcfop-7.64-ao12.ts │ ├── yoruba-5.86-ao5.ts │ ├── yoruba-6.65-ao25.ts │ ├── yoruba-7.19-ao50.ts │ └── yoruba-advanced-example-25.ts ├── types.ts └── zh │ ├── gen-11.68-oh-ao5.ts │ ├── gen-8.99-ao5.ts │ ├── jouda-11.26-oh-ao5.ts │ ├── luna-9.00-ao25.ts │ ├── rouxzzcfop-7.64-ao12.ts │ ├── yoruba-5.86-ao5.ts │ ├── yoruba-6.65-ao25.ts │ ├── yoruba-7.19-ao50.ts │ └── yoruba-advanced-example-25.ts ├── theme.config.tsx ├── translations ├── ReconCollection.tsx ├── ReconViewer.tsx ├── site.tsx ├── types.ts └── useTranslation.ts ├── tsconfig.json ├── types.ts └── vercel.json /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image":"mcr.microsoft.com/devcontainers/universal:2", 3 | "customizations": { 4 | "vscode": { 5 | "settings": { 6 | "[typescriptreact]": { 7 | "editor.defaultFormatter": "esbenp.prettier-vscode" 8 | }, 9 | "[typescript]": { 10 | "editor.defaultFormatter": "esbenp.prettier-vscode" 11 | }, 12 | "[javascript]": { 13 | "editor.defaultFormatter": "esbenp.prettier-vscode" 14 | }, 15 | "editor.formatOnSave": true, 16 | "mdx.experimentalLanguageServer": true 17 | }, 18 | "extensions": ["esbenp.prettier-vscode", "unifiedjs.vscode-mdx"] 19 | } 20 | }, 21 | "updateContentCommand": "npm install", 22 | "postAttachCommand": "npm run dev" 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | 4 | # dev environment 5 | .vscode 6 | .DS_Store 7 | 8 | # produced by tsc 9 | tsconfig.tsbuildinfo 10 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v22.5.1 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 crystalcuber 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 | -------------------------------------------------------------------------------- /LICENSE-ai-robots-txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ai.robots.txt 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 | # ZZ Method Docs 2 | 3 | Learn the ZZ Method for solving the Rubik's Cube! 4 | 5 | **Check it out →** [**zzmethod.com**](https://zzmethod.com) 6 | 7 | ## Local Development 8 | 9 | Run `npm i` to install the dependencies. 10 | 11 | Then, run `npm run dev` to start the development server and visit [localhost:2006](http://localhost:2006). 12 | 13 | ## License 14 | 15 | This project is licensed under the [MIT License](./LICENSE). 16 | 17 | ## Contributing 18 | 19 | - If you have any suggestions, feel free to open an issue. 20 | - If you have fixes/corrections, you can create a Pull Request right away. 21 | - If you would like to make larger contributions, we suggest opening an issue first. 22 | 23 | ## Acknowledgements 24 | 25 | ### ZZMethod.com team 26 | 27 | - [crystalcuber](https://www.youtube.com/@crystalcuber) 28 | - [yoruba](https://www.youtube.com/@yoruba7807) 29 | - [S1neWav\_](https://www.youtube.com/@S1neWav_) 30 | 31 | ### Feedback 32 | 33 | - [Gen The Snail](https://www.youtube.com/@GenTheSnail) 34 | - [Fahmi Aulia Rachman](https://www.worldcubeassociation.org/persons/2016RACH01) 35 | 36 | ### Technologies 37 | 38 | - [`cubing.js`](https://github.com/cubing/cubing.js) for cube visuals 39 | - [`nextra`](https://github.com/shuding/nextra): documentation site framework 40 | 41 | ## Note on Generative AI 42 | This project uses [ai.robots.txt](https://github.com/ai-robots-txt/ai.robots.txt) to block AI web crawlers. 43 | The contents of this site shall not be used to train or develop generative AI models. 44 | -------------------------------------------------------------------------------- /TODOS.md: -------------------------------------------------------------------------------- 1 | - add cube orientation translation support 2 | 3 | ```ts 4 | export type Color = "white" | "yellow" | "blue" | "green" | "orange" | "red"; 5 | export type CubeOrientation = { top: Color; front: Color }; 6 | type Rotation = "x" | "x2" | "x'" | "y" | "y2" | "y'" | "z" | "z2" | "z'"; 7 | type Side = "front" | "top" | "left" | "right" | "bottom" | "back"; 8 | type Cube = { [s in Side]: Color }; 9 | 10 | const rotations: { [rotation in Rotation]: { [side in Side]?: Side } } = { 11 | x: { top: "front", front: "bottom", bottom: "back", back: "top" }, 12 | x2: { top: "bottom", front: "back", bottom: "top", back: "front" }, 13 | "x'": { top: "back", front: "top", bottom: "front", back: "bottom" }, 14 | y: { front: "right", right: "back", back: "left", left: "front" }, 15 | y2: { front: "back", right: "left", back: "front", left: "right" }, 16 | "y'": { front: "left", right: "front", back: "right", left: "back" }, 17 | z: { top: "left", left: "bottom", bottom: "right", right: "top" }, 18 | z2: { top: "bottom", left: "right", bottom: "top", right: "left" }, 19 | "z'": { top: "right", left: "top", bottom: "left", right: "bottom" }, 20 | }; 21 | 22 | const solvedCube: Cube = { 23 | top: "white", 24 | front: "green", 25 | left: "orange", 26 | right: "red", 27 | bottom: "yellow", 28 | back: "blue", 29 | }; 30 | 31 | function rotateCube(cube: Cube, rotation: Rotation): Cube { 32 | const transform = rotations[rotation]; 33 | const newCube: Cube = { ...cube }; 34 | Object.entries(transform).forEach(([src, dst]: [Side, Side]) => { 35 | newCube[src] = cube[dst]; 36 | }); 37 | return newCube; 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /algs/CP.ts: -------------------------------------------------------------------------------- 1 | import { AlgSet } from "./types"; 2 | 3 | const CP: AlgSet = { 4 | name: "CP", 5 | customStickering: "EDGES:OOOO--------,CORNERS:--------,CENTERS:------", 6 | visualization: "2D", 7 | algCases: [ 8 | { 9 | name: "Adjacent swap", 10 | algs: ["R U R' U' R' F R2 U' R' U' R U R' F'"], 11 | }, 12 | { 13 | name: "Diagonal swap", 14 | algs: ["F R U' R' U' R U R' F' R U R' U' R' F R F'"], 15 | }, 16 | ], 17 | }; 18 | 19 | export default CP; 20 | -------------------------------------------------------------------------------- /algs/EPLL.ts: -------------------------------------------------------------------------------- 1 | import { AlgSet } from "./types"; 2 | 3 | const EPLL: AlgSet = { 4 | name: "EPLL", 5 | stickering: "full", // I think full stickering looks better than PLL/EPLL here 6 | visualization: "2D", 7 | algCases: [ 8 | { 9 | name: "H Perm", 10 | algs: ["M2' U M2' U2 M2' U M2'"], 11 | }, 12 | { 13 | name: "Ua Perm", 14 | algs: ["M2' U M U2 M' U M2'"], 15 | }, 16 | { 17 | name: "Ub Perm", 18 | algs: ["M2' U' M U2 M' U' M2'"], 19 | }, 20 | { 21 | name: "Z Perm", 22 | algs: ["M2' U2 M U M2' U M2' U M"], 23 | }, 24 | ], 25 | }; 26 | 27 | export default EPLL; 28 | -------------------------------------------------------------------------------- /algs/OCLL.ts: -------------------------------------------------------------------------------- 1 | import { AlgSet } from "./types"; 2 | 3 | const OCLL: AlgSet = { 4 | name: "OCLL", 5 | stickering: "OLL", // I think OLL stickering looks better than OCLL here 6 | visualization: "2D", 7 | algCases: [ 8 | { 9 | name: "Sune", 10 | algs: ["R U R' U R U2' R'"], 11 | }, 12 | { 13 | name: "Antisune", 14 | algs: ["L' U' L U' L' U2 L"], 15 | }, 16 | { 17 | name: "U", 18 | algs: ["R2' D' R U2 R' D R U2 R"], 19 | }, 20 | { 21 | name: "T", 22 | algs: ["r U R' U' r' F R F'"], 23 | }, 24 | { 25 | name: "L", 26 | algs: ["F R' F' r U R U' r'"], 27 | }, 28 | { 29 | name: "H", 30 | algs: ["R U R' U R U' R' U R U2' R'"], 31 | }, 32 | { 33 | name: "Pi", 34 | algs: ["R U2' R2' U' R2 U' R2' U2' R"], 35 | }, 36 | ], 37 | }; 38 | 39 | export default OCLL; 40 | -------------------------------------------------------------------------------- /algs/types.ts: -------------------------------------------------------------------------------- 1 | export interface AlgSet { 2 | name: string; 3 | stickering?: string; // this should be supported by cubing.js `experimental-stickering` attribute 4 | customStickering?: string; // this is for cubing.js `experimental-stickering-mask-orbits` 5 | visualization: "2D" | "3D"; 6 | algCases: AlgCase[]; 7 | hasCategories?: boolean; 8 | } 9 | 10 | type NonEmptyArray = [T, ...T[]]; 11 | 12 | export interface AlgCase { 13 | name: string; 14 | algs: NonEmptyArray; 15 | category?: string; 16 | } 17 | -------------------------------------------------------------------------------- /components/AlgSheet.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | display: flex; 4 | justify-content: center; 5 | } 6 | 7 | .table { 8 | border-radius: 5px; 9 | width: 100%; 10 | } 11 | 12 | .table th, 13 | .table td { 14 | padding: 10px; 15 | border: 2px solid; 16 | border-color: rgb(229, 229, 229); 17 | } 18 | 19 | :is(html[class~="dark"]) .table th, 20 | :is(html[class~="dark"]) .table td { 21 | border-color: rgb(60, 60, 60); 22 | } 23 | 24 | .nameHeader { 25 | display: none; 26 | } 27 | 28 | .name { 29 | text-align: center; 30 | display: none; 31 | } 32 | 33 | .case { 34 | text-align: center; 35 | width: 0; 36 | } 37 | 38 | .caseContainer { 39 | display: flex; 40 | justify-content: center; 41 | flex-direction: column; 42 | } 43 | 44 | .algImage { 45 | width: 100px; 46 | height: 100px; 47 | } 48 | 49 | .algListContainer { 50 | display: flex; 51 | justify-content: center; 52 | } 53 | 54 | .algList { 55 | list-style-type: disc; 56 | } 57 | 58 | .categoryHeader { 59 | display: none; 60 | } 61 | 62 | .category { 63 | text-align: center; 64 | display: none; 65 | } 66 | 67 | /* responsive styles */ 68 | @media only screen and (min-width: 1024px) { 69 | .nameHeader { 70 | display: table-cell; 71 | } 72 | .name { 73 | display: table-cell; 74 | } 75 | .mobileName { 76 | display: none; 77 | } 78 | .mobileCategory { 79 | display: none; 80 | } 81 | .categoryHeader { 82 | display: table-cell; 83 | } 84 | 85 | .category { 86 | display: table-cell; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /components/AlgSheet.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { AlgCase, AlgSet } from "algs/types"; 4 | import TwistyPlayer from "components/TwistyPlayer"; 5 | import React from "react"; 6 | import styles from "./AlgSheet.module.css"; 7 | 8 | export interface AlgSheetProps { 9 | algSet: AlgSet; 10 | } 11 | 12 | export default function AlgSheet({ algSet }: AlgSheetProps) { 13 | const row = (algCase: AlgCase) => ( 14 | 15 | {algCase.name} 16 | 17 |
18 | {algCase.name} 19 | 34 |

{algCase.category}

35 |
36 | 37 | 38 |
39 | {algCase.algs.length === 1 ? ( 40 |

{algCase.algs[0]}

41 | ) : ( 42 |
    43 | {algCase.algs.map((alg) => ( 44 |
  • {alg}
  • 45 | ))} 46 |
47 | )} 48 |
49 | 50 | {algSet.hasCategories && ( 51 | {algCase.category} 52 | )} 53 | 54 | ); 55 | return ( 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | {algSet.hasCategories && ( 64 | 65 | )} 66 | 67 | 68 | {algSet.algCases.map(row)} 69 |
NameCaseAlgsCategory
70 |
71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /components/Blog.tsx: -------------------------------------------------------------------------------- 1 | import { getPagesUnderRoute } from "nextra/context"; 2 | // import filterRouteLocale from "nextra/filter-route-locale"; 3 | import Link from "next/link"; 4 | import { useRouter } from "nextra/hooks"; 5 | 6 | export default function BlogIndex() { 7 | const { locale } = useRouter(); 8 | return getPagesUnderRoute(`/${locale}/blog`).map((page: any) => { 9 | return ( 10 |
11 |

12 | 17 | {page.meta?.title || page.frontMatter?.title || page.name} 18 | 19 |

20 | {page.frontMatter?.date ? ( 21 |

{page.frontMatter.date}

22 | ) : null} 23 |

24 | {page.frontMatter?.description}{" "} 25 | 26 | 30 | {"Read →"} 31 | 32 | 33 |

34 |
35 | ); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /components/CubeMp4Embed.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | position: relative; 3 | } 4 | 5 | .playButtonWrapper { 6 | position: absolute; 7 | top: 0; 8 | width: 384px; 9 | height: 256px; 10 | max-width: 100%; 11 | max-height: 100%; 12 | opacity: 0.8; 13 | transition: opacity 0.2s; 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | } 18 | 19 | .playButtonWrapper:hover { 20 | opacity: 1; 21 | } 22 | 23 | .playButton { 24 | color: white; 25 | z-index: 3; 26 | width: 50px; 27 | height: 50px; 28 | filter: drop-shadow(0 0 5px rgb(0 0 0 / 0.8)); 29 | } 30 | 31 | .video { 32 | max-width: 384px; 33 | width: 100%; 34 | border-radius: 5px; 35 | } -------------------------------------------------------------------------------- /components/CubeMp4Embed.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from "react"; 2 | import styles from "./CubeMp4Embed.module.css"; 3 | import { FaPlay } from "react-icons/fa"; 4 | 5 | export interface CubeMp4EmbedProps { 6 | src: string; 7 | } 8 | 9 | export default function CubeMp4Embed({ src }: CubeMp4EmbedProps) { 10 | const videoRef = useRef(null); 11 | const [isPlaying, setIsPlaying] = useState(false); 12 | const handlePlay = () => { 13 | if (!videoRef.current) return; 14 | videoRef.current.onended = () => setIsPlaying(false); 15 | videoRef.current.play(); 16 | setIsPlaying(true); 17 | }; 18 | return ( 19 |
20 | 23 |
29 | 30 |
31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /components/PetGallery.module.css: -------------------------------------------------------------------------------- 1 | .petGrid { 2 | display: grid; 3 | gap: 16px; 4 | margin-top: 16px; 5 | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); 6 | } 7 | -------------------------------------------------------------------------------- /components/PetGallery.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./PetGallery.module.css"; 2 | import Image from "next/image"; 3 | 4 | type Pet = { 5 | name: string; 6 | src: string; 7 | owner: string; 8 | }; 9 | 10 | const pets: Pet[] = [ 11 | { 12 | name: "Harley", 13 | src: "/assets/pets/harley.jpg", 14 | owner: "S1neWav_", 15 | }, 16 | { 17 | name: "Cosmo", 18 | src: "/assets/pets/cosmo.jpg", 19 | owner: "S1neWav_", 20 | }, 21 | { 22 | name: "Spumoni", 23 | src: "/assets/pets/spumoni.jpg", 24 | owner: "Gen", 25 | }, 26 | { 27 | name: "Io", 28 | src: "/assets/pets/io.jpg", 29 | owner: "Gen", 30 | }, 31 | { 32 | name: "Artemis", 33 | src: "/assets/pets/artemis.jpg", 34 | owner: "Gen", 35 | }, 36 | { 37 | name: "Finn", 38 | src: "/assets/pets/finn.jpg", 39 | owner: "Gen", 40 | }, 41 | { 42 | name: "Eva", 43 | src: "/assets/pets/eva.jpg", 44 | owner: "Luna", 45 | }, 46 | { 47 | name: "Fufréda", 48 | src: "/assets/pets/fufreda.jpg", 49 | owner: "Jouda", 50 | }, 51 | { 52 | name: "Sir Harold III", 53 | src: "/assets/pets/sir_harold_iii.jpg", 54 | owner: "YeetPizza74", 55 | }, 56 | ]; 57 | 58 | export default function PetGallery() { 59 | return ( 60 |
61 | {pets.map((pet) => ( 62 |
66 | {pet.name} 78 |
79 | 80 | {pet.name} 81 | 82 | 83 | {pet.owner} 84 | 85 |
86 |
87 | ))} 88 |
89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /components/ReconCollection/ReconCollection.module.css: -------------------------------------------------------------------------------- 1 | /* Using colours from here: https://tailwindcss.com/docs/customizing-colors */ 2 | 3 | .container { 4 | display: flex; 5 | flex-direction: column; 6 | gap: 1.5rem; 7 | } 8 | 9 | .video { 10 | padding-bottom: 56.25%; 11 | position: relative; 12 | min-height: 250px; 13 | } 14 | 15 | .videoIframe { 16 | width: 100%; 17 | height: 100%; 18 | position: absolute; 19 | } 20 | 21 | .solvesList { 22 | display: grid; 23 | gap: 0.75rem; 24 | grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr)); 25 | } 26 | 27 | @media screen and (max-width: 1280px) { 28 | .solvesList { 29 | overflow-y: scroll; 30 | max-height: 7rem; 31 | } 32 | } 33 | 34 | .solveButton { 35 | width: 100%; 36 | } 37 | 38 | .button, .dropdown { 39 | height: 2rem; 40 | border-radius: 5px; 41 | transition: 0.2s ease-in; 42 | padding: 0 0.5rem; 43 | background-color: #f4f4f5; 44 | } 45 | 46 | .button:hover:not(:disabled), .dropdown:hover:not(:disabled) { 47 | background-color: #e4e4e7; 48 | } 49 | 50 | .button.active { 51 | background-color: #d4d4d8; 52 | } 53 | 54 | .button:disabled, .dropdown:disabled { 55 | opacity: 50%; 56 | cursor: not-allowed; 57 | } 58 | 59 | :is(html[class~="dark"]) .button, :is(html[class~="dark"]) .dropdown { 60 | background-color: #374151; 61 | } 62 | 63 | :is(html[class~="dark"]) .button:hover:not(:disabled), :is(html[class~="dark"]) .dropdown:hover:not(:disabled) { 64 | background-color: #4b5563; 65 | } 66 | 67 | :is(html[class~="dark"]) .button.active { 68 | background-color: #6b7280; 69 | } 70 | 71 | .checkboxContainer { 72 | display: flex; 73 | align-items: center; 74 | gap: 0.75rem; 75 | } 76 | 77 | .checkbox { 78 | width: 1rem; 79 | height: 1rem; 80 | } 81 | 82 | .navigationButtonGroup { 83 | display: flex; 84 | gap: 0.75rem; 85 | } 86 | 87 | .dropdown { 88 | max-width: min-content; 89 | } 90 | 91 | .dropdownGroup { 92 | display: flex; 93 | gap: 0.75rem; 94 | margin-top: -1rem; 95 | } -------------------------------------------------------------------------------- /components/ReconCollection/index.ts: -------------------------------------------------------------------------------- 1 | import dynamic from "next/dynamic"; 2 | 3 | export const ReconCollection = dynamic(() => import("./ReconCollection"), { 4 | ssr: false, 5 | }); 6 | -------------------------------------------------------------------------------- /components/ReconViewer.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 1.5rem; 5 | } 6 | 7 | .twistyPlayer { 8 | max-width: 100%; 9 | } 10 | 11 | @media screen and (min-width: 1080px) { 12 | .splitOnDesktop { 13 | flex-direction: row; 14 | } 15 | } 16 | 17 | .solveHeading { 18 | font-size: 1.35rem; 19 | font-weight: 600; 20 | } 21 | 22 | .scrambleAndSolution { 23 | display: flex; 24 | flex-direction: column; 25 | gap: 0.75rem; 26 | font-size: 1.1rem; 27 | } 28 | -------------------------------------------------------------------------------- /components/YouTubeEmbed.module.css: -------------------------------------------------------------------------------- 1 | .video { 2 | padding-bottom: 56.25%; 3 | position: relative; 4 | min-height: 250px; 5 | } 6 | 7 | .iframe { 8 | width: 100%; 9 | height: 100%; 10 | position: absolute; 11 | } 12 | -------------------------------------------------------------------------------- /components/YouTubeEmbed.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./YouTubeEmbed.module.css"; 2 | 3 | interface YouTubeEmbedProps { 4 | // Typical YouTube links are in the form "youtube.com/watch?v=" 5 | embedId: string; 6 | } 7 | 8 | // Options are limited. If you need more, use the `react-youtube` package 9 | export default function YouTubeEmbed({ embedId }: YouTubeEmbedProps) { 10 | return ( 11 |
12 |