├── .babelrc ├── .eslintrc.json ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── app ├── api │ └── comments │ │ └── route.ts ├── experiments │ └── family-browser-icon │ │ ├── icon.tsx │ │ └── page.tsx ├── fonts │ ├── intelone-mono │ │ └── regular.woff │ └── pp-editorial-new │ │ ├── bold.woff │ │ ├── bold.woff2 │ │ ├── regular.woff │ │ └── regular.woff2 ├── layout.tsx ├── styles.css └── svg-paths │ ├── arcs │ ├── content.tsx │ ├── content │ │ ├── arc-sandbox.md │ │ ├── arc-sandbox.tsx │ │ ├── ellipse.tsx │ │ ├── flags.tsx │ │ ├── rotation.tsx │ │ ├── small-ellipse.tsx │ │ ├── syntax.tsx │ │ └── types.ts │ ├── index.mdx │ ├── opengraph-image.png │ ├── page.tsx │ └── state.ts │ ├── bezier-curves │ ├── content.tsx │ ├── index.mdx │ ├── opengraph-image.png │ ├── page.tsx │ └── state.ts │ ├── challenge │ ├── content.tsx │ ├── index.mdx │ ├── opengraph-image.png │ └── page.tsx │ ├── components │ ├── README.md │ ├── button.tsx │ ├── command-list.tsx │ ├── draggable-endpoint.tsx │ ├── hooks │ │ └── use-media-query.ts │ ├── icons.tsx │ ├── index-provider.tsx │ ├── interactive-command.tsx │ ├── mdx.tsx │ ├── page-section.module.css │ ├── page-section.tsx │ ├── path-editor.tsx │ ├── path-hover-visual.tsx │ ├── path-practice.tsx │ ├── path-visualizer.tsx │ ├── path.tsx │ ├── play-slider.tsx │ ├── popover.tsx │ ├── ripple.tsx │ ├── slider.tsx │ ├── state-context.tsx │ ├── svg.tsx │ ├── svg │ │ ├── circle.tsx │ │ ├── drag-group.tsx │ │ ├── line.tsx │ │ ├── path.tsx │ │ ├── text.tsx │ │ └── tooltip.tsx │ ├── utils.tsx │ └── visual-wrapper.tsx │ ├── cubic-curves │ ├── content.tsx │ ├── content │ │ ├── chain.tsx │ │ ├── cubic-playground.tsx │ │ ├── curve-general.tsx │ │ ├── pill.tsx │ │ └── syntax.tsx │ ├── index.mdx │ ├── opengraph-image.png │ ├── page.tsx │ ├── state.ts │ └── types.ts │ ├── cursors │ ├── content.tsx │ ├── cursors.tsx │ ├── index.mdx │ ├── opengraph-image.png │ ├── page.tsx │ └── state.ts │ ├── index │ ├── absolute.tsx │ ├── commands.tsx │ ├── content.tsx │ ├── heart.tsx │ ├── index.mdx │ ├── square.tsx │ └── state.ts │ ├── layout.tsx │ ├── lib │ ├── fs.ts │ ├── path.test.ts │ └── path.ts │ ├── lines │ ├── components.tsx │ ├── content.tsx │ ├── index.mdx │ ├── opengraph-image.png │ ├── page.tsx │ └── state.ts │ ├── opengraph-image.png │ ├── page.tsx │ ├── provider.tsx │ └── utils.ts ├── assets ├── dark-default.json └── light-colorblind.json ├── bin ├── component.js └── stories.js ├── components ├── AlgorithmControls.tsx ├── Callout.tsx ├── Caption.tsx ├── ChangeIndicator.tsx ├── Checkbox │ ├── Checkbox.stories.tsx │ ├── Checkbox.tsx │ └── index.tsx ├── Content.tsx ├── Figure.tsx ├── FullWidth.tsx ├── Grid.tsx ├── Heading.tsx ├── Image.tsx ├── Img.tsx ├── Link │ ├── Link.stories.tsx │ ├── Link.tsx │ └── index.tsx ├── MobileBottomBar.tsx ├── MobileNavIsland │ ├── MobileNavIsland.stories.tsx │ ├── MobileNavIsland.tsx │ └── index.tsx ├── NewsletterForm.tsx ├── Note.tsx ├── OrderedList.tsx ├── PlayButton.tsx ├── Post.tsx ├── PrimaryButton │ ├── PrimaryButton.stories.tsx │ ├── PrimaryButton.tsx │ └── index.tsx ├── ProblemStatement.tsx ├── Quiz │ ├── Quiz.stories.tsx │ ├── Quiz.tsx │ └── index.tsx ├── Sandbox │ ├── Sandbox.stories.tsx │ ├── Sandbox.tsx │ ├── codemirror.ts │ └── index.tsx ├── SecondaryButton.tsx ├── SharedState.tsx ├── Slider.tsx ├── Spoiler.tsx ├── SubscribeButton │ ├── SubscribeButton.stories.tsx │ ├── SubscribeButton.tsx │ └── index.tsx ├── SubscribeInput.tsx ├── SvgGridWrapper │ ├── SvgGridWrapper.stories.tsx │ ├── SvgGridWrapper.tsx │ └── index.tsx ├── ThemeToggle.tsx ├── Visualizer │ ├── Visualizer.stories.tsx │ ├── Visualizer.tsx │ └── index.tsx ├── home │ ├── Debugger │ │ ├── Debugger.stories.tsx │ │ ├── Debugger.tsx │ │ └── index.tsx │ ├── FramerMagicMotion │ │ ├── FramerMagicMotion.stories.tsx │ │ ├── FramerMagicMotion.tsx │ │ └── index.tsx │ ├── FramerMotionKeys │ │ ├── FramerMotionKeys.stories.tsx │ │ ├── FramerMotionKeys.tsx │ │ └── index.tsx │ ├── HowArraysWork │ │ ├── HowArraysWork.stories.tsx │ │ ├── HowArraysWork.tsx │ │ └── index.tsx │ ├── SlidingWindow │ │ ├── SlidingWindow.stories.tsx │ │ ├── SlidingWindow.tsx │ │ └── index.tsx │ ├── SvgPaths │ │ ├── SvgPaths.stories.tsx │ │ ├── SvgPaths.tsx │ │ └── index.tsx │ ├── TokenizerVisual │ │ ├── TokenizerVisual.stories.tsx │ │ ├── TokenizerVisual.tsx │ │ └── index.tsx │ └── shared.tsx ├── layout │ ├── ExperimentsPage.tsx │ ├── Row.tsx │ └── StoriesPage.tsx └── utils │ └── SvgBackgroundGradient.tsx ├── content ├── aoc │ └── 2022 │ │ └── day-01 │ │ └── index.tsx ├── keys-in-framer-motion │ ├── components │ │ ├── AnimationShowcase │ │ │ ├── AnimationShowcase.stories.tsx │ │ │ ├── AnimationShowcase.tsx │ │ │ └── index.tsx │ │ ├── CodeQuiz │ │ │ ├── CodeQuiz.tsx │ │ │ └── index.tsx │ │ ├── Counter │ │ │ ├── Counter.tsx │ │ │ └── index.tsx │ │ ├── KanjiCarousel │ │ │ ├── KanjiCarousel.stories.tsx │ │ │ ├── KanjiCarousel.tsx │ │ │ ├── KanjiCarouselSlide.tsx │ │ │ └── index.tsx │ │ ├── NextButton │ │ │ ├── NextButton.stories.tsx │ │ │ ├── NextButton.tsx │ │ │ ├── Sandbox.tsx │ │ │ └── index.tsx │ │ └── RefreshComponent.tsx │ └── index.mdx ├── magic-motion │ ├── components │ │ ├── CorrectedInverseAnimation │ │ │ ├── CorrectedInverseAnimation.stories.tsx │ │ │ ├── CorrectedInverseAnimation.tsx │ │ │ └── index.tsx │ │ ├── FlipExample │ │ │ ├── FlipExample.stories.tsx │ │ │ ├── FlipExample.tsx │ │ │ └── index.tsx │ │ ├── FlipFirst │ │ │ ├── FlipFirst.stories.tsx │ │ │ ├── FlipFirst.tsx │ │ │ ├── index.ts │ │ │ └── machine.ts │ │ ├── FlipInverse │ │ │ ├── FlipInverse.stories.tsx │ │ │ ├── FlipInverse.tsx │ │ │ └── index.tsx │ │ ├── FlipLast │ │ │ ├── FlipLast.stories.tsx │ │ │ ├── FlipLast.tsx │ │ │ ├── index.ts │ │ │ └── machine.ts │ │ ├── FlipLastReact │ │ │ ├── FlipLastReact.stories.tsx │ │ │ ├── FlipLastReact.tsx │ │ │ └── index.tsx │ │ ├── FlipOverview │ │ │ ├── FlipOverview.stories.tsx │ │ │ ├── FlipOverview.tsx │ │ │ ├── index.tsx │ │ │ └── machine.ts │ │ ├── FlipPlay │ │ │ ├── FlipPlay.stories.tsx │ │ │ ├── FlipPlay.tsx │ │ │ └── index.tsx │ │ ├── InitialPositionSandbox │ │ │ ├── InitialPositionSandbox.stories.tsx │ │ │ ├── InitialPositionSandbox.tsx │ │ │ └── index.tsx │ │ ├── InverseSandbox │ │ │ ├── InverseSandbox.stories.tsx │ │ │ ├── InverseSandbox.tsx │ │ │ └── index.tsx │ │ ├── InverseScaleFormula │ │ │ ├── InverseScaleFormula.stories.tsx │ │ │ ├── InverseScaleFormula.tsx │ │ │ └── index.tsx │ │ ├── InverseScaleGraph │ │ │ ├── InverseScaleGraph.stories.tsx │ │ │ ├── InverseScaleGraph.tsx │ │ │ └── index.tsx │ │ ├── InverseScaleSandbox │ │ │ ├── InverseScaleSandbox.stories.tsx │ │ │ ├── InverseScaleSandbox.tsx │ │ │ └── index.tsx │ │ ├── InverseSizeSlider │ │ │ ├── InverseSizeSlider.stories.tsx │ │ │ ├── InverseSizeSlider.tsx │ │ │ └── index.tsx │ │ ├── LayoutChangeExample │ │ │ ├── LayoutChangeExample.stories.tsx │ │ │ ├── LayoutChangeExample.tsx │ │ │ └── index.tsx │ │ ├── Motion │ │ │ ├── Motion.stories.tsx │ │ │ ├── Motion.tsx │ │ │ └── index.tsx │ │ ├── MotionSandbox │ │ │ ├── MotionSandbox.stories.tsx │ │ │ ├── MotionSandbox.tsx │ │ │ └── index.tsx │ │ ├── MotionSquare │ │ │ ├── MotionSquare.stories.tsx │ │ │ ├── MotionSquare.tsx │ │ │ └── index.tsx │ │ ├── PlaySandbox │ │ │ ├── PlaySandbox.stories.tsx │ │ │ ├── PlaySandbox.tsx │ │ │ └── index.tsx │ │ ├── SizeDistanceExample │ │ │ ├── SizeDistanceExample.stories.tsx │ │ │ ├── SizeDistanceExample.tsx │ │ │ └── index.tsx │ │ ├── SizeDistanceInverseSnapshot │ │ │ ├── SizeDistanceInverseSnapshot.stories.tsx │ │ │ ├── SizeDistanceInverseSnapshot.tsx │ │ │ └── index.tsx │ │ ├── SizeDistanceRelationship │ │ │ ├── SizeDistanceRelationship.stories.tsx │ │ │ ├── SizeDistanceRelationship.tsx │ │ │ └── index.tsx │ │ ├── SizeLayoutExample │ │ │ ├── SizeLayoutExample.stories.tsx │ │ │ ├── SizeLayoutExample.tsx │ │ │ └── index.tsx │ │ ├── SizeMeasurements │ │ │ ├── SizeMeasurements.stories.tsx │ │ │ ├── SizeMeasurements.tsx │ │ │ └── index.tsx │ │ ├── SizePlayAnimation │ │ │ ├── SizePlayAnimation.stories.tsx │ │ │ ├── SizePlayAnimation.tsx │ │ │ └── index.tsx │ │ ├── WidthTransitionSandbox │ │ │ ├── WidthTransitionSandbox.stories.tsx │ │ │ ├── WidthTransitionSandbox.tsx │ │ │ └── index.tsx │ │ ├── shared.tsx │ │ ├── shared │ │ │ ├── HorizontalRuler │ │ │ │ ├── HorizontalRuler.stories.tsx │ │ │ │ ├── HorizontalRuler.tsx │ │ │ │ └── index.tsx │ │ │ ├── Ruler.tsx │ │ │ ├── SizeDiagram │ │ │ │ ├── SizeDiagram.stories.tsx │ │ │ │ ├── SizeDiagram.tsx │ │ │ │ └── index.tsx │ │ │ └── styles.tsx │ │ └── size.tsx │ └── index.mdx └── tokenizer │ ├── components │ ├── Boilerplate.tsx │ ├── CharacterList.tsx │ ├── Pipeline.tsx │ ├── TokenList.tsx │ ├── Tokenizer │ │ ├── Tokenizer.stories.tsx │ │ ├── Tokenizer.tsx │ │ └── index.ts │ └── lib │ │ ├── single-character.ts │ │ └── tokenize.ts │ └── index.mdx ├── experiments ├── BezierCurveQuiz │ ├── BezierCurveQuiz.stories.tsx │ ├── BezierCurveQuiz.tsx │ └── index.tsx ├── Cards │ ├── Cards.stories.tsx │ ├── Cards.tsx │ └── index.tsx ├── ControllableAnimation │ ├── ControllableAnimation.stories.tsx │ ├── ControllableAnimation.tsx │ └── index.tsx ├── EasingCurveEditor │ ├── EasingCurveEditor.stories.tsx │ ├── EasingCurveEditor.tsx │ └── index.tsx ├── EasingFunctionSandbox │ ├── EasingFunctionSandbox.stories.tsx │ ├── EasingFunctionSandbox.tsx │ └── index.tsx ├── TextLoadAnimation │ ├── TextLoadAnimation.stories.tsx │ ├── TextLoadAnimation.tsx │ ├── alphanumeric.tsx │ └── index.tsx └── TopDownParser │ ├── TopDownParser.stories.tsx │ ├── TopDownParser.tsx │ └── index.tsx ├── lib ├── algorithm │ ├── exec.ts │ ├── index.ts │ ├── snapshot.macro.d.ts │ ├── snapshot.macro.js │ ├── types.ts │ └── use-algorithm.ts ├── config.ts ├── content.server.ts ├── highlighter.ts ├── use-interval.ts └── utils.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── [content].tsx ├── _app.tsx ├── _document.tsx ├── _stories │ ├── [name].tsx │ └── index.tsx ├── api │ ├── auth │ │ └── [...nextauth].ts │ └── subscribe.ts ├── experiments │ ├── bezier-curves.tsx │ ├── cards.tsx │ ├── family-input.tsx │ ├── heavy-square.tsx │ ├── path-animation.tsx │ ├── safari-scroll.tsx │ └── scrambled-text.tsx ├── index.tsx └── stack-overflow.mdx ├── postcss.config.js ├── public ├── bg-dark.svg ├── favicon.ico ├── fonts │ └── pp-editorial-new │ │ ├── PPEditorialNew-Bold.woff │ │ ├── PPEditorialNew-Bold.woff2 │ │ ├── PPEditorialNew-Regular.woff │ │ └── PPEditorialNew-Regular.woff2 ├── grid-dark.svg ├── grid.svg ├── noise.png ├── og │ ├── debugger.png │ ├── how-arrays-work.png │ ├── index.png │ ├── keys-in-framer-motion.png │ ├── magic-motion.png │ ├── sliding-window.png │ └── tokenizer.png ├── shiki │ ├── dark-default.json │ ├── languages │ │ ├── css.tmLanguage.json │ │ ├── html.tmLanguage.json │ │ ├── javascript.tmLanguage.json │ │ ├── jsx.tmLanguage.json │ │ ├── tsx.tmLanguage.json │ │ └── typescript.tmLanguage.json │ └── onig.wasm ├── tokenizer │ ├── logo.svg │ ├── pipeline.svg │ ├── single-tokens.svg │ ├── token-types.svg │ └── tokens.svg └── vercel.svg ├── stitches.config.ts ├── stories.meta.ts ├── styles └── fonts.css ├── tailwind.config.js ├── tsconfig-esbuild.json ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["module-resolver", { 4 | "root": ["."], 5 | "alias": { 6 | "^~/(.+)": "./\\1" 7 | } 8 | }], 9 | "@babel/plugin-syntax-jsx", 10 | "macros" 11 | ], 12 | "presets": [ 13 | "@babel/preset-typescript" 14 | ] 15 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "react/no-unescaped-entities": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.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 | # vercel 32 | .vercel 33 | 34 | # generated files 35 | _dist-content/ 36 | old-src/ 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "grammarly.selectors": [ 4 | { 5 | "language": "mdx", 6 | "scheme": "file" 7 | } 8 | ], 9 | "typescript.enablePromptUseWorkspaceTsdk": true 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, 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 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /app/experiments/family-browser-icon/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { Icon } from "./icon"; 3 | 4 | export const metadata = { 5 | title: "Not a Number | Experiments", 6 | }; 7 | 8 | export default function FamilyBrowserIconPage() { 9 | return ( 10 |
11 |
12 |
13 |

14 | NaN 15 |

16 |
17 | 18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /app/fonts/intelone-mono/regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandanmen/NotANumber/6407ce11d8284491938e85ab73a9c85b5818663d/app/fonts/intelone-mono/regular.woff -------------------------------------------------------------------------------- /app/fonts/pp-editorial-new/bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandanmen/NotANumber/6407ce11d8284491938e85ab73a9c85b5818663d/app/fonts/pp-editorial-new/bold.woff -------------------------------------------------------------------------------- /app/fonts/pp-editorial-new/bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandanmen/NotANumber/6407ce11d8284491938e85ab73a9c85b5818663d/app/fonts/pp-editorial-new/bold.woff2 -------------------------------------------------------------------------------- /app/fonts/pp-editorial-new/regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandanmen/NotANumber/6407ce11d8284491938e85ab73a9c85b5818663d/app/fonts/pp-editorial-new/regular.woff -------------------------------------------------------------------------------- /app/fonts/pp-editorial-new/regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandanmen/NotANumber/6407ce11d8284491938e85ab73a9c85b5818663d/app/fonts/pp-editorial-new/regular.woff2 -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Nunito } from "next/font/google"; 2 | import localFont from "next/font/local"; 3 | import { clsx } from "clsx"; 4 | import "./styles.css"; 5 | import { AuthProvider } from "./svg-paths/provider"; 6 | import { Analytics } from "@vercel/analytics/react"; 7 | 8 | export const metadata = { 9 | title: "Next.js", 10 | description: "Generated by Next.js", 11 | }; 12 | 13 | const sans = Nunito({ 14 | weight: ["400", "600", "700"], 15 | variable: "--font-sans", 16 | subsets: ["latin"], 17 | }); 18 | 19 | const mono = localFont({ 20 | src: [ 21 | { 22 | path: "./fonts/intelone-mono/regular.woff", 23 | weight: "400", 24 | style: "normal", 25 | }, 26 | ], 27 | variable: "--font-mono", 28 | }); 29 | 30 | const serif = localFont({ 31 | src: [ 32 | { 33 | path: "./fonts/pp-editorial-new/regular.woff2", 34 | weight: "400", 35 | style: "normal", 36 | }, 37 | { 38 | path: "./fonts/pp-editorial-new/bold.woff2", 39 | weight: "700", 40 | style: "normal", 41 | }, 42 | ], 43 | variable: "--font-serif", 44 | }); 45 | 46 | export default function RootLayout({ 47 | children, 48 | }: { 49 | children: React.ReactNode; 50 | }) { 51 | return ( 52 | 53 | 61 | {children} 62 | 63 | 64 | 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /app/styles.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .inline-code:not(pre > code) { 6 | @apply bg-gray7 px-1 text-sm; 7 | } 8 | -------------------------------------------------------------------------------- /app/svg-paths/arcs/content/arc-sandbox.md: -------------------------------------------------------------------------------- 1 | The ideal API for this would be: 2 | 3 | ```tsx 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ``` 20 | -------------------------------------------------------------------------------- /app/svg-paths/arcs/content/rotation.tsx: -------------------------------------------------------------------------------- 1 | import { useStateContext } from "../state"; 2 | import { parsePath } from "app/svg-paths/lib/path"; 3 | import * as Arc from "./arc-sandbox"; 4 | import { SyntaxState } from "./types"; 5 | 6 | const path = parsePath("M 3 10 A 10 7.5 30 0 0 20 17"); 7 | 8 | export const initialState = { 9 | slice: [1], 10 | active: ["1.xAxisRotation"], 11 | path, 12 | }; 13 | 14 | export const components = { Rotation }; 15 | 16 | function Rotation() { 17 | const { data, set } = useStateContext("rotation"); 18 | return ( 19 | <> 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ); 35 | } 36 | 37 | export const page = { 38 | children: , 39 | }; 40 | -------------------------------------------------------------------------------- /app/svg-paths/arcs/content/small-ellipse.tsx: -------------------------------------------------------------------------------- 1 | import { useStateContext } from "../state"; 2 | import { useSvgContext } from "app/svg-paths/components/svg"; 3 | import { Tooltip } from "app/svg-paths/components/svg/tooltip"; 4 | import { parsePath } from "app/svg-paths/lib/path"; 5 | import * as Arc from "./arc-sandbox"; 6 | import { SyntaxState } from "./types"; 7 | 8 | const path = parsePath("M 3 10 A 10 7.5 0 0 0 20 17"); 9 | 10 | export const initialState = { path }; 11 | 12 | export const components = { SmallEllipse }; 13 | 14 | function SmallEllipse() { 15 | const { getRelative } = useSvgContext(); 16 | const { data, set } = useStateContext("small-ellipse"); 17 | const { path } = data; 18 | const arc = path.atAbsolute<"A">(1); 19 | return ( 20 | <> 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 36 | {arc.rx.toFixed(1)} 37 | 38 | 39 | ); 40 | } 41 | 42 | export const page = { 43 | children: , 44 | }; 45 | -------------------------------------------------------------------------------- /app/svg-paths/arcs/content/types.ts: -------------------------------------------------------------------------------- 1 | import type { Path } from "app/svg-paths/lib/path"; 2 | import type { DragGroupState } from "../../components/svg/drag-group"; 3 | 4 | export type SyntaxState = { 5 | path: Path; 6 | } & DragGroupState; 7 | -------------------------------------------------------------------------------- /app/svg-paths/arcs/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandanmen/NotANumber/6407ce11d8284491938e85ab73a9c85b5818663d/app/svg-paths/arcs/opengraph-image.png -------------------------------------------------------------------------------- /app/svg-paths/arcs/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import { serialize } from "next-mdx-remote/serialize"; 3 | import { readPage } from "../lib/fs"; 4 | import { Content } from "./content"; 5 | 6 | export const metadata: Metadata = { 7 | title: "SVG Path Commands | Arcs", 8 | description: `The arc command is the least intuitive of the SVG path commands; let's take a look at how to use it to draw arcs.`, 9 | authors: [ 10 | { 11 | name: "Nanda Syahrasyad", 12 | url: "https://twitter.com/nandafyi", 13 | }, 14 | ], 15 | twitter: { 16 | card: "summary_large_image", 17 | title: "SVG Path Commands | Arcs", 18 | description: `The arc command is the least intuitive of the SVG path commands; let's take a look at how to use it to draw arcs.`, 19 | creator: "@nandafyi", 20 | }, 21 | openGraph: { 22 | title: "SVG Path Commands | Arcs", 23 | description: `The arc command is the least intuitive of the SVG path commands; let's take a look at how to use it to draw arcs.`, 24 | url: "https://nan.fyi/svg-paths", 25 | siteName: "Not a Number", 26 | }, 27 | }; 28 | 29 | export default async function ArcsPage() { 30 | const { content, length } = await readPage("arcs"); 31 | return ; 32 | } 33 | -------------------------------------------------------------------------------- /app/svg-paths/arcs/state.ts: -------------------------------------------------------------------------------- 1 | import { useStateContext as useStateContextBase } from "../components/state-context"; 2 | import { getInitialPracticeQuestionState } from "../components/path-practice"; 3 | import { parsePath } from "../lib/path"; 4 | import * as ellipse from "./content/ellipse"; 5 | import * as smallEllipse from "./content/small-ellipse"; 6 | import * as rotation from "./content/rotation"; 7 | import * as flags from "./content/flags"; 8 | import { createInitialState } from "../components/svg/drag-group"; 9 | 10 | const practicePath = parsePath( 11 | "M 3 15 q 1.5 -2 1.5 -5 q 0 -2 1.5 -4 M 8 4 a 8 8 0 0 1 12 6 q 0.5 4 -2 9 M 13 21 q 1.5 -2 2 -5 M 16 12 v -1 a 4 4 0 0 0 -8 0 q 0 4 -2.5 7 M 8.5 20 q 3 -3 3.5 -9" 12 | ); 13 | 14 | const path = parsePath("M 3 10 A 10 7.5 0 0 0 20 15"); 15 | 16 | export const syntaxState = { 17 | ...createInitialState(), 18 | index: null, 19 | expanded: false, 20 | path, 21 | }; 22 | 23 | const state = { 24 | syntax: syntaxState, 25 | ellipse: ellipse.initialState, 26 | "small-ellipse": smallEllipse.initialState, 27 | rotation: rotation.initialState, 28 | flags: flags.initialState, 29 | ...getInitialPracticeQuestionState(practicePath), 30 | }; 31 | 32 | export const initialState = state; 33 | 34 | export const useStateContext = ( 35 | key: Key 36 | ) => { 37 | return useStateContextBase()(key); 38 | }; 39 | -------------------------------------------------------------------------------- /app/svg-paths/bezier-curves/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandanmen/NotANumber/6407ce11d8284491938e85ab73a9c85b5818663d/app/svg-paths/bezier-curves/opengraph-image.png -------------------------------------------------------------------------------- /app/svg-paths/bezier-curves/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import { serialize } from "next-mdx-remote/serialize"; 3 | import { readPage } from "../lib/fs"; 4 | import { Content } from "./content"; 5 | 6 | export const metadata: Metadata = { 7 | title: "SVG Path Commands | Bezier Curves", 8 | description: `The real power of SVG paths lies in its ability to draw curves. Let's take a look at the first type of curve: the quadratic bezier curve.`, 9 | authors: [ 10 | { 11 | name: "Nanda Syahrasyad", 12 | url: "https://twitter.com/nandafyi", 13 | }, 14 | ], 15 | twitter: { 16 | card: "summary_large_image", 17 | title: "SVG Path Commands | Bezier Curves", 18 | description: `The real power of SVG paths lies in its ability to draw curves. Let's take a look at the first type of curve: the quadratic bezier curve.`, 19 | creator: "@nandafyi", 20 | }, 21 | openGraph: { 22 | title: "SVG Path Commands | Bezier Curves", 23 | description: `The real power of SVG paths lies in its ability to draw curves. Let's take a look at the first type of curve: the quadratic bezier curve.`, 24 | url: "https://nan.fyi/svg-paths", 25 | siteName: "Not a Number", 26 | }, 27 | }; 28 | 29 | export default async function BezierCurvesPage() { 30 | const { content, length } = await readPage("bezier-curves"); 31 | return ; 32 | } 33 | -------------------------------------------------------------------------------- /app/svg-paths/bezier-curves/state.ts: -------------------------------------------------------------------------------- 1 | import { useStateContext as useStateContextBase } from "../components/state-context"; 2 | import { getInitialPracticeQuestionState } from "../components/path-practice"; 3 | import { parsePath } from "../lib/path"; 4 | import { createInitialState } from "../components/svg/drag-group"; 5 | 6 | export const initialState = { 7 | intro: parsePath( 8 | "M 5 5 q 5 -3 10 0 M 5 10 c 5 -3 5 3 10 0 M 5 15 a 5 3 0 0 0 10 0" 9 | ), 10 | curve: { 11 | path: parsePath("M 5 0 v 5 Q 5 15 15 15 h 5"), 12 | ...createInitialState(), 13 | }, 14 | chain: { 15 | path: parsePath("M 5 5 Q 5 10 10 10 T 15 15"), 16 | ...createInitialState(), 17 | }, 18 | ...getInitialPracticeQuestionState( 19 | parsePath( 20 | "M 5 17 Q 10 8 15 17 M 10 12.5 Q 15 5 20 12.5 M 5 5 v 15 h 15 v -15 z" 21 | ) 22 | ), 23 | }; 24 | 25 | export const useStateContext = ( 26 | key: Key 27 | ) => { 28 | return useStateContextBase()(key); 29 | }; 30 | -------------------------------------------------------------------------------- /app/svg-paths/challenge/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandanmen/NotANumber/6407ce11d8284491938e85ab73a9c85b5818663d/app/svg-paths/challenge/opengraph-image.png -------------------------------------------------------------------------------- /app/svg-paths/challenge/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import { serialize } from "next-mdx-remote/serialize"; 3 | import { readPage } from "../lib/fs"; 4 | import { Content } from "./content"; 5 | 6 | export const metadata: Metadata = { 7 | title: "SVG Path Commands | Challenges", 8 | description: `Build a better intuition of SVG paths by tracing over eight beautiful icons courtesy of the Iconist's Central icon system.`, 9 | authors: [ 10 | { 11 | name: "Nanda Syahrasyad", 12 | url: "https://twitter.com/nandafyi", 13 | }, 14 | ], 15 | twitter: { 16 | card: "summary_large_image", 17 | title: "SVG Path Commands | Challenges", 18 | description: `Build a better intuition of SVG paths by tracing over eight beautiful icons courtesy of the Iconist's Central icon system.`, 19 | creator: "@nandafyi", 20 | }, 21 | openGraph: { 22 | title: "SVG Path Commands | Challenges", 23 | description: `Build a better intuition of SVG paths by tracing over eight beautiful icons courtesy of the Iconist's Central icon system.`, 24 | url: "https://nan.fyi/svg-paths", 25 | siteName: "Not a Number", 26 | }, 27 | }; 28 | 29 | export default async function ChallengePage() { 30 | const { content, length } = await readPage("challenge"); 31 | return ; 32 | } 33 | -------------------------------------------------------------------------------- /app/svg-paths/components/button.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import { motion } from "framer-motion"; 3 | 4 | export function Button({ 5 | className, 6 | ...props 7 | }: React.ComponentPropsWithoutRef<(typeof motion)["button"]>) { 8 | return ( 9 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /app/svg-paths/components/hooks/use-media-query.ts: -------------------------------------------------------------------------------- 1 | // src: https://usehooks-ts.com/react-hook/use-media-query 2 | 3 | import { useEffect, useState } from "react"; 4 | 5 | export function useMediaQuery(query: string): boolean { 6 | const getMatches = (query: string): boolean => { 7 | // Prevents SSR issues 8 | if (typeof window !== "undefined") { 9 | return window.matchMedia(query).matches; 10 | } 11 | return false; 12 | }; 13 | 14 | const [matches, setMatches] = useState(getMatches(query)); 15 | 16 | function handleChange() { 17 | setMatches(getMatches(query)); 18 | } 19 | 20 | useEffect(() => { 21 | const matchMedia = window.matchMedia(query); 22 | 23 | // Triggered at the first client-side load and if query changes 24 | handleChange(); 25 | 26 | // Listen matchMedia 27 | matchMedia.addEventListener("change", handleChange); 28 | 29 | return () => { 30 | matchMedia.removeEventListener("change", handleChange); 31 | }; 32 | // eslint-disable-next-line react-hooks/exhaustive-deps 33 | }, [query]); 34 | 35 | return matches; 36 | } 37 | -------------------------------------------------------------------------------- /app/svg-paths/components/icons.tsx: -------------------------------------------------------------------------------- 1 | export const Refresh = () => { 2 | return ( 3 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export const Pause = () => { 22 | return ( 23 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | export const Play = () => { 40 | return ( 41 | 51 | 52 | 53 | ); 54 | }; 55 | 56 | export const ArrowRight = () => { 57 | return ( 58 | 68 | 69 | 70 | 71 | ); 72 | }; 73 | 74 | export const ArrowLeft = () => { 75 | return ( 76 | 86 | 87 | 88 | 89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /app/svg-paths/components/index-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | 5 | const IndexContext = React.createContext<{ 6 | index: number; 7 | numSections: number; 8 | next: () => void; 9 | prev: () => void; 10 | set: (index: number) => void; 11 | }>(null); 12 | 13 | export function IndexProvider({ 14 | children, 15 | numSections = Number.POSITIVE_INFINITY, 16 | }: { 17 | numSections?: number; 18 | children: React.ReactNode; 19 | }) { 20 | const [index, setIndex] = React.useState(0); 21 | const next = React.useCallback( 22 | () => setIndex((i) => Math.min(i + 1, numSections - 1)), 23 | [numSections] 24 | ); 25 | const prev = React.useCallback(() => setIndex((i) => Math.max(i - 1, 0)), []); 26 | return ( 27 | 30 | {children} 31 | 32 | ); 33 | } 34 | 35 | export const useIndexContext = () => React.useContext(IndexContext); 36 | -------------------------------------------------------------------------------- /app/svg-paths/components/interactive-command.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion, transform } from "framer-motion"; 3 | import { useStateContext } from "./state-context"; 4 | 5 | const BUTTON_GRID_SIZE = 32; 6 | const GRID_PADDING = 4; 7 | const MIDPOINT = BUTTON_GRID_SIZE / 2; 8 | 9 | const getOffset = transform([-100, 100], [-MIDPOINT, MIDPOINT]); 10 | 11 | export const InteractiveCommand = ({ id, template }) => { 12 | const [offset, setOffset] = React.useState({ x: 0, y: 0 }); 13 | const { 14 | data: { x, y }, 15 | set, 16 | } = useStateContext>()(id); 17 | return ( 18 |
19 |

20 | {template({ x, y })} 21 |

22 | { 25 | const deltaX = transform(info.delta.x, [-100, 100], [-10, 10]); 26 | const deltaY = transform(info.delta.y, [-100, 100], [-10, 10]); 27 | set({ x: x + deltaX, y: y + deltaY }); 28 | setOffset({ 29 | x: getOffset(info.offset.x), 30 | y: getOffset(info.offset.y), 31 | }); 32 | }} 33 | onPanEnd={() => setOffset({ x: 0, y: 0 })} 34 | > 35 | 42 | 48 | 49 | 50 | 51 | 52 | 64 | 65 | 66 |
67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /app/svg-paths/components/page-section.module.css: -------------------------------------------------------------------------------- 1 | .section > * { 2 | grid-column: 2; 3 | } 4 | 5 | .section > figure, 6 | .section > svg { 7 | grid-column: 1 / -1; 8 | } 9 | 10 | .prefix + .section:first-of-type { 11 | padding-top: 0; 12 | } 13 | 14 | @media (min-width: theme(screens.lg)) { 15 | .section > figure, 16 | .section > svg { 17 | display: none; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/svg-paths/components/path-editor.tsx: -------------------------------------------------------------------------------- 1 | import { useStateContext } from "./state-context"; 2 | 3 | export const PathEditor = ({ id, placeholder = "" }) => { 4 | const { data, set } = 5 | useStateContext>()(id); 6 | const value = data?.value || ""; 7 | return ( 8 |
9 | 10 | 11 | 12 |