Repository: remeda/remeda
Pull request #708 (2024-06-02 15:20:45)

docs(mapping): Move mappings to astro content infra
Using astro content to streamline our mapping docs:
1. Splitting lodash and ramda, rarely will the user of one lib care about the mapping for the other
2. Indexing the mapping on the mapped function, and not the remeda equivalent, as that's the direction the user cares about.
3. Allowing both a simple mapping and additional content in markdown to add comments, examples, caveats, etc...
4. The ability to not provide a remeda function and instead provide the alternative, in cases where we don't have an equivalent function.
5. This allows adding docs to the mapping, like prerequisite steps or general instructions.
6. Allows mapping any library, without getting into issues with the size of the table.
7. Less merge conflicts because there isn't a single monolithic object to edit. 

codesandbox-ci[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/remeda/remeda/pr/708/builds/511011) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 76d2303b8d68291f1d7c92593e2f63e31df5c0f3:

|Sandbox| Source |
|--|--|
|[remeda-example-vanilla](https://codesandbox.io/s/remeda-example-vanilla-fc26dv)| Configuration |

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/remeda/remeda/pull/708?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`f87d15c`)](https://app.codecov.io/gh/remeda/remeda/commit/f87d15c31602640d1b68fe166e7bc506e1c4027c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda) to head [(`76d2303`)](https://app.codecov.io/gh/remeda/remeda/commit/76d2303b8d68291f1d7c92593e2f63e31df5c0f3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda).
> Report is 2 commits behind head on main.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main      #708   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          147       147           
  Lines        10151     10165   +14     
  Branches       734       734           
=========================================
+ Hits         10151     10165   +14     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/remeda/remeda/pull/708?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda).

cjquines: do we want to maintain this in the docs folder? in #519 it was suggested we put it in jsdoc. i guess a similar question would be like, do we still want to keep the metadata in the jsdoc, then, if we're going to have this?
eranhirsch: > in #519 it was suggested we put it in jsdoc. i guess a similar question would be like, do we still want to keep the metadata in the jsdoc, then, if we're going to have this?

I think this solution makes more sense, putting it in the JSDoc means that everyone using Remeda has some abstract "dependency" on Lodash and Ramda. It doesn't make sense. Originally I thought it's a clean way to bundle everything together, mainly so we can stop maintaining the mapping file, but with this design we can decouple everything. We can build more mappings without impacting other mappings or the Remeda code itself. Mapping live on their own.
eranhirsch: > do we want to maintain this in the docs folder? 

It could, but I feel this is a different concern. It allows us to add metadata specific to mappings (like the "remeda" field). It makes the **docs** folder a classical wiki for adding articles to a docs page while the **mapping** objects focus on just the mapping entry.

Patch:
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 9c85bea..75b31bb 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -7,7 +7,7 @@ Make sure that you:
 - [ ] Typedoc added for new methods and updated for changed
 - [ ] Tests added for new methods and updated for changed
 - [ ] New methods added to `src/index.ts`
-- [ ] New methods added to `docs/src/pages/mapping.md`
+- [ ] New methods added to `docs/src/content/mapping/lodash` and `docs/src/pages/migrate/ramda.md`
 
 ---
 
diff --git a/docs/package-lock.json b/docs/package-lock.json
index ceba634..d8b8392 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -20,12 +20,12 @@
         "@radix-ui/react-scroll-area": "^1.0.5",
         "@radix-ui/react-select": "^2.0.0",
         "@radix-ui/react-slot": "^1.0.2",
-        "astro": "^4.9.2",
+        "astro": "^4.9.3",
         "class-variance-authority": "^0.7.0",
         "clsx": "^2.1.1",
         "react": "^18.3.1",
         "react-dom": "^18.3.1",
-        "remeda": "^2.0.3",
+        "remeda": "^2.0.5",
         "tailwind-merge": "^2.3.0"
       },
       "devDependencies": {
@@ -34,11 +34,11 @@
         "@types/dompurify": "^3.0.5",
         "@types/eslint": "^8.56.10",
         "@types/jsdom": "^21.1.7",
-        "@types/node": "^20.13.0",
+        "@types/node": "^20.14.2",
         "@types/react": "^18.3.3",
         "@types/react-dom": "^18.3.0",
-        "@typescript-eslint/eslint-plugin": "^7.11.0",
-        "@typescript-eslint/parser": "^7.11.0",
+        "@typescript-eslint/eslint-plugin": "^7.12.0",
+        "@typescript-eslint/parser": "^7.12.0",
         "dompurify": "^3.1.5",
         "eslint": "^8.57.0",
         "eslint-config-prettier": "^9.1.0",
@@ -50,14 +50,14 @@
         "jsdom": "^24.1.0",
         "marked": "^12.0.2",
         "open-cli": "^8.0.0",
-        "prettier": "^3.2.5",
+        "prettier": "^3.3.1",
         "prettier-plugin-astro": "^0.14.0",
         "prettier-plugin-tailwindcss": "^0.6.1",
         "rollup-plugin-visualizer": "^5.12.0",
-        "tailwindcss": "^3.4.3",
+        "tailwindcss": "^3.4.4",
         "tailwindcss-animate": "^1.0.7",
         "tiny-invariant": "^1.3.3",
-        "type-fest": "^4.18.3",
+        "type-fest": "^4.19.0",
         "typedoc": "^0.25.13",
         "typescript": "^5.4.5"
       }
@@ -412,11 +412,12 @@
       }
     },
     "node_modules/@babel/code-frame": {
-      "version": "7.24.2",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
-      "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
+      "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/highlight": "^7.24.2",
+        "@babel/highlight": "^7.24.7",
         "picocolors": "^1.0.0"
       },
       "engines": {
@@ -424,28 +425,30 @@
       }
     },
     "node_modules/@babel/compat-data": {
-      "version": "7.24.4",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz",
-      "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz",
+      "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==",
+      "license": "MIT",
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/core": {
-      "version": "7.24.5",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz",
-      "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz",
+      "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==",
+      "license": "MIT",
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
-        "@babel/code-frame": "^7.24.2",
-        "@babel/generator": "^7.24.5",
-        "@babel/helper-compilation-targets": "^7.23.6",
-        "@babel/helper-module-transforms": "^7.24.5",
-        "@babel/helpers": "^7.24.5",
-        "@babel/parser": "^7.24.5",
-        "@babel/template": "^7.24.0",
-        "@babel/traverse": "^7.24.5",
-        "@babel/types": "^7.24.5",
+        "@babel/code-frame": "^7.24.7",
+        "@babel/generator": "^7.24.7",
+        "@babel/helper-compilation-targets": "^7.24.7",
+        "@babel/helper-module-transforms": "^7.24.7",
+        "@babel/helpers": "^7.24.7",
+        "@babel/parser": "^7.24.7",
+        "@babel/template": "^7.24.7",
+        "@babel/traverse": "^7.24.7",
+        "@babel/types": "^7.24.7",
         "convert-source-map": "^2.0.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.2",
@@ -469,11 +472,12 @@
       }
     },
     "node_modules/@babel/generator": {
-      "version": "7.24.5",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz",
-      "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz",
+      "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/types": "^7.24.5",
+        "@babel/types": "^7.24.7",
         "@jridgewell/gen-mapping": "^0.3.5",
         "@jridgewell/trace-mapping": "^0.3.25",
         "jsesc": "^2.5.1"
@@ -483,23 +487,25 @@
       }
     },
     "node_modules/@babel/helper-annotate-as-pure": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
-      "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz",
+      "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/types": "^7.22.5"
+        "@babel/types": "^7.24.7"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-compilation-targets": {
-      "version": "7.23.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz",
-      "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz",
+      "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/compat-data": "^7.23.5",
-        "@babel/helper-validator-option": "^7.23.5",
+        "@babel/compat-data": "^7.24.7",
+        "@babel/helper-validator-option": "^7.24.7",
         "browserslist": "^4.22.2",
         "lru-cache": "^5.1.1",
         "semver": "^6.3.1"
@@ -512,62 +518,72 @@
       "version": "6.3.1",
       "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
       "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "license": "ISC",
       "bin": {
         "semver": "bin/semver.js"
       }
     },
     "node_modules/@babel/helper-environment-visitor": {
-      "version": "7.22.20",
-      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
-      "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
+      "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.24.7"
+      },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-function-name": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
-      "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
+      "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/template": "^7.22.15",
-        "@babel/types": "^7.23.0"
+        "@babel/template": "^7.24.7",
+        "@babel/types": "^7.24.7"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-hoist-variables": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
-      "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
+      "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/types": "^7.22.5"
+        "@babel/types": "^7.24.7"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-module-imports": {
-      "version": "7.24.3",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz",
-      "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
+      "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/types": "^7.24.0"
+        "@babel/traverse": "^7.24.7",
+        "@babel/types": "^7.24.7"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-module-transforms": {
-      "version": "7.24.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz",
-      "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz",
+      "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/helper-environment-visitor": "^7.22.20",
-        "@babel/helper-module-imports": "^7.24.3",
-        "@babel/helper-simple-access": "^7.24.5",
-        "@babel/helper-split-export-declaration": "^7.24.5",
-        "@babel/helper-validator-identifier": "^7.24.5"
+        "@babel/helper-environment-visitor": "^7.24.7",
+        "@babel/helper-module-imports": "^7.24.7",
+        "@babel/helper-simple-access": "^7.24.7",
+        "@babel/helper-split-export-declaration": "^7.24.7",
+        "@babel/helper-validator-identifier": "^7.24.7"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -577,78 +593,86 @@
       }
     },
     "node_modules/@babel/helper-plugin-utils": {
-      "version": "7.24.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz",
-      "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz",
+      "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==",
+      "license": "MIT",
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-simple-access": {
-      "version": "7.24.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz",
-      "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz",
+      "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/types": "^7.24.5"
+        "@babel/traverse": "^7.24.7",
+        "@babel/types": "^7.24.7"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-split-export-declaration": {
-      "version": "7.24.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
-      "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
+      "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/types": "^7.24.5"
+        "@babel/types": "^7.24.7"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-string-parser": {
-      "version": "7.24.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
-      "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz",
+      "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==",
+      "license": "MIT",
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-validator-identifier": {
-      "version": "7.24.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
-      "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
+      "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
+      "license": "MIT",
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-validator-option": {
-      "version": "7.23.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
-      "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz",
+      "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==",
+      "license": "MIT",
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.24.5",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz",
-      "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz",
+      "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/template": "^7.24.0",
-        "@babel/traverse": "^7.24.5",
-        "@babel/types": "^7.24.5"
+        "@babel/template": "^7.24.7",
+        "@babel/types": "^7.24.7"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/highlight": {
-      "version": "7.24.5",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz",
-      "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
+      "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/helper-validator-identifier": "^7.24.5",
+        "@babel/helper-validator-identifier": "^7.24.7",
         "chalk": "^2.4.2",
         "js-tokens": "^4.0.0",
         "picocolors": "^1.0.0"
@@ -658,9 +682,10 @@
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.24.5",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
-      "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz",
+      "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==",
+      "license": "MIT",
       "bin": {
         "parser": "bin/babel-parser.js"
       },
@@ -669,11 +694,12 @@
       }
     },
     "node_modules/@babel/plugin-syntax-jsx": {
-      "version": "7.24.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz",
-      "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz",
+      "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/helper-plugin-utils": "^7.24.0"
+        "@babel/helper-plugin-utils": "^7.24.7"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -683,15 +709,16 @@
       }
     },
     "node_modules/@babel/plugin-transform-react-jsx": {
-      "version": "7.23.4",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz",
-      "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.24.7.tgz",
+      "integrity": "sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/helper-annotate-as-pure": "^7.22.5",
-        "@babel/helper-module-imports": "^7.22.15",
-        "@babel/helper-plugin-utils": "^7.22.5",
-        "@babel/plugin-syntax-jsx": "^7.23.3",
-        "@babel/types": "^7.23.4"
+        "@babel/helper-annotate-as-pure": "^7.24.7",
+        "@babel/helper-module-imports": "^7.24.7",
+        "@babel/helper-plugin-utils": "^7.24.7",
+        "@babel/plugin-syntax-jsx": "^7.24.7",
+        "@babel/types": "^7.24.7"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -740,31 +767,33 @@
       }
     },
     "node_modules/@babel/template": {
-      "version": "7.24.0",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
-      "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
+      "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/code-frame": "^7.23.5",
-        "@babel/parser": "^7.24.0",
-        "@babel/types": "^7.24.0"
+        "@babel/code-frame": "^7.24.7",
+        "@babel/parser": "^7.24.7",
+        "@babel/types": "^7.24.7"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/traverse": {
-      "version": "7.24.5",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz",
-      "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==",
-      "dependencies": {
-        "@babel/code-frame": "^7.24.2",
-        "@babel/generator": "^7.24.5",
-        "@babel/helper-environment-visitor": "^7.22.20",
-        "@babel/helper-function-name": "^7.23.0",
-        "@babel/helper-hoist-variables": "^7.22.5",
-        "@babel/helper-split-export-declaration": "^7.24.5",
-        "@babel/parser": "^7.24.5",
-        "@babel/types": "^7.24.5",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz",
+      "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.24.7",
+        "@babel/generator": "^7.24.7",
+        "@babel/helper-environment-visitor": "^7.24.7",
+        "@babel/helper-function-name": "^7.24.7",
+        "@babel/helper-hoist-variables": "^7.24.7",
+        "@babel/helper-split-export-declaration": "^7.24.7",
+        "@babel/parser": "^7.24.7",
+        "@babel/types": "^7.24.7",
         "debug": "^4.3.1",
         "globals": "^11.1.0"
       },
@@ -773,12 +802,13 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.24.5",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
-      "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz",
+      "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==",
+      "license": "MIT",
       "dependencies": {
-        "@babel/helper-string-parser": "^7.24.1",
-        "@babel/helper-validator-identifier": "^7.24.5",
+        "@babel/helper-string-parser": "^7.24.7",
+        "@babel/helper-validator-identifier": "^7.24.7",
         "to-fast-properties": "^2.0.0"
       },
       "engines": {
@@ -843,12 +873,13 @@
       }
     },
     "node_modules/@esbuild/aix-ppc64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.3.tgz",
-      "integrity": "sha512-yTgnwQpFVYfvvo4SvRFB0SwrW8YjOxEoT7wfMT7Ol5v7v5LDNvSGo67aExmxOb87nQNeWPVvaGBNfQ7BXcrZ9w==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.4.tgz",
+      "integrity": "sha512-Zrm+B33R4LWPLjDEVnEqt2+SLTATlru1q/xYKVn8oVTbiRBGmK2VIMoIYGJDGyftnGaC788IuzGFAlb7IQ0Y8A==",
       "cpu": [
         "ppc64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "aix"
@@ -858,12 +889,13 @@
       }
     },
     "node_modules/@esbuild/android-arm": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.3.tgz",
-      "integrity": "sha512-bviJOLMgurLJtF1/mAoJLxDZDL6oU5/ztMHnJQRejbJrSc9FFu0QoUoFhvi6qSKJEw9y5oGyvr9fuDtzJ30rNQ==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.4.tgz",
+      "integrity": "sha512-E7H/yTd8kGQfY4z9t3nRPk/hrhaCajfA3YSQSBrst8B+3uTcgsi8N+ZWYCaeIDsiVs6m65JPCaQN/DxBRclF3A==",
       "cpu": [
         "arm"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "android"
@@ -873,12 +905,13 @@
       }
     },
     "node_modules/@esbuild/android-arm64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.3.tgz",
-      "integrity": "sha512-c+ty9necz3zB1Y+d/N+mC6KVVkGUUOcm4ZmT5i/Fk5arOaY3i6CA3P5wo/7+XzV8cb4GrI/Zjp8NuOQ9Lfsosw==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.4.tgz",
+      "integrity": "sha512-fYFnz+ObClJ3dNiITySBUx+oNalYUT18/AryMxfovLkYWbutXsct3Wz2ZWAcGGppp+RVVX5FiXeLYGi97umisA==",
       "cpu": [
         "arm64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "android"
@@ -888,12 +921,13 @@
       }
     },
     "node_modules/@esbuild/android-x64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.3.tgz",
-      "integrity": "sha512-JReHfYCRK3FVX4Ra+y5EBH1b9e16TV2OxrPAvzMsGeES0X2Ndm9ImQRI4Ket757vhc5XBOuGperw63upesclRw==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.4.tgz",
+      "integrity": "sha512-mDqmlge3hFbEPbCWxp4fM6hqq7aZfLEHZAKGP9viq9wMUBVQx202aDIfc3l+d2cKhUJM741VrCXEzRFhPDKH3Q==",
       "cpu": [
         "x64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "android"
@@ -903,12 +937,13 @@
       }
     },
     "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.3.tgz",
-      "integrity": "sha512-U3fuQ0xNiAkXOmQ6w5dKpEvXQRSpHOnbw7gEfHCRXPeTKW9sBzVck6C5Yneb8LfJm0l6le4NQfkNPnWMSlTFUQ==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.4.tgz",
+      "integrity": "sha512-72eaIrDZDSiWqpmCzVaBD58c8ea8cw/U0fq/PPOTqE3c53D0xVMRt2ooIABZ6/wj99Y+h4ksT/+I+srCDLU9TA==",
       "cpu": [
         "arm64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "darwin"
@@ -918,12 +953,13 @@
       }
     },
     "node_modules/@esbuild/darwin-x64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.3.tgz",
-      "integrity": "sha512-3m1CEB7F07s19wmaMNI2KANLcnaqryJxO1fXHUV5j1rWn+wMxdUYoPyO2TnAbfRZdi7ADRwJClmOwgT13qlP3Q==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.4.tgz",
+      "integrity": "sha512-uBsuwRMehGmw1JC7Vecu/upOjTsMhgahmDkWhGLWxIgUn2x/Y4tIwUZngsmVb6XyPSTXJYS4YiASKPcm9Zitag==",
       "cpu": [
         "x64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "darwin"
@@ -933,12 +969,13 @@
       }
     },
     "node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.3.tgz",
-      "integrity": "sha512-fsNAAl5pU6wmKHq91cHWQT0Fz0vtyE1JauMzKotrwqIKAswwP5cpHUCxZNSTuA/JlqtScq20/5KZ+TxQdovU/g==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.4.tgz",
+      "integrity": "sha512-8JfuSC6YMSAEIZIWNL3GtdUT5NhUA/CMUCpZdDRolUXNAXEE/Vbpe6qlGLpfThtY5NwXq8Hi4nJy4YfPh+TwAg==",
       "cpu": [
         "arm64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "freebsd"
@@ -948,12 +985,13 @@
       }
     },
     "node_modules/@esbuild/freebsd-x64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.3.tgz",
-      "integrity": "sha512-tci+UJ4zP5EGF4rp8XlZIdq1q1a/1h9XuronfxTMCNBslpCtmk97Q/5qqy1Mu4zIc0yswN/yP/BLX+NTUC1bXA==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.4.tgz",
+      "integrity": "sha512-8d9y9eQhxv4ef7JmXny7591P/PYsDFc4+STaxC1GBv0tMyCdyWfXu2jBuqRsyhY8uL2HU8uPyscgE2KxCY9imQ==",
       "cpu": [
         "x64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "freebsd"
@@ -963,12 +1001,13 @@
       }
     },
     "node_modules/@esbuild/linux-arm": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.3.tgz",
-      "integrity": "sha512-f6kz2QpSuyHHg01cDawj0vkyMwuIvN62UAguQfnNVzbge2uWLhA7TCXOn83DT0ZvyJmBI943MItgTovUob36SQ==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.4.tgz",
+      "integrity": "sha512-2rqFFefpYmpMs+FWjkzSgXg5vViocqpq5a1PSRgT0AvSgxoXmGF17qfGAzKedg6wAwyM7UltrKVo9kxaJLMF/g==",
       "cpu": [
         "arm"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -978,12 +1017,13 @@
       }
     },
     "node_modules/@esbuild/linux-arm64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.3.tgz",
-      "integrity": "sha512-vvG6R5g5ieB4eCJBQevyDMb31LMHthLpXTc2IGkFnPWS/GzIFDnaYFp558O+XybTmYrVjxnryru7QRleJvmZ6Q==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.4.tgz",
+      "integrity": "sha512-/GLD2orjNU50v9PcxNpYZi+y8dJ7e7/LhQukN3S4jNDXCKkyyiyAz9zDw3siZ7Eh1tRcnCHAo/WcqKMzmi4eMQ==",
       "cpu": [
         "arm64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -993,12 +1033,13 @@
       }
     },
     "node_modules/@esbuild/linux-ia32": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.3.tgz",
-      "integrity": "sha512-HjCWhH7K96Na+66TacDLJmOI9R8iDWDDiqe17C7znGvvE4sW1ECt9ly0AJ3dJH62jHyVqW9xpxZEU1jKdt+29A==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.4.tgz",
+      "integrity": "sha512-pNftBl7m/tFG3t2m/tSjuYeWIffzwAZT9m08+9DPLizxVOsUl8DdFzn9HvJrTQwe3wvJnwTdl92AonY36w/25g==",
       "cpu": [
         "ia32"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -1008,12 +1049,13 @@
       }
     },
     "node_modules/@esbuild/linux-loong64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.3.tgz",
-      "integrity": "sha512-BGpimEccmHBZRcAhdlRIxMp7x9PyJxUtj7apL2IuoG9VxvU/l/v1z015nFs7Si7tXUwEsvjc1rOJdZCn4QTU+Q==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.4.tgz",
+      "integrity": "sha512-cSD2gzCK5LuVX+hszzXQzlWya6c7hilO71L9h4KHwqI4qeqZ57bAtkgcC2YioXjsbfAv4lPn3qe3b00Zt+jIfQ==",
       "cpu": [
         "loong64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -1023,12 +1065,13 @@
       }
     },
     "node_modules/@esbuild/linux-mips64el": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.3.tgz",
-      "integrity": "sha512-5rMOWkp7FQGtAH3QJddP4w3s47iT20hwftqdm7b+loe95o8JU8ro3qZbhgMRy0VuFU0DizymF1pBKkn3YHWtsw==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.4.tgz",
+      "integrity": "sha512-qtzAd3BJh7UdbiXCrg6npWLYU0YpufsV9XlufKhMhYMJGJCdfX/G6+PNd0+v877X1JG5VmjBLUiFB0o8EUSicA==",
       "cpu": [
         "mips64el"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -1038,12 +1081,13 @@
       }
     },
     "node_modules/@esbuild/linux-ppc64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.3.tgz",
-      "integrity": "sha512-h0zj1ldel89V5sjPLo5H1SyMzp4VrgN1tPkN29TmjvO1/r0MuMRwJxL8QY05SmfsZRs6TF0c/IDH3u7XYYmbAg==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.4.tgz",
+      "integrity": "sha512-yB8AYzOTaL0D5+2a4xEy7OVvbcypvDR05MsB/VVPVA7nL4hc5w5Dyd/ddnayStDgJE59fAgNEOdLhBxjfx5+dg==",
       "cpu": [
         "ppc64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -1053,12 +1097,13 @@
       }
     },
     "node_modules/@esbuild/linux-riscv64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.3.tgz",
-      "integrity": "sha512-dkAKcTsTJ+CRX6bnO17qDJbLoW37npd5gSNtSzjYQr0svghLJYGYB0NF1SNcU1vDcjXLYS5pO4qOW4YbFama4A==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.4.tgz",
+      "integrity": "sha512-Y5AgOuVzPjQdgU59ramLoqSSiXddu7F3F+LI5hYy/d1UHN7K5oLzYBDZe23QmQJ9PIVUXwOdKJ/jZahPdxzm9w==",
       "cpu": [
         "riscv64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -1068,12 +1113,13 @@
       }
     },
     "node_modules/@esbuild/linux-s390x": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.3.tgz",
-      "integrity": "sha512-vnD1YUkovEdnZWEuMmy2X2JmzsHQqPpZElXx6dxENcIwTu+Cu5ERax6+Ke1QsE814Zf3c6rxCfwQdCTQ7tPuXA==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.4.tgz",
+      "integrity": "sha512-Iqc/l/FFwtt8FoTK9riYv9zQNms7B8u+vAI/rxKuN10HgQIXaPzKZc479lZ0x6+vKVQbu55GdpYpeNWzjOhgbA==",
       "cpu": [
         "s390x"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -1083,12 +1129,13 @@
       }
     },
     "node_modules/@esbuild/linux-x64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.3.tgz",
-      "integrity": "sha512-IOXOIm9WaK7plL2gMhsWJd+l2bfrhfilv0uPTptoRoSb2p09RghhQQp9YY6ZJhk/kqmeRt6siRdMSLLwzuT0KQ==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.4.tgz",
+      "integrity": "sha512-Td9jv782UMAFsuLZINfUpoF5mZIbAj+jv1YVtE58rFtfvoKRiKSkRGQfHTgKamLVT/fO7203bHa3wU122V/Bdg==",
       "cpu": [
         "x64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -1098,12 +1145,13 @@
       }
     },
     "node_modules/@esbuild/netbsd-x64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.3.tgz",
-      "integrity": "sha512-uTgCwsvQ5+vCQnqM//EfDSuomo2LhdWhFPS8VL8xKf+PKTCrcT/2kPPoWMTs22aB63MLdGMJiE3f1PHvCDmUOw==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.4.tgz",
+      "integrity": "sha512-Awn38oSXxsPMQxaV0Ipb7W/gxZtk5Tx3+W+rAPdZkyEhQ6968r9NvtkjhnhbEgWXYbgV+JEONJ6PcdBS+nlcpA==",
       "cpu": [
         "x64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "netbsd"
@@ -1113,12 +1161,13 @@
       }
     },
     "node_modules/@esbuild/openbsd-x64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.3.tgz",
-      "integrity": "sha512-vNAkR17Ub2MgEud2Wag/OE4HTSI6zlb291UYzHez/psiKarp0J8PKGDnAhMBcHFoOHMXHfExzmjMojJNbAStrQ==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.4.tgz",
+      "integrity": "sha512-IsUmQeCY0aU374R82fxIPu6vkOybWIMc3hVGZ3ChRwL9hA1TwY+tS0lgFWV5+F1+1ssuvvXt3HFqe8roCip8Hg==",
       "cpu": [
         "x64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "openbsd"
@@ -1128,12 +1177,13 @@
       }
     },
     "node_modules/@esbuild/sunos-x64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.3.tgz",
-      "integrity": "sha512-W8H9jlGiSBomkgmouaRoTXo49j4w4Kfbl6I1bIdO/vT0+0u4f20ko3ELzV3hPI6XV6JNBVX+8BC+ajHkvffIJA==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.4.tgz",
+      "integrity": "sha512-hsKhgZ4teLUaDA6FG/QIu2q0rI6I36tZVfM4DBZv3BG0mkMIdEnMbhc4xwLvLJSS22uWmaVkFkqWgIS0gPIm+A==",
       "cpu": [
         "x64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "sunos"
@@ -1143,12 +1193,13 @@
       }
     },
     "node_modules/@esbuild/win32-arm64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.3.tgz",
-      "integrity": "sha512-EjEomwyLSCg8Ag3LDILIqYCZAq/y3diJ04PnqGRgq8/4O3VNlXyMd54j/saShaN4h5o5mivOjAzmU6C3X4v0xw==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.4.tgz",
+      "integrity": "sha512-UUfMgMoXPoA/bvGUNfUBFLCh0gt9dxZYIx9W4rfJr7+hKe5jxxHmfOK8YSH4qsHLLN4Ck8JZ+v7Q5fIm1huErg==",
       "cpu": [
         "arm64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "win32"
@@ -1158,12 +1209,13 @@
       }
     },
     "node_modules/@esbuild/win32-ia32": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.3.tgz",
-      "integrity": "sha512-WGiE/GgbsEwR33++5rzjiYsKyHywE8QSZPF7Rfx9EBfK3Qn3xyR6IjyCr5Uk38Kg8fG4/2phN7sXp4NPWd3fcw==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.4.tgz",
+      "integrity": "sha512-yIxbspZb5kGCAHWm8dexALQ9en1IYDfErzjSEq1KzXFniHv019VT3mNtTK7t8qdy4TwT6QYHI9sEZabONHg+aw==",
       "cpu": [
         "ia32"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "win32"
@@ -1173,12 +1225,13 @@
       }
     },
     "node_modules/@esbuild/win32-x64": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.3.tgz",
-      "integrity": "sha512-xRxC0jaJWDLYvcUvjQmHCJSfMrgmUuvsoXgDeU/wTorQ1ngDdUBuFtgY3W1Pc5sprGAvZBtWdJX7RPg/iZZUqA==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.4.tgz",
+      "integrity": "sha512-sywLRD3UK/qRJt0oBwdpYLBibk7KiRfbswmWRDabuncQYSlf8aLEEUor/oP6KRz8KEG+HoiVLBhPRD5JWjS8Sg==",
       "cpu": [
         "x64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "win32"
@@ -2892,9 +2945,10 @@
       ]
     },
     "node_modules/@shikijs/core": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.6.0.tgz",
-      "integrity": "sha512-NIEAi5U5R7BLkbW1pG/ZKu3eb1lzc3/+jD0lFsuxMT7zjaf9bbNwdNyMr7zh/Zl8EXQtQ+MYBAt5G+JLu+5DlA=="
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.6.2.tgz",
+      "integrity": "sha512-guW5JeDzZ7uwOjTfCOFZ2VtVXk5tmkMzBYbKGfXsmAH1qYOej49L5jQDcGmwd6/OgvpmWhzO2GNJkQIFnbwLPQ==",
+      "license": "MIT"
     },
     "node_modules/@tailwindcss/typography": {
       "version": "0.5.13",
@@ -3064,10 +3118,11 @@
       "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="
     },
     "node_modules/@types/node": {
-      "version": "20.13.0",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.13.0.tgz",
-      "integrity": "sha512-FM6AOb3khNkNIXPnHFDYaHerSv8uN22C91z098AnGccVu+Pcdhi+pNUFDi0iLmPIsVE0JBD0KVS7mzUYt4nRzQ==",
+      "version": "20.14.2",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz",
+      "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==",
       "devOptional": true,
+      "license": "MIT",
       "dependencies": {
         "undici-types": "~5.26.4"
       }
@@ -3118,16 +3173,17 @@
       "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ=="
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "7.11.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz",
-      "integrity": "sha512-P+qEahbgeHW4JQ/87FuItjBj8O3MYv5gELDzr8QaQ7fsll1gSMTYb6j87MYyxwf3DtD7uGFB9ShwgmCJB5KmaQ==",
+      "version": "7.12.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.12.0.tgz",
+      "integrity": "sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "7.11.0",
-        "@typescript-eslint/type-utils": "7.11.0",
-        "@typescript-eslint/utils": "7.11.0",
-        "@typescript-eslint/visitor-keys": "7.11.0",
+        "@typescript-eslint/scope-manager": "7.12.0",
+        "@typescript-eslint/type-utils": "7.12.0",
+        "@typescript-eslint/utils": "7.12.0",
+        "@typescript-eslint/visitor-keys": "7.12.0",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
@@ -3151,15 +3207,16 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "7.11.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.11.0.tgz",
-      "integrity": "sha512-yimw99teuaXVWsBcPO1Ais02kwJ1jmNA1KxE7ng0aT7ndr1pT1wqj0OJnsYVGKKlc4QJai86l/025L6z8CljOg==",
+      "version": "7.12.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.12.0.tgz",
+      "integrity": "sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ==",
       "dev": true,
+      "license": "BSD-2-Clause",
       "dependencies": {
-        "@typescript-eslint/scope-manager": "7.11.0",
-        "@typescript-eslint/types": "7.11.0",
-        "@typescript-eslint/typescript-estree": "7.11.0",
-        "@typescript-eslint/visitor-keys": "7.11.0",
+        "@typescript-eslint/scope-manager": "7.12.0",
+        "@typescript-eslint/types": "7.12.0",
+        "@typescript-eslint/typescript-estree": "7.12.0",
+        "@typescript-eslint/visitor-keys": "7.12.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -3179,13 +3236,14 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "7.11.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.11.0.tgz",
-      "integrity": "sha512-27tGdVEiutD4POirLZX4YzT180vevUURJl4wJGmm6TrQoiYwuxTIY98PBp6L2oN+JQxzE0URvYlzJaBHIekXAw==",
+      "version": "7.12.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.12.0.tgz",
+      "integrity": "sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "7.11.0",
-        "@typescript-eslint/visitor-keys": "7.11.0"
+        "@typescript-eslint/types": "7.12.0",
+        "@typescript-eslint/visitor-keys": "7.12.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -3196,13 +3254,14 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "7.11.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.11.0.tgz",
-      "integrity": "sha512-WmppUEgYy+y1NTseNMJ6mCFxt03/7jTOy08bcg7bxJJdsM4nuhnchyBbE8vryveaJUf62noH7LodPSo5Z0WUCg==",
+      "version": "7.12.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.12.0.tgz",
+      "integrity": "sha512-lib96tyRtMhLxwauDWUp/uW3FMhLA6D0rJ8T7HmH7x23Gk1Gwwu8UZ94NMXBvOELn6flSPiBrCKlehkiXyaqwA==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "7.11.0",
-        "@typescript-eslint/utils": "7.11.0",
+        "@typescript-eslint/typescript-estree": "7.12.0",
+        "@typescript-eslint/utils": "7.12.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.3.0"
       },
@@ -3223,10 +3282,11 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "7.11.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz",
-      "integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==",
+      "version": "7.12.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.12.0.tgz",
+      "integrity": "sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==",
       "dev": true,
+      "license": "MIT",
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
       },
@@ -3236,13 +3296,14 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "7.11.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.11.0.tgz",
-      "integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==",
+      "version": "7.12.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.12.0.tgz",
+      "integrity": "sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ==",
       "dev": true,
+      "license": "BSD-2-Clause",
       "dependencies": {
-        "@typescript-eslint/types": "7.11.0",
-        "@typescript-eslint/visitor-keys": "7.11.0",
+        "@typescript-eslint/types": "7.12.0",
+        "@typescript-eslint/visitor-keys": "7.12.0",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -3264,15 +3325,16 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "7.11.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.11.0.tgz",
-      "integrity": "sha512-xlAWwPleNRHwF37AhrZurOxA1wyXowW4PqVXZVUNCLjB48CqdPJoJWkrpH2nij9Q3Lb7rtWindtoXwxjxlKKCA==",
+      "version": "7.12.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.12.0.tgz",
+      "integrity": "sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "7.11.0",
-        "@typescript-eslint/types": "7.11.0",
-        "@typescript-eslint/typescript-estree": "7.11.0"
+        "@typescript-eslint/scope-manager": "7.12.0",
+        "@typescript-eslint/types": "7.12.0",
+        "@typescript-eslint/typescript-estree": "7.12.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -3286,12 +3348,13 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "7.11.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.11.0.tgz",
-      "integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==",
+      "version": "7.12.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.12.0.tgz",
+      "integrity": "sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "7.11.0",
+        "@typescript-eslint/types": "7.12.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -3538,6 +3601,7 @@
       "version": "3.2.1",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
       "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "license": "MIT",
       "dependencies": {
         "color-convert": "^1.9.0"
       },
@@ -3763,20 +3827,21 @@
       }
     },
     "node_modules/astro": {
-      "version": "4.9.2",
-      "resolved": "https://registry.npmjs.org/astro/-/astro-4.9.2.tgz",
-      "integrity": "sha512-apzpRoISk6L8g99m3zKIW5SYxw2GhOju/1Gq0Ccf3SH+X2UcFXF+MTdxPOFl5yJUQg7X0qsVQDOIVf8zHRRKFg==",
+      "version": "4.9.3",
+      "resolved": "https://registry.npmjs.org/astro/-/astro-4.9.3.tgz",
+      "integrity": "sha512-LhiKZMee56GI7+zIt15bXZVX4JSnj4syTrQdkYNgE+Y0udMad0x8RgJGzc2NMOK31e6w/SL+NFSvnt/d1kgs5w==",
+      "license": "MIT",
       "dependencies": {
         "@astrojs/compiler": "^2.8.0",
         "@astrojs/internal-helpers": "0.4.0",
         "@astrojs/markdown-remark": "5.1.0",
         "@astrojs/telemetry": "3.1.0",
-        "@babel/core": "^7.24.5",
-        "@babel/generator": "^7.24.5",
-        "@babel/parser": "^7.24.5",
-        "@babel/plugin-transform-react-jsx": "^7.23.4",
-        "@babel/traverse": "^7.24.5",
-        "@babel/types": "^7.24.5",
+        "@babel/core": "^7.24.6",
+        "@babel/generator": "^7.24.6",
+        "@babel/parser": "^7.24.6",
+        "@babel/plugin-transform-react-jsx": "^7.24.6",
+        "@babel/traverse": "^7.24.6",
+        "@babel/types": "^7.24.6",
         "@types/babel__core": "^7.20.5",
         "@types/cookie": "^0.6.0",
         "acorn": "^8.11.3",
@@ -3795,8 +3860,8 @@
         "diff": "^5.2.0",
         "dlv": "^1.1.3",
         "dset": "^3.1.3",
-        "es-module-lexer": "^1.5.2",
-        "esbuild": "^0.21.2",
+        "es-module-lexer": "^1.5.3",
+        "esbuild": "^0.21.4",
         "estree-walker": "^3.0.3",
         "execa": "^8.0.1",
         "fast-glob": "^3.3.2",
@@ -3818,15 +3883,15 @@
         "rehype": "^13.0.1",
         "resolve": "^1.22.8",
         "semver": "^7.6.2",
-        "shiki": "^1.5.1",
+        "shiki": "^1.6.1",
         "string-width": "^7.1.0",
         "strip-ansi": "^7.1.0",
-        "tsconfck": "^3.0.3",
+        "tsconfck": "^3.1.0",
         "unist-util-visit": "^5.0.0",
         "vfile": "^6.0.1",
-        "vite": "^5.2.11",
+        "vite": "^5.2.12",
         "vitefu": "^0.2.5",
-        "which-pm": "^2.1.1",
+        "which-pm": "^2.2.0",
         "yargs-parser": "^21.1.1",
         "zod": "^3.23.8",
         "zod-to-json-schema": "^3.23.0"
@@ -4252,6 +4317,7 @@
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
       "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "license": "MIT",
       "dependencies": {
         "ansi-styles": "^3.2.1",
         "escape-string-regexp": "^1.0.5",
@@ -4531,6 +4597,7 @@
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
       "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "license": "MIT",
       "dependencies": {
         "color-name": "1.1.3"
       }
@@ -5215,10 +5282,11 @@
       }
     },
     "node_modules/esbuild": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.3.tgz",
-      "integrity": "sha512-Kgq0/ZsAPzKrbOjCQcjoSmPoWhlcVnGAUo7jvaLHoxW1Drto0KGkR1xBNg2Cp43b9ImvxmPEJZ9xkfcnqPsfBw==",
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.4.tgz",
+      "integrity": "sha512-sFMcNNrj+Q0ZDolrp5pDhH0nRPN9hLIM3fRPwgbLYJeSHHgnXSnbV3xYgSVuOeLWH9c73VwmEverVzupIv5xuA==",
       "hasInstallScript": true,
+      "license": "MIT",
       "bin": {
         "esbuild": "bin/esbuild"
       },
@@ -5226,29 +5294,29 @@
         "node": ">=12"
       },
       "optionalDependencies": {
-        "@esbuild/aix-ppc64": "0.21.3",
-        "@esbuild/android-arm": "0.21.3",
-        "@esbuild/android-arm64": "0.21.3",
-        "@esbuild/android-x64": "0.21.3",
-        "@esbuild/darwin-arm64": "0.21.3",
-        "@esbuild/darwin-x64": "0.21.3",
-        "@esbuild/freebsd-arm64": "0.21.3",
-        "@esbuild/freebsd-x64": "0.21.3",
-        "@esbuild/linux-arm": "0.21.3",
-        "@esbuild/linux-arm64": "0.21.3",
-        "@esbuild/linux-ia32": "0.21.3",
-        "@esbuild/linux-loong64": "0.21.3",
-        "@esbuild/linux-mips64el": "0.21.3",
-        "@esbuild/linux-ppc64": "0.21.3",
-        "@esbuild/linux-riscv64": "0.21.3",
-        "@esbuild/linux-s390x": "0.21.3",
-        "@esbuild/linux-x64": "0.21.3",
-        "@esbuild/netbsd-x64": "0.21.3",
-        "@esbuild/openbsd-x64": "0.21.3",
-        "@esbuild/sunos-x64": "0.21.3",
-        "@esbuild/win32-arm64": "0.21.3",
-        "@esbuild/win32-ia32": "0.21.3",
-        "@esbuild/win32-x64": "0.21.3"
+        "@esbuild/aix-ppc64": "0.21.4",
+        "@esbuild/android-arm": "0.21.4",
+        "@esbuild/android-arm64": "0.21.4",
+        "@esbuild/android-x64": "0.21.4",
+        "@esbuild/darwin-arm64": "0.21.4",
+        "@esbuild/darwin-x64": "0.21.4",
+        "@esbuild/freebsd-arm64": "0.21.4",
+        "@esbuild/freebsd-x64": "0.21.4",
+        "@esbuild/linux-arm": "0.21.4",
+        "@esbuild/linux-arm64": "0.21.4",
+        "@esbuild/linux-ia32": "0.21.4",
+        "@esbuild/linux-loong64": "0.21.4",
+        "@esbuild/linux-mips64el": "0.21.4",
+        "@esbuild/linux-ppc64": "0.21.4",
+        "@esbuild/linux-riscv64": "0.21.4",
+        "@esbuild/linux-s390x": "0.21.4",
+        "@esbuild/linux-x64": "0.21.4",
+        "@esbuild/netbsd-x64": "0.21.4",
+        "@esbuild/openbsd-x64": "0.21.4",
+        "@esbuild/sunos-x64": "0.21.4",
+        "@esbuild/win32-arm64": "0.21.4",
+        "@esbuild/win32-ia32": "0.21.4",
+        "@esbuild/win32-x64": "0.21.4"
       }
     },
     "node_modules/escalade": {
@@ -6452,6 +6520,7 @@
       "version": "11.12.0",
       "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
       "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+      "license": "MIT",
       "engines": {
         "node": ">=4"
       }
@@ -6562,6 +6631,7 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
       "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+      "license": "MIT",
       "engines": {
         "node": ">=4"
       }
@@ -7730,6 +7800,7 @@
       "version": "2.5.2",
       "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
       "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+      "license": "MIT",
       "bin": {
         "jsesc": "bin/jsesc"
       },
@@ -7999,6 +8070,7 @@
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
       "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "license": "ISC",
       "dependencies": {
         "yallist": "^3.0.2"
       }
@@ -10043,10 +10115,11 @@
       }
     },
     "node_modules/prettier": {
-      "version": "3.2.5",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
-      "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.1.tgz",
+      "integrity": "sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==",
       "devOptional": true,
+      "license": "MIT",
       "bin": {
         "prettier": "bin/prettier.cjs"
       },
@@ -10714,9 +10787,9 @@
       }
     },
     "node_modules/remeda": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.0.3.tgz",
-      "integrity": "sha512-3OtBvbFnhPJPNDxrP4dQ+Y7ghIErMeTy7KQ2vkC6XzrQHbFdlD0tT6mYaJ2S96lUu3umNnhkqni2lhVOdL9UOQ==",
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.0.5.tgz",
+      "integrity": "sha512-2+6w6XBjWk0Lysfss4QFo4LKjQ1SAW0wIEjQfp2MJjBs6BbiGQKzNli1xmUk/bHK7CBCdPS1KqjrWojgdruv0A==",
       "license": "MIT",
       "dependencies": {
         "type-fest": "^4.18.2"
@@ -11546,11 +11619,12 @@
       }
     },
     "node_modules/shiki": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.6.0.tgz",
-      "integrity": "sha512-P31ROeXcVgW/k3Z+vUUErcxoTah7ZRaimctOpzGuqAntqnnSmx1HOsvnbAB8Z2qfXPRhw61yptAzCsuKOhTHwQ==",
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.6.2.tgz",
+      "integrity": "sha512-X3hSm5GzzBd/BmPmGfkueOUADLyBoZo1ojYQXhd+NU2VJn458yt4duaS0rVzC+WtqftSV7mTVvDw+OB9AHi3Eg==",
+      "license": "MIT",
       "dependencies": {
-        "@shikijs/core": "1.6.0"
+        "@shikijs/core": "1.6.2"
       }
     },
     "node_modules/side-channel": {
@@ -12005,6 +12079,7 @@
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
       "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "license": "MIT",
       "dependencies": {
         "has-flag": "^3.0.0"
       },
@@ -12058,9 +12133,10 @@
       }
     },
     "node_modules/tailwindcss": {
-      "version": "3.4.3",
-      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
-      "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
+      "version": "3.4.4",
+      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
+      "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==",
+      "license": "MIT",
       "dependencies": {
         "@alloc/quick-lru": "^5.2.0",
         "arg": "^5.0.2",
@@ -12201,7 +12277,8 @@
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
       "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
-      "dev": true
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/to-fast-properties": {
       "version": "2.0.0",
@@ -12302,9 +12379,10 @@
       "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
     },
     "node_modules/tsconfck": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.0.3.tgz",
-      "integrity": "sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.0.tgz",
+      "integrity": "sha512-CMjc5zMnyAjcS9sPLytrbFmj89st2g+JYtY/c02ug4Q+CZaAtCgbyviI0n1YvjZE/pzoc6FbNsINS13DOL1B9w==",
+      "license": "MIT",
       "bin": {
         "tsconfck": "bin/tsconfck.js"
       },
@@ -12338,9 +12416,10 @@
       }
     },
     "node_modules/type-fest": {
-      "version": "4.18.3",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.3.tgz",
-      "integrity": "sha512-Q08/0IrpvM+NMY9PA2rti9Jb+JejTddwmwmVQGskAlhtcrw1wsRzoR6ode6mR+OAabNa75w/dxedSUY2mlphaQ==",
+      "version": "4.19.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.19.0.tgz",
+      "integrity": "sha512-CN2l+hWACRiejlnr68vY0/7734Kzu+9+TOslUXbSCQ1ruY9XIHDBSceVXCcHm/oXrdzhtLMMdJEKfemf1yXiZQ==",
+      "license": "(MIT OR CC0-1.0)",
       "engines": {
         "node": ">=16"
       },
@@ -12855,9 +12934,10 @@
       }
     },
     "node_modules/vite": {
-      "version": "5.2.11",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz",
-      "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==",
+      "version": "5.2.12",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz",
+      "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==",
+      "license": "MIT",
       "dependencies": {
         "esbuild": "^0.20.1",
         "postcss": "^8.4.38",
@@ -13915,7 +13995,8 @@
     "node_modules/yallist": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
-      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "license": "ISC"
     },
     "node_modules/yaml": {
       "version": "2.4.2",
diff --git a/docs/package.json b/docs/package.json
index c4b8ef7..775fb1b 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -28,12 +28,12 @@
     "@radix-ui/react-scroll-area": "^1.0.5",
     "@radix-ui/react-select": "^2.0.0",
     "@radix-ui/react-slot": "^1.0.2",
-    "astro": "^4.9.2",
+    "astro": "^4.9.3",
     "class-variance-authority": "^0.7.0",
     "clsx": "^2.1.1",
     "react": "^18.3.1",
     "react-dom": "^18.3.1",
-    "remeda": "^2.0.3",
+    "remeda": "^2.0.5",
     "tailwind-merge": "^2.3.0"
   },
   "devDependencies": {
@@ -42,11 +42,11 @@
     "@types/dompurify": "^3.0.5",
     "@types/eslint": "^8.56.10",
     "@types/jsdom": "^21.1.7",
-    "@types/node": "^20.13.0",
+    "@types/node": "^20.14.2",
     "@types/react": "^18.3.3",
     "@types/react-dom": "^18.3.0",
-    "@typescript-eslint/eslint-plugin": "^7.11.0",
-    "@typescript-eslint/parser": "^7.11.0",
+    "@typescript-eslint/eslint-plugin": "^7.12.0",
+    "@typescript-eslint/parser": "^7.12.0",
     "dompurify": "^3.1.5",
     "eslint": "^8.57.0",
     "eslint-config-prettier": "^9.1.0",
@@ -58,14 +58,14 @@
     "jsdom": "^24.1.0",
     "marked": "^12.0.2",
     "open-cli": "^8.0.0",
-    "prettier": "^3.2.5",
+    "prettier": "^3.3.1",
     "prettier-plugin-astro": "^0.14.0",
     "prettier-plugin-tailwindcss": "^0.6.1",
     "rollup-plugin-visualizer": "^5.12.0",
-    "tailwindcss": "^3.4.3",
+    "tailwindcss": "^3.4.4",
     "tailwindcss-animate": "^1.0.7",
     "tiny-invariant": "^1.3.3",
-    "type-fest": "^4.18.3",
+    "type-fest": "^4.19.0",
     "typedoc": "^0.25.13",
     "typescript": "^5.4.5"
   }
diff --git a/docs/src/components/docs-page-nav.astro b/docs/src/components/docs-page-nav.astro
new file mode 100644
index 0000000..685f2d9
--- /dev/null
+++ b/docs/src/components/docs-page-nav.astro
@@ -0,0 +1,37 @@
+---
+import { CATEGORIZED } from "@/lib/categorized";
+import { getNavbarEntries } from "@/lib/navbar-entries";
+import { CATEGORIZED as V1_CATEGORIZED } from "@/lib/v1/categorized";
+import { MobileNav } from "./mobile-nav";
+import { VERSIONS } from "./version-selector";
+import { getArticlesForPath } from "@/lib/docs";
+import { getMappingEntries } from "@/lib/mappings";
+
+const MIGRATION_REGEXP = /^\/migrate\/(?<library>\w+)$/iu;
+
+const { pathname } = Astro.url;
+const normalizedPathname = pathname.replace(/\/$/, "");
+
+const migrationLibrary = MIGRATION_REGEXP.exec(pathname)?.groups?.library;
+
+const entries =
+  migrationLibrary !== undefined
+    ? await getMappingEntries(migrationLibrary)
+    : normalizedPathname === VERSIONS.v1.path
+      ? V1_CATEGORIZED
+      : normalizedPathname === VERSIONS.latest.path
+        ? CATEGORIZED
+        : undefined;
+
+const articles = await getArticlesForPath(pathname);
+
+const navbarEntries =
+  entries === undefined ? [] : getNavbarEntries(entries, articles);
+---
+
+<MobileNav
+  showVersionSelector={migrationLibrary === undefined}
+  pathname={pathname}
+  entries={navbarEntries}
+  client:media="(max-width: 767px)"
+/>
diff --git a/docs/src/components/function-description.astro b/docs/src/components/function-description.astro
index 8564005..774d269 100644
--- a/docs/src/components/function-description.astro
+++ b/docs/src/components/function-description.astro
@@ -12,7 +12,7 @@ type Props = {
   readonly category: string | undefined;
   readonly sourceUrl: string | undefined;
   readonly description: string | undefined;
-  readonly tags: ReadonlyArray<Tag>;
+  readonly tags?: ReadonlyArray<Tag>;
 };
 
 const { name, sourceUrl, category, description, tags } = Astro.props;
@@ -21,7 +21,7 @@ const { name, sourceUrl, category, description, tags } = Astro.props;
 <>
   <div class="flex items-center gap-2">
     <CardTitle className="flex-1 text-2xl">{name}</CardTitle>
-    {tags.map((tag) => <TagBadge tag={tag} className="h-8" />)}
+    {tags?.map((tag) => <TagBadge tag={tag} className="h-8" />)}
     <Badge className="h-8">{category}</Badge>
     {sourceUrl !== undefined && <GithubSource url={sourceUrl} />}
   </div>
diff --git a/docs/src/components/github-edit.astro b/docs/src/components/github-edit.astro
new file mode 100644
index 0000000..bb9a8da
--- /dev/null
+++ b/docs/src/components/github-edit.astro
@@ -0,0 +1,28 @@
+---
+import { buttonVariants } from "@/components/ui/button";
+import { Pencil2Icon } from "@radix-ui/react-icons";
+import type { CollectionEntry, CollectionKey } from "astro:content";
+
+const GITHUB_EDIT_BASE_URL =
+  "https://github.com/remeda/remeda/edit/main/docs/src/content/";
+
+type Props<C extends CollectionKey> = {
+  readonly entry: CollectionEntry<C>;
+};
+
+const {
+  entry: { collection, id },
+} = Astro.props;
+
+const url = new URL(`${collection}/${id}`, GITHUB_EDIT_BASE_URL);
+---
+
+<a
+  href={url}
+  target="_blank"
+  class={buttonVariants({ variant: "secondary", size: "sm" })}
+  title="Edit on Github"
+>
+  <Pencil2Icon />
+  <span class="sr-only">View source on GitHub</span>
+</a>
diff --git a/docs/src/components/header.astro b/docs/src/components/header.astro
index 52a1d10..08cab08 100644
--- a/docs/src/components/header.astro
+++ b/docs/src/components/header.astro
@@ -1,21 +1,9 @@
 ---
 import { buttonVariants } from "@/components/ui/button";
-import { CATEGORIZED } from "@/lib/categorized";
-import { getNavbarEntries } from "@/lib/navbar-entries";
-import { CATEGORIZED as V1_CATEGORIZED } from "@/lib/v1/categorized";
 import { GitHubLogoIcon } from "@radix-ui/react-icons";
-import { MobileNav } from "./mobile-nav";
+import DocsPageNav from "./docs-page-nav.astro";
 import NavLink from "./nav-link.astro";
 import { ThemeSwitcher } from "./theme-switcher";
-import { VERSIONS } from "./version-selector";
-
-const { pathname } = Astro.url;
-
-const isV1 = pathname === VERSIONS.v1.path;
-const navbarEntries = await getNavbarEntries(
-  isV1 ? V1_CATEGORIZED : CATEGORIZED,
-  ({ id }) => isV1 === id.startsWith("v1/"),
-);
 ---
 
 <header
@@ -26,7 +14,7 @@ const navbarEntries = await getNavbarEntries(
   >
     <a href="/" class="font-bold">Remeda</a>
     <NavLink href="/docs" additionalHrefs={["/v1"]}>Documentation</NavLink>
-    <NavLink href="/mapping">Lodash/Ramda</NavLink>
+    <NavLink directory href="/migrate">Migration Guides</NavLink>
     <div class="flex-1"></div>
     <a
       href="https://github.com/remeda/remeda"
@@ -38,9 +26,5 @@ const navbarEntries = await getNavbarEntries(
     </a>
   </nav>
   <ThemeSwitcher client:load />
-  <MobileNav
-    pathname={pathname}
-    entries={navbarEntries}
-    client:media="(max-width: 767px)"
-  />
+  <DocsPageNav />
 </header>
diff --git a/docs/src/components/mapping-function-card.astro b/docs/src/components/mapping-function-card.astro
new file mode 100644
index 0000000..7b22063
--- /dev/null
+++ b/docs/src/components/mapping-function-card.astro
@@ -0,0 +1,66 @@
+---
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { getEntry } from "astro:content";
+import { Badge } from "./ui/badge";
+import Prose from "./prose.astro";
+import { ArrowRightIcon } from "@radix-ui/react-icons";
+import { mappingUrl } from "@/lib/mappings";
+import GithubEdit from "./github-edit.astro";
+import invariant from "tiny-invariant";
+
+const COLLECTION = "mapping";
+
+type Props = {
+  readonly name: string;
+  readonly library: string;
+};
+
+const { name, library } = Astro.props;
+
+const slug = `${library}/${name}`.toLowerCase();
+const entry = await getEntry(COLLECTION, slug);
+invariant(entry !== undefined, `No entry for slug: ${slug}`);
+
+const { Content } = await entry.render();
+
+const mappedUrl = mappingUrl(library, name);
+---
+
+<Card id={name} className="scroll-mt-24">
+  <CardHeader>
+    <div class="flex items-center gap-2">
+      <CardTitle className="inline-flex flex-1 items-center gap-2 text-2xl"
+        >{name}
+        <a
+          target="_blank"
+          class="text-base text-primary hover:underline"
+          href={mappedUrl}>[{library}]</a
+        >{
+          entry.data.remeda !== undefined && (
+            <>
+              <ArrowRightIcon className="size-6" />
+              {entry.data.remeda}
+              <a
+                class="text-base text-primary hover:underline"
+                href={`/docs#${entry.data.remeda}`}
+              >
+                [remeda]
+              </a>
+            </>
+          )
+        }</CardTitle
+      >
+      <Badge className="h-8">{entry.data.category}</Badge>
+      <GithubEdit entry={entry} />
+    </div>
+  </CardHeader>
+  {
+    entry.body !== "" && (
+      <CardContent>
+        <Prose>
+          <Content />
+        </Prose>
+      </CardContent>
+    )
+  }
+</Card>
diff --git a/docs/src/components/mobile-nav.tsx b/docs/src/components/mobile-nav.tsx
index d4ae12e..d748197 100644
--- a/docs/src/components/mobile-nav.tsx
+++ b/docs/src/components/mobile-nav.tsx
@@ -4,16 +4,23 @@ import { HamburgerMenuIcon } from "@radix-ui/react-icons";
 import { useState, type ReactNode } from "react";
 import { Navbar, type NavbarCategory } from "./navbar";
 import { VersionSelector } from "./version-selector";
+import { isEmpty } from "remeda";
 
 export function MobileNav({
   pathname,
   entries,
+  showVersionSelector = false,
 }: {
   readonly pathname: string;
   readonly entries: ReadonlyArray<NavbarCategory>;
+  readonly showVersionSelector?: boolean;
 }): ReactNode {
   const [isOpen, setIsOpen] = useState(false);
 
+  if (isEmpty(entries)) {
+    return;
+  }
+
   return (
     <Sheet open={isOpen} onOpenChange={setIsOpen} modal={false}>
       <SheetTrigger asChild>
@@ -21,7 +28,6 @@ export function MobileNav({
           <HamburgerMenuIcon />
         </Button>
       </SheetTrigger>
-
       <SheetContent className="pt-12">
         <Navbar
           entries={entries}
@@ -29,7 +35,7 @@ export function MobileNav({
             setIsOpen(false);
           }}
         >
-          <VersionSelector pathname={pathname} />
+          {showVersionSelector && <VersionSelector pathname={pathname} />}
         </Navbar>
       </SheetContent>
     </Sheet>
diff --git a/docs/src/components/nav-link.astro b/docs/src/components/nav-link.astro
index 5f36690..c4e92b7 100644
--- a/docs/src/components/nav-link.astro
+++ b/docs/src/components/nav-link.astro
@@ -2,14 +2,21 @@
 type Props = {
   readonly href: string;
   readonly additionalHrefs?: readonly string[];
+  readonly directory?: boolean;
 };
 
-const { href, additionalHrefs = [] } = Astro.props;
+const {
+  href,
+  additionalHrefs = [],
+  directory: isDirectory = false,
+} = Astro.props;
 
 const { pathname } = Astro.url;
 
 const trimmed = pathname.replace(/\/$/, "");
-const isActive = [href, ...additionalHrefs].some((path) => path === trimmed);
+const isActive = [href, ...additionalHrefs].some((path) =>
+  isDirectory ? trimmed.startsWith(path) : path === trimmed,
+);
 ---
 
 <a
diff --git a/docs/src/components/ui/sheet.tsx b/docs/src/components/ui/sheet.tsx
index 9e6bf39..f4def2d 100644
--- a/docs/src/components/ui/sheet.tsx
+++ b/docs/src/components/ui/sheet.tsx
@@ -23,7 +23,7 @@ const SheetOverlay = forwardRef<
 >(({ className, ...props }, ref) => (
   <SheetPrimitive.Overlay
     className={cn(
-      "fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+      "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
       className,
     )}
     {...props}
diff --git a/docs/src/components/version-selector.tsx b/docs/src/components/version-selector.tsx
index a524c80..55ad6d6 100644
--- a/docs/src/components/version-selector.tsx
+++ b/docs/src/components/version-selector.tsx
@@ -25,7 +25,6 @@ export function VersionSelector({
 }: {
   readonly pathname: string;
 }): ReactNode {
-  console.log(pathname);
   const current = useMemo(() => {
     const trimmed = pathname.replace(/\/$/, "");
     return pipe(
diff --git a/docs/src/content/config.ts b/docs/src/content/config.ts
index 8ecafee..b35d202 100644
--- a/docs/src/content/config.ts
+++ b/docs/src/content/config.ts
@@ -1,7 +1,7 @@
 import { defineCollection, z } from "astro:content";
 
 export const collections = {
-  docs: defineCollection({
+  "docs-articles": defineCollection({
     type: "content",
     schema: z.object({
       /**
@@ -23,4 +23,12 @@ export const collections = {
   }),
 
   "v1-migration": defineCollection({ type: "content" }),
+
+  mapping: defineCollection({
+    type: "content",
+    schema: z.object({
+      category: z.string().min(1),
+      remeda: z.string().min(1).optional(),
+    }),
+  }),
 };
diff --git a/docs/src/content/docs/intro.md b/docs/src/content/docs-articles/docs/intro.md
similarity index 100%
rename from docs/src/content/docs/intro.md
rename to docs/src/content/docs-articles/docs/intro.md
diff --git a/docs/src/content/docs/v1/migration-environment.md b/docs/src/content/docs-articles/v1/migration-environment.md
similarity index 100%
rename from docs/src/content/docs/v1/migration-environment.md
rename to docs/src/content/docs-articles/v1/migration-environment.md
diff --git a/docs/src/content/docs/v1/migration-headless.md b/docs/src/content/docs-articles/v1/migration-headless.md
similarity index 100%
rename from docs/src/content/docs/v1/migration-headless.md
rename to docs/src/content/docs-articles/v1/migration-headless.md
diff --git a/docs/src/content/docs/v1/migration-intro.md b/docs/src/content/docs-articles/v1/migration-intro.md
similarity index 100%
rename from docs/src/content/docs/v1/migration-intro.md
rename to docs/src/content/docs-articles/v1/migration-intro.md
diff --git a/docs/src/content/docs/v1/migration-keys.md b/docs/src/content/docs-articles/v1/migration-keys.md
similarity index 100%
rename from docs/src/content/docs/v1/migration-keys.md
rename to docs/src/content/docs-articles/v1/migration-keys.md
diff --git a/docs/src/content/docs/v1/migration-removed.md b/docs/src/content/docs-articles/v1/migration-removed.md
similarity index 100%
rename from docs/src/content/docs/v1/migration-removed.md
rename to docs/src/content/docs-articles/v1/migration-removed.md
diff --git a/docs/src/content/docs/v1/migration-runtime.md b/docs/src/content/docs-articles/v1/migration-runtime.md
similarity index 100%
rename from docs/src/content/docs/v1/migration-runtime.md
rename to docs/src/content/docs-articles/v1/migration-runtime.md
diff --git a/docs/src/content/docs/v1/migration-variants.md b/docs/src/content/docs-articles/v1/migration-variants.md
similarity index 100%
rename from docs/src/content/docs/v1/migration-variants.md
rename to docs/src/content/docs-articles/v1/migration-variants.md
diff --git a/docs/src/content/mapping/lodash/__MISSING.md b/docs/src/content/mapping/lodash/__MISSING.md
new file mode 100644
index 0000000..ea20efc
--- /dev/null
+++ b/docs/src/content/mapping/lodash/__MISSING.md
@@ -0,0 +1,188 @@
+TODO: Go over: https://you-dont-need.github.io/You-Dont-Need-Lodash-Underscore/#/
+
+# Array
+
+- differenceBy
+- fill
+- flattenDepth
+- indexOf
+- initial
+- intersectionBy
+- lastIndexOf
+- pull
+- pullAll
+- pullAllBy
+- pullAllWith
+- pullAt
+- remove
+- slice
+- sortedIndexOf
+- sortedLastIndexOf
+- sortedUniqBy
+- tail
+- union
+- unionBy
+- unionWith
+- unzip
+- unzipWith
+- without
+- xor
+- xorBy
+- xorWith
+- zipObjectDeep
+
+# Date
+
+- now
+
+# Function
+
+- A lot...
+
+# Lang
+
+- castArray
+- clone
+- cloneDeepWith
+- cloneWith
+- conformsTo
+- eq
+- gt
+- gte
+- isArguments
+- All the type-guards we already have!
+- isArrayBuffer
+- isArrayLike
+- isArrayLikeObject
+- isBuffer
+- isElement
+- isEqualWith
+- isFinite
+- isInteger
+- isLength
+- isMap
+- isMatch
+- isMatchWith
+- isNaN
+- isNative
+- isObject
+- isSafeInteger
+- isSet
+- isTypedArray
+- isWeakMap
+- isWeakSet
+- lt
+- lte
+- toArray
+- toFinite
+- toInteger
+- toLength
+- toNumber
+- toPlainObject
+- toSafeInteger
+- toString
+
+# Math
+
+- mean
+
+# Number
+
+- inRange
+- random
+
+# Object
+
+- assignIn
+- assignInWith
+- assignWith
+- at
+- create
+- defaults
+- defaultsDeep
+- findKey
+- findLastKey
+- forIn
+- forInRight
+- forOwnRight
+- functions
+- functionsIn
+- has
+- hasIn
+- invertBy
+- invoke
+- keysIn
+- merge
+- mergeWith
+- result
+- setWith
+- toPairsIn
+- unset
+- update
+- updateWith
+
+# Seq
+
+- chain
+- thru
+
+# String
+
+- camelCase
+- capitalize
+- deburr
+- endsWith
+- escape
+- escapeRegExp
+- kebabCase
+- lowerCase
+- lowerFirst
+- pad
+- padEnd
+- padStart
+- parseInt
+- repeat
+- replace
+- snakeCase
+- split
+- startCase
+- startsWith
+- template
+- toLower
+- toUpper
+- trim
+- trimEnd
+- trimStart
+- truncate
+- unescape
+- upperCase
+- upperFirst
+
+# Util
+
+- attempt
+- bindAll
+- conforms
+- defaultTo
+- flowRight
+- iteratee
+- matches
+- matchesProperty
+- method
+- methodOf
+- mixin
+- noConflict
+- nthArg
+- over
+- overEvery
+- overSome
+- property
+- propertyOf
+- rangeRight
+- runInContext
+- stubArray
+- stubFalse
+- stubObject
+- stubString
+- stubTrue
+- uniqueId
diff --git a/docs/src/content/mapping/lodash/___TODO.md b/docs/src/content/mapping/lodash/___TODO.md
new file mode 100644
index 0000000..165e625
--- /dev/null
+++ b/docs/src/content/mapping/lodash/___TODO.md
@@ -0,0 +1,12 @@
+1. Guide: All Lodash functions under the "collection" group are actually hybrid
+   array/object functions. This isn't supported very well by the @types/lodash
+   package anyway, but we should provide some general instructions on how to
+   approach this difference, mainly suggesting using `values`.
+
+2. Guide: Lodash accepts `null | undefined` for most data inputs. We don't
+
+3. Guide: most `iteratee` functions in lodash only take one argument, we usually
+   take 3 params (index, data).
+
+4. `pick`, `pickBy`, `omit`, and `omitBy` handle symbol keys differently. We
+   need to compare this with how Lodash does it and document any differences.
diff --git a/docs/src/content/mapping/lodash/add.md b/docs/src/content/mapping/lodash/add.md
new file mode 100644
index 0000000..921e36d
--- /dev/null
+++ b/docs/src/content/mapping/lodash/add.md
@@ -0,0 +1,4 @@
+---
+category: Math
+remeda: add
+---
diff --git a/docs/src/content/mapping/lodash/assign.md b/docs/src/content/mapping/lodash/assign.md
new file mode 100644
index 0000000..3668a26
--- /dev/null
+++ b/docs/src/content/mapping/lodash/assign.md
@@ -0,0 +1,24 @@
+---
+category: Object
+remeda: merge
+---
+
+- In Lodash the merge is done in-place, on the first argument to the function.
+  In Remeda a new object is always returned, and none of the input objects are
+  mutated.
+- In Lodash `assign` can be used to merge any number of objects by accepting a
+  variadic argument list. In Remeda, if you are merging only 2 objects use
+  `merge`, but if you need to merge more than 2 objects use
+  [`mergeAll`](/docs#mergeAll) instead, which takes an _Array_ of objects.
+
+```ts
+// Lodash
+const DATA = { a: 1, b: 2 };
+assign(DATA, b);
+assign(DATA, b, c, d, e);
+
+// Remeda
+let DATA = { a: 1, b: 2 };
+DATA = merge(DATA, b);
+DATA = mergeAll([DATA, b, c, d, e]);
+```
diff --git a/docs/src/content/mapping/lodash/ceil.md b/docs/src/content/mapping/lodash/ceil.md
new file mode 100644
index 0000000..e4074f5
--- /dev/null
+++ b/docs/src/content/mapping/lodash/ceil.md
@@ -0,0 +1,4 @@
+---
+category: Math
+remeda: ceil
+---
diff --git a/docs/src/content/mapping/lodash/chunk.md b/docs/src/content/mapping/lodash/chunk.md
new file mode 100644
index 0000000..25f51e7
--- /dev/null
+++ b/docs/src/content/mapping/lodash/chunk.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: chunk
+---
diff --git a/docs/src/content/mapping/lodash/clamp.md b/docs/src/content/mapping/lodash/clamp.md
new file mode 100644
index 0000000..e07bb40
--- /dev/null
+++ b/docs/src/content/mapping/lodash/clamp.md
@@ -0,0 +1,4 @@
+---
+category: Number
+remeda: clamp
+---
diff --git a/docs/src/content/mapping/lodash/cloneDeep.md b/docs/src/content/mapping/lodash/cloneDeep.md
new file mode 100644
index 0000000..4d8e504
--- /dev/null
+++ b/docs/src/content/mapping/lodash/cloneDeep.md
@@ -0,0 +1,4 @@
+---
+category: Lang
+remeda: clone
+---
diff --git a/docs/src/content/mapping/lodash/compact.md b/docs/src/content/mapping/lodash/compact.md
new file mode 100644
index 0000000..2b2724d
--- /dev/null
+++ b/docs/src/content/mapping/lodash/compact.md
@@ -0,0 +1,14 @@
+---
+category: Array
+remeda: filter
+---
+
+Use [`isTruthy`](/docs#isTruthy) as the predicate.
+
+```ts
+// Lodash
+compact(DATA);
+
+// Remeda
+filter(DATA, isTruthy);
+```
diff --git a/docs/src/content/mapping/lodash/concat.md b/docs/src/content/mapping/lodash/concat.md
new file mode 100644
index 0000000..7a9e96a
--- /dev/null
+++ b/docs/src/content/mapping/lodash/concat.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: concat
+---
diff --git a/docs/src/content/mapping/lodash/cond.md b/docs/src/content/mapping/lodash/cond.md
new file mode 100644
index 0000000..63b848a
--- /dev/null
+++ b/docs/src/content/mapping/lodash/cond.md
@@ -0,0 +1,4 @@
+---
+category: Util
+remeda: conditional
+---
diff --git a/docs/src/content/mapping/lodash/constant.md b/docs/src/content/mapping/lodash/constant.md
new file mode 100644
index 0000000..ca3feee
--- /dev/null
+++ b/docs/src/content/mapping/lodash/constant.md
@@ -0,0 +1,4 @@
+---
+category: Util
+remeda: constant
+---
diff --git a/docs/src/content/mapping/lodash/countBy.md b/docs/src/content/mapping/lodash/countBy.md
new file mode 100644
index 0000000..0025eb1
--- /dev/null
+++ b/docs/src/content/mapping/lodash/countBy.md
@@ -0,0 +1,21 @@
+---
+category: Collection
+---
+
+_Not provided by Remeda._
+
+This function could be recreated in Remeda via a composition of [`groupBy`](/docs#groupBy),
+[`mapValues`](/docs#mapValues), and [`length`](/docs#length). We acknowledge
+that this is less efficient than a singular implementation; if you need this
+function please open an issue at the Remeda GitHub project to get it added.
+
+```ts
+// Lodash
+countBy(DATA, groupingFunction);
+
+// Remeda
+mapValues(groupBy(DATA, groupingFunction), length());
+
+// Or as a pipe
+pipe(DATA, groupBy(groupingFunction), mapValues(length()));
+```
diff --git a/docs/src/content/mapping/lodash/debounce.md b/docs/src/content/mapping/lodash/debounce.md
new file mode 100644
index 0000000..8cc8d6b
--- /dev/null
+++ b/docs/src/content/mapping/lodash/debounce.md
@@ -0,0 +1,4 @@
+---
+category: Function
+remeda: debounce
+---
diff --git a/docs/src/content/mapping/lodash/difference.md b/docs/src/content/mapping/lodash/difference.md
new file mode 100644
index 0000000..e4a7b4e
--- /dev/null
+++ b/docs/src/content/mapping/lodash/difference.md
@@ -0,0 +1,30 @@
+---
+category: Array
+remeda: difference
+---
+
+In Remeda `difference` treats the inputs as multisets/bags which respect
+**item duplication**, whereas in Lodash all matching values are filtered out.
+This only matters for cases where _both_ arrays might have duplicate values. Use
+[`filter`](/docs#filter), [`isNot`](/docs#isNot), and
+[`isIncludedIn`](/docs#isIncludedIn) instead to compose the same logic.
+
+### No duplicates
+
+```ts
+// Lodash
+difference([2, 1], [2, 3]);
+
+// Remeda
+difference([2, 1], [2, 3]);
+```
+
+### With duplicates
+
+```ts
+// Lodash
+difference([1, 1, 2, 2], [1]);
+
+// Remeda
+filter([1, 1, 2, 2], isNot(isIncludedIn([1])));
+```
diff --git a/docs/src/content/mapping/lodash/differenceWith.md b/docs/src/content/mapping/lodash/differenceWith.md
new file mode 100644
index 0000000..7b541f3
--- /dev/null
+++ b/docs/src/content/mapping/lodash/differenceWith.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: differenceWith
+---
diff --git a/docs/src/content/mapping/lodash/divide.md b/docs/src/content/mapping/lodash/divide.md
new file mode 100644
index 0000000..eb87f1d
--- /dev/null
+++ b/docs/src/content/mapping/lodash/divide.md
@@ -0,0 +1,4 @@
+---
+category: Math
+remeda: divide
+---
diff --git a/docs/src/content/mapping/lodash/drop.md b/docs/src/content/mapping/lodash/drop.md
new file mode 100644
index 0000000..fafa5f5
--- /dev/null
+++ b/docs/src/content/mapping/lodash/drop.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: drop
+---
diff --git a/docs/src/content/mapping/lodash/dropRight.md b/docs/src/content/mapping/lodash/dropRight.md
new file mode 100644
index 0000000..87f8430
--- /dev/null
+++ b/docs/src/content/mapping/lodash/dropRight.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: dropLast
+---
diff --git a/docs/src/content/mapping/lodash/dropRightWhile.md b/docs/src/content/mapping/lodash/dropRightWhile.md
new file mode 100644
index 0000000..b2f1220
--- /dev/null
+++ b/docs/src/content/mapping/lodash/dropRightWhile.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: dropLastWhile
+---
diff --git a/docs/src/content/mapping/lodash/dropWhile.md b/docs/src/content/mapping/lodash/dropWhile.md
new file mode 100644
index 0000000..c946113
--- /dev/null
+++ b/docs/src/content/mapping/lodash/dropWhile.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: dropWhile
+---
diff --git a/docs/src/content/mapping/lodash/every.md b/docs/src/content/mapping/lodash/every.md
new file mode 100644
index 0000000..c9019ca
--- /dev/null
+++ b/docs/src/content/mapping/lodash/every.md
@@ -0,0 +1,17 @@
+---
+category: Collection
+---
+
+_Not provided by Remeda._
+
+Use the native JS [`Array.prototype.every`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) instead.
+
+_**@see** ["You don't need Lodash"](https://you-dont-need.github.io/You-Dont-Need-Lodash-Underscore/#/?id=_every)_
+
+```ts
+// Lodash
+every(DATA, predicate);
+
+// Native
+DATA.every(predicate);
+```
diff --git a/docs/src/content/mapping/lodash/filter.md b/docs/src/content/mapping/lodash/filter.md
new file mode 100644
index 0000000..48b0981
--- /dev/null
+++ b/docs/src/content/mapping/lodash/filter.md
@@ -0,0 +1,4 @@
+---
+category: Collection
+remeda: filter
+---
diff --git a/docs/src/content/mapping/lodash/find.md b/docs/src/content/mapping/lodash/find.md
new file mode 100644
index 0000000..b2d4415
--- /dev/null
+++ b/docs/src/content/mapping/lodash/find.md
@@ -0,0 +1,4 @@
+---
+category: Collection
+remeda: find
+---
diff --git a/docs/src/content/mapping/lodash/findIndex.md b/docs/src/content/mapping/lodash/findIndex.md
new file mode 100644
index 0000000..0c803d2
--- /dev/null
+++ b/docs/src/content/mapping/lodash/findIndex.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: findIndex
+---
diff --git a/docs/src/content/mapping/lodash/findLast.md b/docs/src/content/mapping/lodash/findLast.md
new file mode 100644
index 0000000..ee8ee46
--- /dev/null
+++ b/docs/src/content/mapping/lodash/findLast.md
@@ -0,0 +1,4 @@
+---
+category: Collection
+remeda: findLast
+---
diff --git a/docs/src/content/mapping/lodash/findLastIndex.md b/docs/src/content/mapping/lodash/findLastIndex.md
new file mode 100644
index 0000000..70bdbd8
--- /dev/null
+++ b/docs/src/content/mapping/lodash/findLastIndex.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: findLastIndex
+---
diff --git a/docs/src/content/mapping/lodash/flatMap.md b/docs/src/content/mapping/lodash/flatMap.md
new file mode 100644
index 0000000..2b80d33
--- /dev/null
+++ b/docs/src/content/mapping/lodash/flatMap.md
@@ -0,0 +1,4 @@
+---
+category: Collection
+remeda: flatMap
+---
diff --git a/docs/src/content/mapping/lodash/flatMapDeep.md b/docs/src/content/mapping/lodash/flatMapDeep.md
new file mode 100644
index 0000000..a1462f0
--- /dev/null
+++ b/docs/src/content/mapping/lodash/flatMapDeep.md
@@ -0,0 +1,20 @@
+---
+category: Collection
+---
+
+_Not provided by Remeda._
+
+This function could be recreated in Remeda via a composition of
+[`map`](/docs#map), and [`flat`](/docs#flat) with a **large, const** depth
+parameter.
+
+```ts
+// Lodash
+flatMapDeep(DATA, mappingFunction);
+
+// Remeda
+flat(map(DATA, mappingFunction), 10 /* depth */);
+
+// Or as a pipe
+pipe(DATA, map(mappingFunction), flat(10 /* depth */));
+```
diff --git a/docs/src/content/mapping/lodash/flatMapDepth.md b/docs/src/content/mapping/lodash/flatMapDepth.md
new file mode 100644
index 0000000..dadecc9
--- /dev/null
+++ b/docs/src/content/mapping/lodash/flatMapDepth.md
@@ -0,0 +1,36 @@
+---
+category: Collection
+---
+
+_Not provided by Remeda._
+
+This function could be recreated in Remeda via [`map`](/docs#map),
+[`flatMap`](/docs#flatMap), or [`flat`](/docs#flat) (or a combination of them,
+depending on the parameters used).
+
+```ts
+// Lodash
+flatMapDepth(DATA);
+
+// Remeda
+flat(DATA);
+```
+
+```ts
+// Lodash
+flatMapDepth(DATA, mappingFunction);
+
+// Remeda
+flatMap(DATA, mappingFunction);
+```
+
+```ts
+// Lodash
+flatMapDepth(DATA, mappingFunction, depth);
+
+// Remeda
+flat(map(DATA, mappingFunction), depth);
+
+// Or as a pipe
+pipe(DATA, map(mappingFunction), flat(depth));
+```
diff --git a/docs/src/content/mapping/lodash/flatten.md b/docs/src/content/mapping/lodash/flatten.md
new file mode 100644
index 0000000..a8fd233
--- /dev/null
+++ b/docs/src/content/mapping/lodash/flatten.md
@@ -0,0 +1,15 @@
+---
+category: Array
+remeda: flat
+---
+
+Either use with no params, or with a depth of **1**.
+
+```ts
+// Lodash
+flatten(DATA);
+
+// Remeda
+flat(DATA);
+flat(DATA, 1 /* depth */);
+```
diff --git a/docs/src/content/mapping/lodash/flattenDeep.md b/docs/src/content/mapping/lodash/flattenDeep.md
new file mode 100644
index 0000000..241fee6
--- /dev/null
+++ b/docs/src/content/mapping/lodash/flattenDeep.md
@@ -0,0 +1,18 @@
+---
+category: Array
+remeda: flat
+---
+
+Unlike `flatten` in Lodash, the Remeda `flat` function is always bound by the
+`depth` param. To replicate the Lodash behavior use a high, **const** value.
+`Infinity` and `Number.MAX_INTEGER` are not consts and would result in
+inefficient typing.
+
+```ts
+// Lodash
+flattenDeep(DATA);
+
+// Remeda
+flat(DATA, 100); // ✅
+flat(DATA, Infinity); // ❌
+```
diff --git a/docs/src/content/mapping/lodash/floor.md b/docs/src/content/mapping/lodash/floor.md
new file mode 100644
index 0000000..79f53fb
--- /dev/null
+++ b/docs/src/content/mapping/lodash/floor.md
@@ -0,0 +1,4 @@
+---
+category: Math
+remeda: floor
+---
diff --git a/docs/src/content/mapping/lodash/flow.md b/docs/src/content/mapping/lodash/flow.md
new file mode 100644
index 0000000..49d6f51
--- /dev/null
+++ b/docs/src/content/mapping/lodash/flow.md
@@ -0,0 +1,4 @@
+---
+category: Util
+remeda: pipe
+---
diff --git a/docs/src/content/mapping/lodash/forEach.md b/docs/src/content/mapping/lodash/forEach.md
new file mode 100644
index 0000000..c7b8108
--- /dev/null
+++ b/docs/src/content/mapping/lodash/forEach.md
@@ -0,0 +1,57 @@
+---
+category: Collection
+remeda: forEach
+---
+
+- In Lodash `forEach` first checks if the data argument is array-like (by
+  looking for a `length` prop). In Remeda the function only accepts arrays and
+  calling it with an object would result in a TypeScript error. Use
+  [`forEachObj`](/docs#forEachObj) instead for those cases.
+- In Lodash `forEach` stops iterating early when the callback returns `false`.
+  In Remeda the callback cannot return a value (it must return `void`) and this
+  feature isn't available. You can build a [`pipe`](/docs#pipe) that would
+  replicate this logic.
+
+### Array
+
+```ts
+// Lodash
+forEach([1, 2, 3], (item) => console.log(item));
+
+// Remeda
+forEach([1, 2, 3], (item) => {
+  console.log(item);
+});
+```
+
+### Object
+
+```ts
+// Lodash
+forEach({ a: 1, b: 2, c: 3 }, (value) => console.log(value));
+
+// Remeda
+forEachObj({ a: 1, b: 2, c: 3 }, (value) => {
+  console.log(value);
+});
+```
+
+### Early exit
+
+```ts
+// Lodash
+forEach([1, 2, 3], (item) => (item === 2 ? false : console.log(item)));
+
+// Remeda
+// ❌ You can't return a value in a forEach callback in Remeda:
+forEach([1, 2, 3], (item) => (item === 2 ? false : console.log(item)));
+
+// ✅ Instead you can rely on lazy evaluation of pipes:
+pipe(
+  [1, 2, 3],
+  takeWhile((item) => item !== 2),
+  forEach((item) => {
+    console.log(item);
+  }),
+);
+```
diff --git a/docs/src/content/mapping/lodash/forEachRight.md b/docs/src/content/mapping/lodash/forEachRight.md
new file mode 100644
index 0000000..4b2c1ad
--- /dev/null
+++ b/docs/src/content/mapping/lodash/forEachRight.md
@@ -0,0 +1,54 @@
+---
+category: Collection
+---
+
+_Not provided by Remeda._
+
+- This function could be replicated using a native JS `for` loop.
+- If the _order_ of iteration doesn't matter, you can use
+  [`forEach`](/docs#foreach) for arrays, or [`forEachObj`](/docs#forEachObj) for
+  objects.
+- The order in which object properties are iterated over is well-defined, but
+  might not be the order you expect ([MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in#description)); nevertheless, [`entries`](/docs#entries) maintains the same
+  order, and could be used to replicate `forEachRight`.
+- If the native solution doesn't suffice please open an issue at Remeda's
+  GitHub project so we can learn about your use-case.
+
+### Arrays
+
+```ts
+const DATA = [1, 2, 3];
+
+// Lodash
+forEachRight(DATA, (item) => (item === 2 ? false : console.log(item)));
+
+// Native
+for (const index = DATA.length - 1; index >= 0; index--) {
+  const item = DATA[item]!;
+  if (item === 2) {
+    break;
+  }
+
+  console.log(item);
+}
+```
+
+### Objects
+
+```ts
+const DATA = { a: 1, b: 2, c: 3 };
+
+// Lodash
+forEachRight(DATA, (value) => (value === 2 ? false : console.log(value)));
+
+// Native
+const keys = Object.keys(DATA);
+for (const index = keys.length - 1; index >= 0; index--) {
+  const value = DATA[key]!;
+  if (value === 2) {
+    break;
+  }
+
+  console.log(value);
+}
+```
diff --git a/docs/src/content/mapping/lodash/forOwn.md b/docs/src/content/mapping/lodash/forOwn.md
new file mode 100644
index 0000000..62fb58e
--- /dev/null
+++ b/docs/src/content/mapping/lodash/forOwn.md
@@ -0,0 +1,4 @@
+---
+category: Object
+remeda: forEachObj
+---
diff --git a/docs/src/content/mapping/lodash/fromPairs.md b/docs/src/content/mapping/lodash/fromPairs.md
new file mode 100644
index 0000000..af33394
--- /dev/null
+++ b/docs/src/content/mapping/lodash/fromPairs.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: fromEntries
+---
diff --git a/docs/src/content/mapping/lodash/get.md b/docs/src/content/mapping/lodash/get.md
new file mode 100644
index 0000000..0383cd0
--- /dev/null
+++ b/docs/src/content/mapping/lodash/get.md
@@ -0,0 +1,28 @@
+---
+category: Object
+remeda: pathOr
+---
+
+- In Lodash the `get` function supports two ways of defining the path parameter:
+  a string representation of the path (similar to XPath: e.g. `a.b[0].c`), and
+  an array representation of the path (e.g. `['a', 'b', 0, 'c']`). In Remeda
+  only the array representation is accepted. Use the helper function
+  [`stringToPath`](/docs#stringToPath) to translate string paths to array paths.
+- In order to provide good types, the Remeda `pathOr` function is limited to
+  paths of length 3 or less. Longer paths are not supported.
+- [`prop`](/docs#prop) is a simplified version of `pathOr` for paths of length
+  **1** and `undefined` as the default (fallback) value.
+
+```ts
+const DATA = { a: [{ b: 123 }] };
+
+// Lodash
+get(DATA, "a");
+get(DATA, ["a", 0, "b"], 456);
+get(DATA, "a[0].b", 456);
+
+// Remeda
+prop(DATA, "a");
+pathOr(DATA, ["a", 0, "b"], 456);
+pathOr(DATA, stringToPath("a[0].b"), 456);
+```
diff --git a/docs/src/content/mapping/lodash/groupBy.md b/docs/src/content/mapping/lodash/groupBy.md
new file mode 100644
index 0000000..646b181
--- /dev/null
+++ b/docs/src/content/mapping/lodash/groupBy.md
@@ -0,0 +1,4 @@
+---
+category: Collection
+remeda: groupBy
+---
diff --git a/docs/src/content/mapping/lodash/head.md b/docs/src/content/mapping/lodash/head.md
new file mode 100644
index 0000000..c55961f
--- /dev/null
+++ b/docs/src/content/mapping/lodash/head.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: first
+---
diff --git a/docs/src/content/mapping/lodash/identity.md b/docs/src/content/mapping/lodash/identity.md
new file mode 100644
index 0000000..0be4a63
--- /dev/null
+++ b/docs/src/content/mapping/lodash/identity.md
@@ -0,0 +1,15 @@
+---
+category: Util
+remeda: identity
+---
+
+In Remeda the `identity` function is a factory that creates an _identity
+function_; it needs to be called to be used.
+
+```ts
+// Lodash
+map(identity);
+
+// Remeda
+map(identity());
+```
diff --git a/docs/src/content/mapping/lodash/includes.md b/docs/src/content/mapping/lodash/includes.md
new file mode 100644
index 0000000..d98d3e3
--- /dev/null
+++ b/docs/src/content/mapping/lodash/includes.md
@@ -0,0 +1,66 @@
+---
+category: Collection
+remeda: isIncludedIn
+---
+
+- In Lodash `includes` also works on strings and objects; In Remeda only arrays
+  are supported. For objects, use [`values`](/docs#values) first. For strings
+  prefer the native JS [`String.prototype.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes).
+- The 3rd optional parameter `fromIndex` isn't supported in Remeda. You can
+  replicate it's usage with [`drop`](/docs#drop) (for non-negative indices), or
+  [`takeLast`](/docs#takeLast) (for non-positive indices).
+- The order of the parameters is flipped in Remeda, the item is first, and then
+  the array.
+
+### Arrays
+
+```ts
+// Lodash
+includes([1, 2, 3], 1);
+
+// Remeda
+isIncludedIn(1, [1, 2, 3]);
+```
+
+### Objects
+
+```ts
+// Lodash
+includes({ a: 1, b: 2 }, 1);
+
+// Remeda
+isIncludedIn(1, values({ a: 1, b: 2 }));
+```
+
+### Strings
+
+```ts
+// Lodash
+const DATA = "Hello, World!";
+
+// Lodash
+includes(DATA, "lo");
+
+// Native
+DATA.includes("lo");
+```
+
+### fromIndex: non-negative
+
+```ts
+// Lodash
+includes([1, 2, 3], 1, 2);
+
+// Remeda
+isIncludedIn(1, drop([1, 2, 3], 2));
+```
+
+### fromIndex: non-positive
+
+```ts
+// Lodash
+includes([1, 2, 3], 1, -2);
+
+// Remeda
+isIncludedIn(1, takeLast([1, 2, 3], 2));
+```
diff --git a/docs/src/content/mapping/lodash/intersection.md b/docs/src/content/mapping/lodash/intersection.md
new file mode 100644
index 0000000..c7a6edd
--- /dev/null
+++ b/docs/src/content/mapping/lodash/intersection.md
@@ -0,0 +1,29 @@
+---
+category: Array
+remeda: intersection
+---
+
+In Remeda `intersection` treats the inputs as multisets/bags which respect
+**item duplication**, whereas in Lodash the result is always de-dupped. This only
+matters for cases where _both_ arrays might have duplicate values. To achieve
+the same output pass the result to [`unique`](/docs#unique).
+
+### No duplicates
+
+```ts
+// Lodash
+intersection([2, 1], [2, 3]);
+
+// Remeda
+intersection([2, 1], [2, 3]);
+```
+
+### With duplicates
+
+```ts
+// Lodash
+intersection([1, 1], [1, 1]);
+
+// Remeda
+unique(intersection([1, 1], [1, 1]));
+```
diff --git a/docs/src/content/mapping/lodash/intersectionWith.md b/docs/src/content/mapping/lodash/intersectionWith.md
new file mode 100644
index 0000000..62a0549
--- /dev/null
+++ b/docs/src/content/mapping/lodash/intersectionWith.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: intersectionWith
+---
diff --git a/docs/src/content/mapping/lodash/invert.md b/docs/src/content/mapping/lodash/invert.md
new file mode 100644
index 0000000..9421615
--- /dev/null
+++ b/docs/src/content/mapping/lodash/invert.md
@@ -0,0 +1,4 @@
+---
+category: Object
+remeda: invert
+---
diff --git a/docs/src/content/mapping/lodash/invokeMap.md b/docs/src/content/mapping/lodash/invokeMap.md
new file mode 100644
index 0000000..042a0f4
--- /dev/null
+++ b/docs/src/content/mapping/lodash/invokeMap.md
@@ -0,0 +1,70 @@
+---
+category: Collection
+remeda: map
+---
+
+- `map` can be used instead of `invokeMap` by using a callback functions instead
+  of (string) function _names_.
+- If you need to invoke functions dynamically via a provided function name,
+  narrow the name first (so you can handle errors explicitly), and then use the
+  name to access the object.
+
+### No arguments
+
+```ts
+// Lodash
+invokeMap(
+  [
+    [5, 1, 7],
+    [3, 2, 1],
+  ],
+  "sort",
+);
+
+// Remeda
+map(
+  [
+    [5, 1, 7],
+    [3, 2, 1],
+  ],
+  (array) => array.sort(),
+);
+```
+
+### With arguments
+
+```ts
+// Lodash
+invokeMap([123, 456], String.prototype.split, "");
+
+// Remeda
+map([123, 456], (num) => num.toString().split(""));
+```
+
+### Dynamic
+
+```ts
+const DATA = [
+  { foo: (x: number) => x + 1, bar: (x: number) => x - 1 },
+  { foo: (x: number) => x * 2, bar: (x: number) => x / 2 },
+] as const;
+
+// Lodash
+invokeMap(DATA, funcName, 3);
+
+// Remeda
+if (funcName === "foo" || funcName === "bar") {
+  map(DATA, ({ [funcName]: func }) => func(3));
+} else {
+  // Error!
+}
+
+// Or
+map(DATA, (obj) => {
+  if (funcName === "foo" || funcName === "bar") {
+    return obj[funcName](3);
+  } else {
+    // Error
+  }
+});
+```
diff --git a/docs/src/content/mapping/lodash/isEmpty.md b/docs/src/content/mapping/lodash/isEmpty.md
new file mode 100644
index 0000000..67aedbc
--- /dev/null
+++ b/docs/src/content/mapping/lodash/isEmpty.md
@@ -0,0 +1,4 @@
+---
+category: Lang
+remeda: isEmpty
+---
diff --git a/docs/src/content/mapping/lodash/isEqual.md b/docs/src/content/mapping/lodash/isEqual.md
new file mode 100644
index 0000000..25737c5
--- /dev/null
+++ b/docs/src/content/mapping/lodash/isEqual.md
@@ -0,0 +1,4 @@
+---
+category: Lang
+remeda: isDeepEqual
+---
diff --git a/docs/src/content/mapping/lodash/isMatch.md b/docs/src/content/mapping/lodash/isMatch.md
new file mode 100644
index 0000000..2379473
--- /dev/null
+++ b/docs/src/content/mapping/lodash/isMatch.md
@@ -0,0 +1,4 @@
+---
+category: Lang
+remeda: hasSubObject
+---
diff --git a/docs/src/content/mapping/lodash/isNil.md b/docs/src/content/mapping/lodash/isNil.md
new file mode 100644
index 0000000..cdba143
--- /dev/null
+++ b/docs/src/content/mapping/lodash/isNil.md
@@ -0,0 +1,4 @@
+---
+category: Lang
+remeda: isNullish
+---
diff --git a/docs/src/content/mapping/lodash/isObjectLike.md b/docs/src/content/mapping/lodash/isObjectLike.md
new file mode 100644
index 0000000..3f119c0
--- /dev/null
+++ b/docs/src/content/mapping/lodash/isObjectLike.md
@@ -0,0 +1,4 @@
+---
+category: Lang
+remeda: isObjectType
+---
diff --git a/docs/src/content/mapping/lodash/isPlainObject.md b/docs/src/content/mapping/lodash/isPlainObject.md
new file mode 100644
index 0000000..269433a
--- /dev/null
+++ b/docs/src/content/mapping/lodash/isPlainObject.md
@@ -0,0 +1,4 @@
+---
+category: Lang
+remeda: isPlainObject
+---
diff --git a/docs/src/content/mapping/lodash/isSymbol.md b/docs/src/content/mapping/lodash/isSymbol.md
new file mode 100644
index 0000000..0d1d806
--- /dev/null
+++ b/docs/src/content/mapping/lodash/isSymbol.md
@@ -0,0 +1,4 @@
+---
+category: Lang
+remeda: isSymbol
+---
diff --git a/docs/src/content/mapping/lodash/join.md b/docs/src/content/mapping/lodash/join.md
new file mode 100644
index 0000000..fa90d2c
--- /dev/null
+++ b/docs/src/content/mapping/lodash/join.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: join
+---
diff --git a/docs/src/content/mapping/lodash/keyBy.md b/docs/src/content/mapping/lodash/keyBy.md
new file mode 100644
index 0000000..1252775
--- /dev/null
+++ b/docs/src/content/mapping/lodash/keyBy.md
@@ -0,0 +1,4 @@
+---
+category: Collection
+remeda: indexBy
+---
diff --git a/docs/src/content/mapping/lodash/keys.md b/docs/src/content/mapping/lodash/keys.md
new file mode 100644
index 0000000..6cc45a0
--- /dev/null
+++ b/docs/src/content/mapping/lodash/keys.md
@@ -0,0 +1,4 @@
+---
+category: Object
+remeda: keys
+---
diff --git a/docs/src/content/mapping/lodash/last.md b/docs/src/content/mapping/lodash/last.md
new file mode 100644
index 0000000..8d05a71
--- /dev/null
+++ b/docs/src/content/mapping/lodash/last.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: last
+---
diff --git a/docs/src/content/mapping/lodash/map.md b/docs/src/content/mapping/lodash/map.md
new file mode 100644
index 0000000..abbeb8f
--- /dev/null
+++ b/docs/src/content/mapping/lodash/map.md
@@ -0,0 +1,4 @@
+---
+category: Collection
+remeda: map
+---
diff --git a/docs/src/content/mapping/lodash/mapKeys.md b/docs/src/content/mapping/lodash/mapKeys.md
new file mode 100644
index 0000000..ebb0e25
--- /dev/null
+++ b/docs/src/content/mapping/lodash/mapKeys.md
@@ -0,0 +1,4 @@
+---
+category: Object
+remeda: mapKeys
+---
diff --git a/docs/src/content/mapping/lodash/mapValues.md b/docs/src/content/mapping/lodash/mapValues.md
new file mode 100644
index 0000000..442e473
--- /dev/null
+++ b/docs/src/content/mapping/lodash/mapValues.md
@@ -0,0 +1,4 @@
+---
+category: Object
+remeda: mapValues
+---
diff --git a/docs/src/content/mapping/lodash/max.md b/docs/src/content/mapping/lodash/max.md
new file mode 100644
index 0000000..29556ef
--- /dev/null
+++ b/docs/src/content/mapping/lodash/max.md
@@ -0,0 +1,14 @@
+---
+category: Math
+remeda: firstBy
+---
+
+Use [`identity`](/docs#identity) as the criteria and `"desc"` as the direction.
+
+```ts
+// Lodash
+max(DATA);
+
+// Remeda
+firstBy(DATA, [identity(), "desc"]);
+```
diff --git a/docs/src/content/mapping/lodash/maxBy.md b/docs/src/content/mapping/lodash/maxBy.md
new file mode 100644
index 0000000..316622a
--- /dev/null
+++ b/docs/src/content/mapping/lodash/maxBy.md
@@ -0,0 +1,14 @@
+---
+category: Math
+remeda: firstBy
+---
+
+Use `"desc"` as the direction.
+
+```ts
+// Lodash
+maxBy(DATA, criteria);
+
+// Remeda
+firstBy(DATA, [criteria, "desc"]);
+```
diff --git a/docs/src/content/mapping/lodash/meanBy.md b/docs/src/content/mapping/lodash/meanBy.md
new file mode 100644
index 0000000..472f164
--- /dev/null
+++ b/docs/src/content/mapping/lodash/meanBy.md
@@ -0,0 +1,4 @@
+---
+category: Math
+remeda: meanBy
+---
diff --git a/docs/src/content/mapping/lodash/min.md b/docs/src/content/mapping/lodash/min.md
new file mode 100644
index 0000000..acfa2a2
--- /dev/null
+++ b/docs/src/content/mapping/lodash/min.md
@@ -0,0 +1,14 @@
+---
+category: Math
+remeda: firstBy
+---
+
+Use [`identity`](/docs#identity) as the criteria.
+
+```ts
+// Lodash
+min(DATA);
+
+// Remeda
+firstBy(DATA, identity());
+```
diff --git a/docs/src/content/mapping/lodash/minBy.md b/docs/src/content/mapping/lodash/minBy.md
new file mode 100644
index 0000000..f7dd0b2
--- /dev/null
+++ b/docs/src/content/mapping/lodash/minBy.md
@@ -0,0 +1,4 @@
+---
+category: Math
+remeda: firstBy
+---
diff --git a/docs/src/content/mapping/lodash/multiply.md b/docs/src/content/mapping/lodash/multiply.md
new file mode 100644
index 0000000..dfea7e6
--- /dev/null
+++ b/docs/src/content/mapping/lodash/multiply.md
@@ -0,0 +1,4 @@
+---
+category: Math
+remeda: multiply
+---
diff --git a/docs/src/content/mapping/lodash/noop.md b/docs/src/content/mapping/lodash/noop.md
new file mode 100644
index 0000000..2587360
--- /dev/null
+++ b/docs/src/content/mapping/lodash/noop.md
@@ -0,0 +1,17 @@
+---
+category: Util
+remeda: doNothing
+---
+
+- If a return value is needed, use [`constant`](/docs#constant) with
+  `undefined`.
+- Otherwise, use `doNothing` (which returns `void`).
+
+```ts
+// Lodash
+noop;
+
+// Remeda
+constant(undefined);
+doNothing();
+```
diff --git a/docs/src/content/mapping/lodash/nth.md b/docs/src/content/mapping/lodash/nth.md
new file mode 100644
index 0000000..ffc11de
--- /dev/null
+++ b/docs/src/content/mapping/lodash/nth.md
@@ -0,0 +1,38 @@
+---
+category: Array
+---
+
+_Not provided by Remeda._
+
+- For index `0` use [`first`](/docs#first).
+- For index `-1` use [`last`](/docs#last).
+- For arbitrary _non-negative_ indices use the native JS `data[n]`.
+- Or use [`Array.prototype.at`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at) for any index.
+
+```ts
+// Lodash
+nth(DATA);
+nth(DATA, 0);
+
+// Remeda
+first(DATA);
+
+// Lodash
+nth(DATA, 1);
+
+// Native
+DATA[1];
+DATA.at(1);
+
+// Lodash
+nth(DATA, -1);
+
+// Remeda
+last(DATA);
+
+// Lodash
+nth(DATA, -2);
+
+// Native
+DATA.at(-2);
+```
diff --git a/docs/src/content/mapping/lodash/omit.md b/docs/src/content/mapping/lodash/omit.md
new file mode 100644
index 0000000..0158a36
--- /dev/null
+++ b/docs/src/content/mapping/lodash/omit.md
@@ -0,0 +1,4 @@
+---
+category: Object
+remeda: omit
+---
diff --git a/docs/src/content/mapping/lodash/omitBy.md b/docs/src/content/mapping/lodash/omitBy.md
new file mode 100644
index 0000000..df7f53a
--- /dev/null
+++ b/docs/src/content/mapping/lodash/omitBy.md
@@ -0,0 +1,4 @@
+---
+category: Object
+remeda: omitBy
+---
diff --git a/docs/src/content/mapping/lodash/once.md b/docs/src/content/mapping/lodash/once.md
new file mode 100644
index 0000000..42168dc
--- /dev/null
+++ b/docs/src/content/mapping/lodash/once.md
@@ -0,0 +1,4 @@
+---
+category: Function
+remeda: once
+---
diff --git a/docs/src/content/mapping/lodash/orderBy.md b/docs/src/content/mapping/lodash/orderBy.md
new file mode 100644
index 0000000..61b9b73
--- /dev/null
+++ b/docs/src/content/mapping/lodash/orderBy.md
@@ -0,0 +1,23 @@
+---
+category: Collection
+remeda: sortBy
+---
+
+Unlike in Lodash, in Remeda the sort criteria can only be a callback (and not a
+prop name), it is paired with the order modifier ("asc"/"desc", if needed), and
+it's provided as a variadic argument and not two array arguments.
+
+```ts
+const DATA = [
+  { user: "fred", age: 48 },
+  { user: "barney", age: 34 },
+  { user: "fred", age: 40 },
+  { user: "barney", age: 36 },
+];
+
+// Lodash
+orderBy(DATA, ["user", "age"], ["asc", "desc"]);
+
+// Remeda
+sortBy(DATA, prop("user"), [prop("age"), "desc"]);
+```
diff --git a/docs/src/content/mapping/lodash/partition.md b/docs/src/content/mapping/lodash/partition.md
new file mode 100644
index 0000000..edfd895
--- /dev/null
+++ b/docs/src/content/mapping/lodash/partition.md
@@ -0,0 +1,4 @@
+---
+category: Collection
+remeda: partition
+---
diff --git a/docs/src/content/mapping/lodash/pick.md b/docs/src/content/mapping/lodash/pick.md
new file mode 100644
index 0000000..a6f2e5b
--- /dev/null
+++ b/docs/src/content/mapping/lodash/pick.md
@@ -0,0 +1,4 @@
+---
+category: Object
+remeda: pick
+---
diff --git a/docs/src/content/mapping/lodash/pickBy.md b/docs/src/content/mapping/lodash/pickBy.md
new file mode 100644
index 0000000..0ab0cd1
--- /dev/null
+++ b/docs/src/content/mapping/lodash/pickBy.md
@@ -0,0 +1,4 @@
+---
+category: Object
+remeda: pickBy
+---
diff --git a/docs/src/content/mapping/lodash/range.md b/docs/src/content/mapping/lodash/range.md
new file mode 100644
index 0000000..2883ec3
--- /dev/null
+++ b/docs/src/content/mapping/lodash/range.md
@@ -0,0 +1,4 @@
+---
+category: Util
+remeda: range
+---
diff --git a/docs/src/content/mapping/lodash/reduce.md b/docs/src/content/mapping/lodash/reduce.md
new file mode 100644
index 0000000..478adca
--- /dev/null
+++ b/docs/src/content/mapping/lodash/reduce.md
@@ -0,0 +1,4 @@
+---
+category: Collection
+remeda: reduce
+---
diff --git a/docs/src/content/mapping/lodash/reduceRight.md b/docs/src/content/mapping/lodash/reduceRight.md
new file mode 100644
index 0000000..6dad45d
--- /dev/null
+++ b/docs/src/content/mapping/lodash/reduceRight.md
@@ -0,0 +1,17 @@
+---
+category: Collection
+remeda: reduce
+---
+
+Use [`reverse`](/docs#reverse) on the input.
+
+```ts
+// Lodash
+reduceRight(DATA, reducer, accumulator);
+
+// Remeda
+reduce(reverse(DATA), reducer, accumulator);
+
+// Or with a pipe
+pipe(DATA, reverse(), reduce(reducer, accumulator));
+```
diff --git a/docs/src/content/mapping/lodash/reject.md b/docs/src/content/mapping/lodash/reject.md
new file mode 100644
index 0000000..d9c1322
--- /dev/null
+++ b/docs/src/content/mapping/lodash/reject.md
@@ -0,0 +1,14 @@
+---
+category: Collection
+remeda: filter
+---
+
+Wrap the callback with [`isNot`](/docs#isNot).
+
+```ts
+// Lodash
+reject(DATA, predicate);
+
+// Remeda
+filter(DATA, isNot(predicate));
+```
diff --git a/docs/src/content/mapping/lodash/reverse.md b/docs/src/content/mapping/lodash/reverse.md
new file mode 100644
index 0000000..89e380d
--- /dev/null
+++ b/docs/src/content/mapping/lodash/reverse.md
@@ -0,0 +1,16 @@
+---
+category: Array
+remeda: reverse
+---
+
+Lodash mutates the input but in Remeda a new array is returned instead.
+
+```ts
+// Lodash
+const DATA = [1, 2, 3];
+reverse(DATA);
+
+// Remeda
+let DATA = [1, 2, 3];
+DATA = reverse(DATA);
+```
diff --git a/docs/src/content/mapping/lodash/round.md b/docs/src/content/mapping/lodash/round.md
new file mode 100644
index 0000000..98123e4
--- /dev/null
+++ b/docs/src/content/mapping/lodash/round.md
@@ -0,0 +1,4 @@
+---
+category: Math
+remeda: round
+---
diff --git a/docs/src/content/mapping/lodash/sample.md b/docs/src/content/mapping/lodash/sample.md
new file mode 100644
index 0000000..af0b93e
--- /dev/null
+++ b/docs/src/content/mapping/lodash/sample.md
@@ -0,0 +1,41 @@
+---
+category: Collection
+remeda: sample
+---
+
+- The `sample` function in Remeda takes a required second parameter of the size
+  of the sample. Use **1**.
+- The Remeda function returns an array; to extract the value either use
+  native destructuring or index access, or use [`only`](/docs#only).
+- For objects, first call [`values`](/docs#values).
+- If you are using `sample` in an extremely hot path where you need the most
+  efficient implementation possible, prefer a native implementation instead.
+
+### Simple
+
+```ts
+// Lodash
+sample(DATA);
+
+// Remeda
+only(sample(DATA, 1 /* size */));
+
+// or with a pipe
+pipe(DATA, sample(1 /* size */), only());
+
+// Or with native accessors
+sample(DATA, 1 /* size */)[0];
+sample(DATA, 1 /* size */).at(0)!;
+const [result] = sample(DATA, 1 /* size */);
+```
+
+### Native
+
+```ts
+// Lodash
+sample(DATA);
+
+// Native
+DATA[Math.floor(Math.random() * DATA.length)]!;
+DATA.at(Math.floor(Math.random() * DATA.length))!;
+```
diff --git a/docs/src/content/mapping/lodash/sampleSize.md b/docs/src/content/mapping/lodash/sampleSize.md
new file mode 100644
index 0000000..4d53f09
--- /dev/null
+++ b/docs/src/content/mapping/lodash/sampleSize.md
@@ -0,0 +1,33 @@
+---
+category: Collection
+remeda: sample
+---
+
+- Unlike Lodash, In Remeda the `sample` function returns items in the **same
+  order** they appear in the input. Use the `shuffle` function if you also need
+  to randomize their order.
+- The second parameter to `sampleSize` in Lodash is optional, and defaults to
+  **1** when not provided. In Remeda this is a required param.
+
+```ts
+// Lodash
+sampleSize(DATA);
+
+// Remeda
+sample(DATA, 1);
+```
+
+```ts
+// Lodash
+sampleSize(DATA, 2);
+
+// Remeda
+// ❌ The result isn't shuffled!
+sample(DATA, 2);
+
+// ✅ Add `shuffle` after `sample` (if order is important).
+shuffle(sample(DATA, 2));
+
+// Or with a pipe
+pipe(DATA, sample(2), shuffle());
+```
diff --git a/docs/src/content/mapping/lodash/set.md b/docs/src/content/mapping/lodash/set.md
new file mode 100644
index 0000000..53d8978
--- /dev/null
+++ b/docs/src/content/mapping/lodash/set.md
@@ -0,0 +1,52 @@
+---
+category: Object
+remeda: setPath
+---
+
+- In Lodash the `set` function supports two ways of defining the path parameter:
+  a string representation of the path (similar to XPath: e.g. `a.b[0].c`), and
+  an array representation of the path (e.g. `['a', 'b', 0, 'c']`). In Remeda
+  only the array representation is accepted. Use the helper function
+  [`stringToPath`](/docs#stringToPath) to translate string paths to array paths.
+- Unlike the Lodash `set` function, In Remeda the provided value must match the
+  type of the prop at that path, the function does not support creating "sparse"
+  objects.
+- For better type-safety, Remeda offers two additional functions to handle
+  paths of length **1**. Use [`set`](/docs#set) to update an existing prop in an
+  object (best type-safety and with IDE type-ahead), and
+  [`addProp`](/docs#addProp) to add a new prop to the object (or to override
+  its _type_)
+- In Lodash `set` _mutates_ the input object. In Remeda a **new** object is
+  returned instead. The input object is never mutated.
+
+```ts
+let data = { a: "hello", deep: [{ z: true }] };
+
+// Lodash
+set(data, "a", "world");
+set(data, ["a"], "foo");
+
+set(data, "a", 123);
+set(data, ["a"], 456);
+set(data, "b", 123);
+set(data, ["b"], 456);
+
+set(data, "deep[0].z", false);
+set(data, ["deep", 0, "z"], false);
+
+// ❌ Not supported:
+set(data, "deep[0].newProp", 123);
+set(data, ["deep", 0, "newProp"], 123);
+
+// Remeda
+data = set(data, "a", "world");
+data = set(data, "a", "foo");
+
+data = addProp(data, "a", 123);
+data = addProp(data, "a", 456);
+data = addProp(data, "b", 123);
+data = addProp(data, "b", 456);
+
+data = setPath(data, stringToPath("deep[0].z"), false);
+data = setPath(data, ["deep", 0, "z"], false);
+```
diff --git a/docs/src/content/mapping/lodash/shuffle.md b/docs/src/content/mapping/lodash/shuffle.md
new file mode 100644
index 0000000..8e83c4a
--- /dev/null
+++ b/docs/src/content/mapping/lodash/shuffle.md
@@ -0,0 +1,4 @@
+---
+category: Collection
+remeda: shuffle
+---
diff --git a/docs/src/content/mapping/lodash/size.md b/docs/src/content/mapping/lodash/size.md
new file mode 100644
index 0000000..77a5306
--- /dev/null
+++ b/docs/src/content/mapping/lodash/size.md
@@ -0,0 +1,4 @@
+---
+category: Collection
+remeda: length
+---
diff --git a/docs/src/content/mapping/lodash/some.md b/docs/src/content/mapping/lodash/some.md
new file mode 100644
index 0000000..d9931ca
--- /dev/null
+++ b/docs/src/content/mapping/lodash/some.md
@@ -0,0 +1,17 @@
+---
+category: Collection
+---
+
+_Not provided by Remeda._
+
+Use the native JS [`Array.prototype.some`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) instead.
+
+_**@see** ["You don't need Lodash"](https://you-dont-need.github.io/You-Dont-Need-Lodash-Underscore/#/?id=_some)_
+
+```ts
+// Lodash
+some(DATA, predicate);
+
+// Native
+DATA.some(predicate);
+```
diff --git a/docs/src/content/mapping/lodash/sortBy.md b/docs/src/content/mapping/lodash/sortBy.md
new file mode 100644
index 0000000..d900546
--- /dev/null
+++ b/docs/src/content/mapping/lodash/sortBy.md
@@ -0,0 +1,29 @@
+---
+category: Collection
+remeda: sortBy
+---
+
+The `sortBy` function in Remeda does not support defining the sort criteria
+using prop names, only callback functions. Also, the sort criteria is provided
+as a variadic argument of sort criteria, and not a single array argument.
+
+```ts
+const DATA = [
+  { user: "fred", age: 48 },
+  { user: "barney", age: 34 },
+  { user: "fred", age: 40 },
+  { user: "barney", age: 36 },
+];
+
+// Lodash
+sortBy(DATA, [
+  function (o) {
+    return o.user;
+  },
+]);
+sortBy(DATA, ["user", "age"]);
+
+// Remeda
+sortBy(DATA, prop("user"));
+sortBy(DATA, prop("user"), prop("age"));
+```
diff --git a/docs/src/content/mapping/lodash/sortedIndex.md b/docs/src/content/mapping/lodash/sortedIndex.md
new file mode 100644
index 0000000..f0c43a8
--- /dev/null
+++ b/docs/src/content/mapping/lodash/sortedIndex.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: sortedIndex
+---
diff --git a/docs/src/content/mapping/lodash/sortedIndexBy.md b/docs/src/content/mapping/lodash/sortedIndexBy.md
new file mode 100644
index 0000000..98920f4
--- /dev/null
+++ b/docs/src/content/mapping/lodash/sortedIndexBy.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: sortedIndexBy
+---
diff --git a/docs/src/content/mapping/lodash/sortedLastIndex.md b/docs/src/content/mapping/lodash/sortedLastIndex.md
new file mode 100644
index 0000000..f744a83
--- /dev/null
+++ b/docs/src/content/mapping/lodash/sortedLastIndex.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: sortedLastIndex
+---
diff --git a/docs/src/content/mapping/lodash/sortedLastIndexBy.md b/docs/src/content/mapping/lodash/sortedLastIndexBy.md
new file mode 100644
index 0000000..536c351
--- /dev/null
+++ b/docs/src/content/mapping/lodash/sortedLastIndexBy.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: sortedLastIndexBy
+---
diff --git a/docs/src/content/mapping/lodash/sortedUniq.md b/docs/src/content/mapping/lodash/sortedUniq.md
new file mode 100644
index 0000000..55db967
--- /dev/null
+++ b/docs/src/content/mapping/lodash/sortedUniq.md
@@ -0,0 +1,25 @@
+---
+category: Array
+remeda: unique
+---
+
+_Not provided by Remeda._
+
+There is no equivalent function in Remeda because both `unique` and `sortedUniq`
+run at _O(n) **time** complexity_. In **space** complexity (the amount of memory
+needed to compute them), `unique` is _O(k)_ where _k_ is the number of unique
+values in the array, while `sortedUniq` is _O(1)_. In most real-life use cases,
+the two implementations would have near-identical performance. We highly
+recommend always using `unique` unless you **know** that the difference matters.
+
+If you still need this algorithm, use [`filter`](/docs#filter) with the
+following predicate:
+
+```ts
+// Lodash
+sortedUniq(DATA);
+
+// Remeda
+unique(DATA);
+filter(DATA, (item, index, array) => index === 0 || item !== array[index - 1]);
+```
diff --git a/docs/src/content/mapping/lodash/subtract.md b/docs/src/content/mapping/lodash/subtract.md
new file mode 100644
index 0000000..851dea4
--- /dev/null
+++ b/docs/src/content/mapping/lodash/subtract.md
@@ -0,0 +1,4 @@
+---
+category: Math
+remeda: subtract
+---
diff --git a/docs/src/content/mapping/lodash/sum.md b/docs/src/content/mapping/lodash/sum.md
new file mode 100644
index 0000000..46e0d4f
--- /dev/null
+++ b/docs/src/content/mapping/lodash/sum.md
@@ -0,0 +1,4 @@
+---
+category: Math
+remeda: sum
+---
diff --git a/docs/src/content/mapping/lodash/sumBy.md b/docs/src/content/mapping/lodash/sumBy.md
new file mode 100644
index 0000000..5cb6b02
--- /dev/null
+++ b/docs/src/content/mapping/lodash/sumBy.md
@@ -0,0 +1,4 @@
+---
+category: Math
+remeda: sumBy
+---
diff --git a/docs/src/content/mapping/lodash/take.md b/docs/src/content/mapping/lodash/take.md
new file mode 100644
index 0000000..b5d5c38
--- /dev/null
+++ b/docs/src/content/mapping/lodash/take.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: take
+---
diff --git a/docs/src/content/mapping/lodash/takeRight.md b/docs/src/content/mapping/lodash/takeRight.md
new file mode 100644
index 0000000..2e474d8
--- /dev/null
+++ b/docs/src/content/mapping/lodash/takeRight.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remead: takeLast
+---
diff --git a/docs/src/content/mapping/lodash/takeRightWhile.md b/docs/src/content/mapping/lodash/takeRightWhile.md
new file mode 100644
index 0000000..7cb9572
--- /dev/null
+++ b/docs/src/content/mapping/lodash/takeRightWhile.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: takeLastWhile
+---
diff --git a/docs/src/content/mapping/lodash/takeWhile.md b/docs/src/content/mapping/lodash/takeWhile.md
new file mode 100644
index 0000000..3a0f5e1
--- /dev/null
+++ b/docs/src/content/mapping/lodash/takeWhile.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: takeWhile
+---
diff --git a/docs/src/content/mapping/lodash/tap.md b/docs/src/content/mapping/lodash/tap.md
new file mode 100644
index 0000000..463df90
--- /dev/null
+++ b/docs/src/content/mapping/lodash/tap.md
@@ -0,0 +1,4 @@
+---
+category: Seq
+remeda: tap
+---
diff --git a/docs/src/content/mapping/lodash/times.md b/docs/src/content/mapping/lodash/times.md
new file mode 100644
index 0000000..b436ec5
--- /dev/null
+++ b/docs/src/content/mapping/lodash/times.md
@@ -0,0 +1,4 @@
+---
+category: Util
+remeda: times
+---
diff --git a/docs/src/content/mapping/lodash/toPairs.md b/docs/src/content/mapping/lodash/toPairs.md
new file mode 100644
index 0000000..1f8503a
--- /dev/null
+++ b/docs/src/content/mapping/lodash/toPairs.md
@@ -0,0 +1,4 @@
+---
+category: Object
+remeda: entries
+---
diff --git a/docs/src/content/mapping/lodash/toPath.md b/docs/src/content/mapping/lodash/toPath.md
new file mode 100644
index 0000000..ee7f02e
--- /dev/null
+++ b/docs/src/content/mapping/lodash/toPath.md
@@ -0,0 +1,4 @@
+---
+category: Util
+remeda: stringToPath
+---
diff --git a/docs/src/content/mapping/lodash/transform.md b/docs/src/content/mapping/lodash/transform.md
new file mode 100644
index 0000000..d500421
--- /dev/null
+++ b/docs/src/content/mapping/lodash/transform.md
@@ -0,0 +1,4 @@
+---
+category: Object
+remeda: transform
+---
diff --git a/docs/src/content/mapping/lodash/uniq.md b/docs/src/content/mapping/lodash/uniq.md
new file mode 100644
index 0000000..49ad401
--- /dev/null
+++ b/docs/src/content/mapping/lodash/uniq.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: unique
+---
diff --git a/docs/src/content/mapping/lodash/uniqBy.md b/docs/src/content/mapping/lodash/uniqBy.md
new file mode 100644
index 0000000..596344c
--- /dev/null
+++ b/docs/src/content/mapping/lodash/uniqBy.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: uniqueBy
+---
diff --git a/docs/src/content/mapping/lodash/uniqWith.md b/docs/src/content/mapping/lodash/uniqWith.md
new file mode 100644
index 0000000..c65a4f8
--- /dev/null
+++ b/docs/src/content/mapping/lodash/uniqWith.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: uniqueWith
+---
diff --git a/docs/src/content/mapping/lodash/values.md b/docs/src/content/mapping/lodash/values.md
new file mode 100644
index 0000000..26e50fa
--- /dev/null
+++ b/docs/src/content/mapping/lodash/values.md
@@ -0,0 +1,4 @@
+---
+category: Object
+remeda: values
+---
diff --git a/docs/src/content/mapping/lodash/words.md b/docs/src/content/mapping/lodash/words.md
new file mode 100644
index 0000000..7a7b37a
--- /dev/null
+++ b/docs/src/content/mapping/lodash/words.md
@@ -0,0 +1,15 @@
+---
+category: String
+---
+
+_Not provided by Remeda._
+
+Use the native JS [`String.prototype.split`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split) with the regex `/\s+/u` instead.
+
+```ts
+// Lodash
+words(str);
+
+// Native
+str.split(/\s+/u);
+```
diff --git a/docs/src/content/mapping/lodash/zip.md b/docs/src/content/mapping/lodash/zip.md
new file mode 100644
index 0000000..352f386
--- /dev/null
+++ b/docs/src/content/mapping/lodash/zip.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: zip
+---
diff --git a/docs/src/content/mapping/lodash/zipObject.md b/docs/src/content/mapping/lodash/zipObject.md
new file mode 100644
index 0000000..1242f4a
--- /dev/null
+++ b/docs/src/content/mapping/lodash/zipObject.md
@@ -0,0 +1,15 @@
+---
+category: Array
+---
+
+_Not provided by Remeda._
+
+Compose [`zip`](/doc#zip) and [`fromEntries`](/doc#fromEntries):
+
+```ts
+// Lodash
+zipObject(keys, values);
+
+// Remeda
+fromEntries(zip(keys, values));
+```
diff --git a/docs/src/content/mapping/lodash/zipWith.md b/docs/src/content/mapping/lodash/zipWith.md
new file mode 100644
index 0000000..d961095
--- /dev/null
+++ b/docs/src/content/mapping/lodash/zipWith.md
@@ -0,0 +1,4 @@
+---
+category: Array
+remeda: zipWith
+---
diff --git a/docs/src/layouts/docs-page.astro b/docs/src/layouts/docs-page.astro
index d4b55ed..9c098e0 100644
--- a/docs/src/layouts/docs-page.astro
+++ b/docs/src/layouts/docs-page.astro
@@ -2,32 +2,29 @@
 import { Navbar } from "@/components/navbar";
 import Prose from "@/components/prose.astro";
 import { VersionSelector } from "@/components/version-selector";
+import { getArticlesForPath } from "@/lib/docs";
 import {
   getNavbarEntries,
   type CategorizedFunctions,
 } from "@/lib/navbar-entries";
-import { getCollection, type CollectionEntry } from "astro:content";
-import BaseLayout from "./base.astro";
 import { map, pipe, sortBy } from "remeda";
-
-const COLLECTION = "docs";
+import BaseLayout from "./base.astro";
 
 type Props = {
   readonly categorized: CategorizedFunctions;
-  readonly collectionQuery: (
-    entry: CollectionEntry<typeof COLLECTION>,
-  ) => boolean;
+  readonly showVersionSelector?: boolean;
 };
 
-const { categorized, collectionQuery } = Astro.props;
+const { categorized, showVersionSelector = false } = Astro.props;
+
+const { pathname } = Astro.url;
+
+const articles = await getArticlesForPath(pathname);
 
-const [navbarEntries, collection] = await Promise.all([
-  getNavbarEntries(categorized, collectionQuery),
-  getCollection(COLLECTION, collectionQuery),
-]);
+const navbarEntries = getNavbarEntries(categorized, articles);
 
-const renderedCollection = await pipe(
-  collection,
+const renderedArticles = await pipe(
+  articles,
   sortBy(({ data: { priority } }) => priority ?? Infinity),
   map(async (entry) => ({
     ...entry,
@@ -35,8 +32,6 @@ const renderedCollection = await pipe(
   })),
   async ($) => await Promise.all($),
 );
-
-const { pathname } = Astro.url;
 ---
 
 <BaseLayout>
@@ -45,12 +40,16 @@ const { pathname } = Astro.url;
       class="sticky top-24 hidden h-[calc(100dvh-8rem)] w-64 overscroll-contain md:block"
     >
       <Navbar entries={navbarEntries} client:load>
-        <VersionSelector pathname={pathname} client:load /></Navbar
+        {
+          showVersionSelector && (
+            <VersionSelector pathname={pathname} client:load />
+          )
+        }</Navbar
       >
     </aside>
     <main class="flex-1 space-y-8 overflow-hidden">
       {
-        renderedCollection.map(({ slug, rendered: { Content } }) => (
+        renderedArticles.map(({ slug, rendered: { Content } }) => (
           <Prose id={slug} class="scroll-mt-24">
             <Content />
           </Prose>
diff --git a/docs/src/lib/docs.ts b/docs/src/lib/docs.ts
new file mode 100644
index 0000000..ab0cb4e
--- /dev/null
+++ b/docs/src/lib/docs.ts
@@ -0,0 +1,11 @@
+import { getCollection, type CollectionEntry } from "astro:content";
+
+const COLLECTION = "docs-articles";
+
+export const getArticlesForPath = async (
+  pathname: string,
+): Promise<ReadonlyArray<CollectionEntry<typeof COLLECTION>>> =>
+  await getCollection(COLLECTION, ({ id }) =>
+    // We rely on our docs content library to mirror our pages structure.
+    id.startsWith(pathname.replaceAll(/^\/|\/$/g, "") + "/"),
+  );
diff --git a/docs/src/lib/mappings.ts b/docs/src/lib/mappings.ts
new file mode 100644
index 0000000..60c2dff
--- /dev/null
+++ b/docs/src/lib/mappings.ts
@@ -0,0 +1,33 @@
+import { getCollection } from "astro:content";
+import { pipe, groupBy, mapValues, map, pick, piped, prop } from "remeda";
+
+const COLLECTION = "mapping";
+
+export async function getMappingEntries(library: string) {
+  const fileNameRegExp = new RegExp(`^${library}/(?<name>.*).mdx?$`, "ui");
+
+  return pipe(
+    await getCollection(COLLECTION, ({ id }) => id.startsWith(library)),
+    groupBy(({ data: { category } }) => category),
+    mapValues(
+      map(
+        piped(
+          prop("id"),
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- We trust astro to be consistent with the id format.
+          ($) => fileNameRegExp.exec($)!.groups!,
+          pick(["name"]),
+        ),
+      ),
+    ),
+  );
+}
+
+export function mappingUrl(library: string, name: string): string | undefined {
+  switch (library) {
+    case "lodash":
+      return `https://lodash.com/docs/4.17.15#${name}`;
+
+    default:
+      return;
+  }
+}
diff --git a/docs/src/lib/navbar-entries.ts b/docs/src/lib/navbar-entries.ts
index 420b0fa..e14c82f 100644
--- a/docs/src/lib/navbar-entries.ts
+++ b/docs/src/lib/navbar-entries.ts
@@ -1,23 +1,23 @@
-import { getCollection, type CollectionEntry } from "astro:content";
+import { type CollectionEntry } from "astro:content";
 import { entries, groupBy, map, pipe, sortBy } from "remeda";
 import { getTags } from "./get-tags";
 import type { SourceTags } from "./transform";
 
-const COLLECTION = "docs";
-
-type FunctionItem = ReadonlyArray<{
+type FunctionItem = {
   readonly name: string;
-  readonly methods: ReadonlyArray<SourceTags>;
-}>;
+  readonly methods?: ReadonlyArray<SourceTags>;
+};
 
-export type CategorizedFunctions = Readonly<Record<string, FunctionItem>>;
+export type CategorizedFunctions = Readonly<
+  Record<string, ReadonlyArray<FunctionItem>>
+>;
 
-export async function getNavbarEntries(
+export function getNavbarEntries(
   categorized: CategorizedFunctions,
-  collectionQuery: (entry: CollectionEntry<typeof COLLECTION>) => boolean,
+  collection: ReadonlyArray<CollectionEntry<"docs-articles">>,
 ) {
   const contentEntries = pipe(
-    await getCollection(COLLECTION, collectionQuery),
+    collection,
     groupBy(({ data: { category } }) => category),
     entries(),
     map(
@@ -42,7 +42,7 @@ export async function getNavbarEntries(
           category,
           map(funcs, ({ name: title, methods }) => ({
             title,
-            tags: getTags(methods),
+            tags: methods === undefined ? [] : getTags(methods),
           })),
         ] as const,
     ),
diff --git a/docs/src/pages/docs.astro b/docs/src/pages/docs.astro
index f7cbb69..3a91d7e 100644
--- a/docs/src/pages/docs.astro
+++ b/docs/src/pages/docs.astro
@@ -5,10 +5,7 @@ import { CATEGORIZED } from "@/lib/categorized";
 import { flat, map, pipe, values } from "remeda";
 ---
 
-<DocsPage
-  categorized={CATEGORIZED}
-  collectionQuery={({ id }) => !id.startsWith("v1/")}
->
+<DocsPage showVersionSelector categorized={CATEGORIZED}>
   {
     pipe(
       CATEGORIZED,
diff --git a/docs/src/pages/mapping.md b/docs/src/pages/mapping.md
deleted file mode 100644
index 7a21d50..0000000
--- a/docs/src/pages/mapping.md
+++ /dev/null
@@ -1,147 +0,0 @@
----
-layout: ../layouts/wiki.astro
----
-
-# Mapping for Lodash and Ramda
-
-_Remeda functions are not necessarily drop-in replacements for the
-listed Lodash and Ramda functions. Just as the Lodash function may behave
-differently than the Ramda equivalent, the Remeda function may also
-behave differently from either or both. Be sure to consult each library's
-documentation when migrating._
-
-| Remeda                                                              | Lodash                                                                   | Ramda                                                              |
-| ------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------ |
-| [`add`](https://remedajs.com/docs/#add)                             | [`add`](https://lodash.com/docs/4.17.15#add)                             | [`add`](https://ramdajs.com/docs/#add)                             |
-| [`addProp`](https://remedajs.com/docs/#addProp)                     | [`set`](https://lodash.com/docs/4.17.15#set)                             | [`set`](https://ramdajs.com/docs/#set)                             |
-| [`allPass`](https://remedajs.com/docs/#allPass)                     |                                                                          | [`allPass`](https://ramdajs.com/docs/#allPass)                     |
-| [`anyPass`](https://remedajs.com/docs/#anyPass)                     |                                                                          | [`anyPass`](https://ramdajs.com/docs/#anyPass)                     |
-| [`ceil`](https://remedajs.com/docs/#ceil)                           | [`ceil`](https://lodash.com/docs/4.17.15#ceil)                           |                                                                    |
-| [`chunk`](https://remedajs.com/docs/#chunk)                         | [`chunk`](https://lodash.com/docs/4.17.15#chunk)                         |                                                                    |
-| [`clamp`](https://remedajs.com/docs/#clamp)                         | [`clamp`](https://lodash.com/docs/4.17.15#clamp)                         | [`clamp`](https://ramdajs.com/docs/#clamp)                         |
-| [`clone`](https://remedajs.com/docs/#clone)                         | [`cloneDeep`](https://lodash.com/docs/4.17.15#cloneDeep)                 | [`clone`](https://ramdajs.com/docs/#clone)                         |
-| [`concat`](https://remedajs.com/docs/#concat)                       | [`concat`](https://lodash.com/docs/4.17.15#concat)                       | [`concat`](https://ramdajs.com/docs/#concat)                       |
-| [`conditional`](https://remedajs.com/docs/#conditional)             | [`cond`](https://lodash.com/docs/4.17.15#cond)                           | [`cond`](https://ramdajs.com/docs/#cond)                           |
-| [`constant`](https://remedajs.com/docs/#constant)                   | [`constant`](https://lodash.com/docs/4.17.15#constant)                   | [`always`](https://ramdajs.com/docs/#always)                       |
-| [`debounce`](https://remedajs.com/docs/#debounce)                   | [`debounce`](https://lodash.com/docs/4.17.15#debounce)                   |                                                                    |
-| [`difference`](https://remedajs.com/docs/#difference)               | [`difference`](https://lodash.com/docs/4.17.15#difference)               | [`difference`](https://ramdajs.com/docs/#difference)               |
-| [`differenceWith`](https://remedajs.com/docs/#differenceWith)       | [`differenceWith`](https://lodash.com/docs/4.17.15#differenceWith)       | [`differenceWith`](https://ramdajs.com/docs/#differenceWith)       |
-| [`divide`](https://remedajs.com/docs/#divide)                       | [`divide`](https://lodash.com/docs/4.17.15#divide)                       | [`divide`](https://ramdajs.com/docs/#divide)                       |
-| [`drop`](https://remedajs.com/docs/#drop)                           | [`drop`](https://lodash.com/docs/4.17.15#drop)                           | [`drop`](https://ramdajs.com/docs/#drop)                           |
-| [`dropLast`](https://remedajs.com/docs/#dropLast)                   | [`dropRight`](https://lodash.com/docs/4.17.15#dropRight)                 | [`dropLast`](https://ramdajs.com/docs/#dropLast)                   |
-| [`dropLastWhile`](https://remedajs.com/docs/#dropLastWhile)         | [`dropRightWhile`](https://lodash.com/docs/4.17.15#dropRightWhile)       | [`dropLastWhile`](https://ramdajs.com/docs/#dropLastWhile)         |
-| [`dropWhile`](https://remedajs.com/docs/#dropWhile)                 | [`dropWhile`](https://lodash.com/docs/4.17.15#dropWhile)                 | [`dropWhile`](https://ramdajs.com/docs/#dropWhile)                 |
-| [`entries`](https://remedajs.com/docs/#entries)                     | [`toPairs`](https://lodash.com/docs/4.17.15#toPairs)                     | [`toPairs`](https://ramdajs.com/docs/#toPairs)                     |
-| [`evolve`](https://remedajs.com/docs/#evolve)                       |                                                                          | [`evolve`](https://ramdajs.com/docs/#evolve)                       |
-| [`filter`](https://remedajs.com/docs/#filter)                       | [`filter`](https://lodash.com/docs/4.17.15#filter)                       | [`filter`](https://ramdajs.com/docs/#filter)                       |
-| [`find`](https://remedajs.com/docs/#find)                           | [`find`](https://lodash.com/docs/4.17.15#find)                           | [`find`](https://ramdajs.com/docs/#find)                           |
-| [`findIndex`](https://remedajs.com/docs/#findIndex)                 | [`findIndex`](https://lodash.com/docs/4.17.15#findIndex)                 | [`findIndex`](https://ramdajs.com/docs/#findIndex)                 |
-| [`findLast`](https://remedajs.com/docs/#findLast)                   | [`findLast`](https://lodash.com/docs/4.17.15#findLast)                   | [`findLast`](https://ramdajs.com/docs/#findLast)                   |
-| [`findLastIndex`](https://remedajs.com/docs/#findLastIndex)         | [`findLastIndex`](https://lodash.com/docs/4.17.15#findLastIndex)         | [`findLastIndex`](https://ramdajs.com/docs/#findLastIndex)         |
-| [`first`](https://remedajs.com/docs/#first)                         | [`head`](https://lodash.com/docs/4.17.15#head)                           | [`head`](https://ramdajs.com/docs/#head)                           |
-| [`flat`](https://remedajs.com/docs/#flat)                           | [`flatten`](https://lodash.com/docs/4.17.15#flatten)                     | [`flatten`](https://ramdajs.com/docs/#flatten)                     |
-| [`flat`](https://remedajs.com/docs/#flattenDeep)                    | [`flattenDeep`](https://lodash.com/docs/4.17.15#flattenDeep)             |                                                                    |
-| [`flatMap`](https://remedajs.com/docs/#flatMap)                     | [`flatMap`](https://lodash.com/docs/4.17.15#flatMap)                     | [`chain`](https://ramdajs.com/docs/#chain)                         |
-| [`floor`](https://remedajs.com/docs/#floor)                         | [`floor`](https://lodash.com/docs/4.17.15#floor)                         |                                                                    |
-| [`forEach`](https://remedajs.com/docs/#forEach)                     | [`forEach`](https://lodash.com/docs/4.17.15#forEach)                     | [`forEach`](https://ramdajs.com/docs/#forEach)                     |
-| [`forEachObj`](https://remedajs.com/docs/#forEachObj)               | [`forEach`](https://lodash.com/docs/4.17.15#forEach)                     | [`forEachObjIndexed`](https://ramdajs.com/docs/#forEachObjIndexed) |
-| [`groupBy`](https://remedajs.com/docs/#groupBy)                     | [`groupBy`](https://lodash.com/docs/4.17.15#groupBy)                     | [`groupBy`](https://ramdajs.com/docs/#groupBy)                     |
-| [`hasSubobject`](https://remedajs.com/docs/#hasSubobject)           | [`isMatch`](https://lodash.com/docs/4.17.15#isMatch)                     | [`where`](https://ramdajs.com/docs/#where)                         |
-| [`identity`](https://remedajs.com/docs/#identity)                   | [`identity`](https://lodash.com/docs/4.17.15#identity)                   | [`identity`](https://ramdajs.com/docs/#identity)                   |
-| [`indexBy`](https://remedajs.com/docs/#indexBy)                     | [`keyBy`](https://lodash.com/docs/4.17.15#keyBy)                         | [`indexBy`](https://ramdajs.com/docs/#indexBy)                     |
-| [`intersection`](https://remedajs.com/docs/#intersection)           | [`intersection`](https://lodash.com/docs/4.17.15#intersection)           | [`intersection`](https://ramdajs.com/docs/#intersection)           |
-| [`intersectionWith`](https://remedajs.com/docs/#intersectionWith)   | [`intersectionWith`](https://lodash.com/docs/4.17.15#intersectionWith)   | [`innerJoin`](https://ramdajs.com/docs/#innerJoin)                 |
-| [`invert`](https://remedajs.com/docs/#invert)                       | [`invert`](https://lodash.com/docs/4.17.15#invert)                       | [`invertObj`](https://ramdajs.com/docs/#invertObj)                 |
-| [`isDeepEqual`](https://remedajs.com/docs/#isDeepEqual)             | [`isEqual`](https://lodash.com/docs/4.17.15#isEqual)                     | [`equals`](https://ramdajs.com/docs/#equals)                       |
-| [`isEmpty`](https://remedajs.com/docs/#isEmpty)                     | [`isEmpty`](https://lodash.com/docs/4.17.15#isEmpty)                     | [`isEmpty`](https://ramdajs.com/docs/#isEmpty)                     |
-| [`isNonNullish`](https://remedajs.com/docs/#isNonNullish)           |                                                                          | [`isNotNil`](https://ramdajs.com/docs/#isNotNil)                   |
-| [`isNullish`](https://remedajs.com/docs/#isNullish)                 | [`isNil`](https://lodash.com/docs/4.17.15#isNil)                         | [`isNil`](https://ramdajs.com/docs/#isNil)                         |
-| [`isObjectType`](https://remedajs.com/docs/#isObjectType)           | [`isObjectLike`](https://lodash.com/docs/4.17.15#isObjectLike)           |                                                                    |
-| [`isPlainObject`](https://remedajs.com/docs/#isPlainObject)         | [`isPlainObject`](https://lodash.com/docs/4.17.15#isPlainObject)         |                                                                    |
-| [`isSymbol`](https://remedajs.com/docs/#isSymbol)                   | [`isSymbol`](https://lodash.com/docs/4.17.15#isSymbol)                   |                                                                    |
-| [`join`](https://remedajs.com/docs/#join)                           | [`join`](https://lodash.com/docs/4.17.15#join)                           | [`join`](https://ramdajs.com/docs/#join)                           |
-| [`last`](https://remedajs.com/docs/#last)                           | [`last`](https://lodash.com/docs/4.17.15#last)                           | [`last`](https://ramdajs.com/docs/#last)                           |
-| [`length`](https://remedajs.com/docs/#length)                       | [`size`](https://lodash.com/docs/4.17.15#size)                           | [`length`](https://ramdajs.com/docs/#length)                       |
-| [`map`](https://remedajs.com/docs/#map)                             | [`map`](https://lodash.com/docs/4.17.15#map)                             | [`map`](https://ramdajs.com/docs/#map)                             |
-| [`mapKeys`](https://remedajs.com/docs/#mapKeys)                     | [`mapKeys`](https://lodash.com/docs/4.17.15#mapKeys)                     |                                                                    |
-| [`mapValues`](https://remedajs.com/docs/#mapValues)                 | [`mapValues`](https://lodash.com/docs/4.17.15#mapValues)                 | [`mapValues`](https://ramdajs.com/docs/#mapObjIndexed)             |
-| [`mapWithFeedback`](https://remedajs.com/docs/#mapWithFeedback)     |                                                                          | [`scan`](https://ramdajs.com/docs/#scan)                           |
-| [`meanBy`](https://remedajs.com/docs/#meanBy)                       | [`meanBy`](https://lodash.com/docs/4.17.15#meanBy)                       |                                                                    |
-| [`merge`](https://remedajs.com/docs/#merge)                         | [`assign`](https://lodash.com/docs/4.17.15#assign)                       | [`merge`](https://ramdajs.com/docs/#merge)                         |
-| [`mergeAll`](https://remedajs.com/docs/#mergeAll)                   | [`assign`](https://lodash.com/docs/4.17.15#assign)                       | [`mergeAll`](https://ramdajs.com/docs/#mergeAll)                   |
-| [`mergeDeep`](https://remedajs.com/docs/#mergeDeep)                 |                                                                          | [`mergeDeepRight`](https://ramdajs.com/docs/#mergeDeepRight)       |
-| [`multiply`](https://remedajs.com/docs/#multiply)                   | [`multiply`](https://lodash.com/docs/4.17.15#multiply)                   | [`multiply`](https://ramdajs.com/docs/#multiply)                   |
-| [`objOf`](https://remedajs.com/docs/#objOf)                         |                                                                          | [`objOf`](https://ramdajs.com/docs/#objOf)                         |
-| [`omit`](https://remedajs.com/docs/#omit)                           | [`omit`](https://lodash.com/docs/4.17.15#omit)                           | [`omit`](https://ramdajs.com/docs/#omit)                           |
-| [`omitBy`](https://remedajs.com/docs/#omitBy)                       | [`omitBy`](https://lodash.com/docs/4.17.15#omitBy)                       |                                                                    |
-| [`once`](https://remedajs.com/docs/#once)                           | [`once`](https://lodash.com/docs/4.17.15#once)                           | [`once`](https://ramdajs.com/docs/#once)                           |
-| [`partition`](https://remedajs.com/docs/#partition)                 | [`partition`](https://lodash.com/docs/4.17.15#partition)                 | [`partition`](https://ramdajs.com/docs/#partition)                 |
-| [`pathOr`](https://remedajs.com/docs/#pathOr)                       | [`get`](https://lodash.com/docs/4.17.15#get)                             | [`pathOr`](https://ramdajs.com/docs/#pathOr)                       |
-| [`pick`](https://remedajs.com/docs/#pick)                           | [`pick`](https://lodash.com/docs/4.17.15#pick)                           | [`pick`](https://ramdajs.com/docs/#pick)                           |
-| [`pickBy`](https://remedajs.com/docs/#pickBy)                       | [`pickBy`](https://lodash.com/docs/4.17.15#pickBy)                       | [`pickBy`](https://ramdajs.com/docs/#pickBy)                       |
-| [`pipe`](https://remedajs.com/docs/#pipe)                           | [`flow`](https://lodash.com/docs/4.17.15#flow)                           | [`pipe`](https://ramdajs.com/docs/#pipe)                           |
-| [`product`](https://remedajs.com/docs/#product)                     |                                                                          | [`product`](https://ramdajs.com/docs/#product)                     |
-| [`prop`](https://remedajs.com/docs/#prop)                           | [`get`](https://lodash.com/docs/4.17.15#get)                             | [`prop`](https://ramdajs.com/docs/#prop)                           |
-| [`randomString`](https://remedajs.com/docs/#randomString)           |                                                                          |                                                                    |
-| [`range`](https://remedajs.com/docs/#range)                         | [`range`](https://lodash.com/docs/4.17.15#range)                         | [`range`](https://ramdajs.com/docs/#range)                         |
-| [`reduce`](https://remedajs.com/docs/#reduce)                       | [`reduce`](https://lodash.com/docs/4.17.15#reduce)                       | [`reduce`](https://ramdajs.com/docs/#reduce)                       |
-| [`reverse`](https://remedajs.com/docs/#reverse)                     | [`reverse`](https://lodash.com/docs/4.17.15#reverse)                     | [`reverse`](https://ramdajs.com/docs/#reverse)                     |
-| [`round`](https://remedajs.com/docs/#round)                         | [`round`](https://lodash.com/docs/4.17.15#round)                         |                                                                    |
-| [`sample`](https://remedajs.com/docs/#sample)                       | [`sampleSize`](https://lodash.com/docs/4.17.15#)\*                       |                                                                    |
-| [`set`](https://remedajs.com/docs/#set)                             | [`set`](https://lodash.com/docs/4.17.15#set)                             | [`assoc`](https://ramdajs.com/docs/#assoc)                         |
-| [`setPath`](https://remedajs.com/docs/#setPath)                     | [`set`](https://lodash.com/docs/4.17.15#set)                             | [`assocPath`](https://ramdajs.com/docs/#assocPath)                 |
-| [`shuffle`](https://remedajs.com/docs/#shuffle)                     | [`shuffle`](https://lodash.com/docs/4.17.15#shuffle)                     |                                                                    |
-| [`sort`](https://remedajs.com/docs/#sort)                           |                                                                          | [`sort`](https://ramdajs.com/docs/#sort)                           |
-| [`sortBy`](https://remedajs.com/docs/#sortBy)                       | [`orderBy`](https://lodash.com/docs/4.17.15#orderBy)                     |                                                                    |
-| [`sortBy`](https://remedajs.com/docs/#sortBy)                       | [`sortBy`](https://lodash.com/docs/4.17.15#sortBy)                       | [`sortBy`](https://ramdajs.com/docs/#sortBy)                       |
-| [`sortedIndex`](https://remedajs.com/docs/#sortedIndex)             | [`sortedIndex`](https://lodash.com/docs/4.17.15#sortedIndex)             |                                                                    |
-| [`sortedIndexBy`](https://remedajs.com/docs/#sortedIndexBy)         | [`sortedIndexBy`](https://lodash.com/docs/4.17.15#sortedIndexBy)         |                                                                    |
-| [`sortedLastIndex`](https://remedajs.com/docs/#sortedLastIndex)     | [`sortedLastIndex`](https://lodash.com/docs/4.17.15#sortedLastIndex)     |                                                                    |
-| [`sortedLastIndexBy`](https://remedajs.com/docs/#sortedLastIndexBy) | [`sortedLastIndexBy`](https://lodash.com/docs/4.17.15#sortedLastIndexBy) |                                                                    |
-| [`splice`](https://remedajs.com/docs/#splice)                       |                                                                          | [`remove`](https://ramdajs.com/docs/#remove)                       |
-| [`splitAt`](https://remedajs.com/docs/#splitAt)                     |                                                                          | [`splitAt`](https://ramdajs.com/docs/#splitAt)                     |
-| [`splitWhen`](https://remedajs.com/docs/#splitWhen)                 |                                                                          | [`splitWhen`](https://ramdajs.com/docs/#splitWhen)                 |
-| [`stringToPath`](https://remedajs.com/docs/#stringToPath)           | [`toPath`](https://lodash.com/docs/4.17.15#toPath)                       |                                                                    |
-| [`subtract`](https://remedajs.com/docs/#subtract)                   | [`subtract`](https://lodash.com/docs/4.17.15#subtract)                   | [`subtract`](https://ramdajs.com/docs/#subtract)                   |
-| [`sum`](https://remedajs.com/docs/#sum)                             | [`sum`](https://lodash.com/docs/4.17.15#sum)                             | [`sum`](https://ramdajs.com/docs/#sum)                             |
-| [`sumBy`](https://remedajs.com/docs/#sumBy)                         | [`sumBy`](https://lodash.com/docs/4.17.15#sumBy)                         |                                                                    |
-| [`swapIndices`](https://remedajs.com/docs/#swapIndices)             |                                                                          | [`swap`](https://ramdajs.com/docs/#swap)                           |
-| [`swapProps`](https://remedajs.com/docs/#swapProps)                 |                                                                          | [`swap`](https://ramdajs.com/docs/#swap)                           |
-| [`take`](https://remedajs.com/docs/#take)                           | [`take`](https://lodash.com/docs/4.17.15#take)                           | [`take`](https://ramdajs.com/docs/#take)                           |
-| [`takeLast`](https://remedajs.com/docs/#takeLast)                   | [`takeRight`](https://lodash.com/docs/4.17.15#takeRight)                 | [`takeLast`](https://ramdajs.com/docs/#takeLast)                   |
-| [`takeLastWhile`](https://remedajs.com/docs/#takeLastWhile)         | [`takeRightWhile`](https://lodash.com/docs/4.17.15#takeRightWhile)       | [`takeLastWhile`](https://ramdajs.com/docs/#takeLastWhile)         |
-| [`takeWhile`](https://remedajs.com/docs/#takeWhile)                 | [`takeWhile`](https://lodash.com/docs/4.17.15#takeWhile)                 | [`takeWhile`](https://ramdajs.com/docs/#takeWhile)                 |
-| [`tap`](https://remedajs.com/docs/#tap)                             | [`tap`](https://lodash.com/docs/4.17.15#tap)                             | [`tap`](https://ramdajs.com/docs/#tap)                             |
-| [`times`](https://remedajs.com/docs/#times)                         | [`times`](https://lodash.com/docs/4.17.15#times)                         | [`times`](https://ramdajs.com/docs/#times)                         |
-| [`unique`](https://remedajs.com/docs/#unique)                       | [`uniq`](https://lodash.com/docs/4.17.15#uniq)                           | [`uniq`](https://ramdajs.com/docs/#uniq)                           |
-| [`uniqueBy`](https://remedajs.com/docs/#uniqueBy)                   | [`uniqBy`](https://lodash.com/docs/4.17.15#uniqBy)                       | [`uniqBy`](https://ramdajs.com/docs/#uniqBy)                       |
-| [`uniqueWith`](https://remedajs.com/docs/#uniqueWith)               | [`uniqWith`](https://lodash.com/docs/4.17.15#uniqWith)                   | [`uniqWith`](https://ramdajs.com/docs/#uniqWith)                   |
-
-## Helpful one-liners
-
-Some lodash and ramda functions don't have a Remeda equivalent, but can be
-easily replaced with a one-liner in TypeScript. Some of the most common
-are listed below.
-
-_TypeScript one-liners are not necessarily drop-in replacements that
-provide the exact functionality of the listed Lodash and Ramda functions.
-Be sure to consult each library's documentation and to check what features
-you are relying on when migrating._
-
-| Remeda                                                                         | Lodash       | Ramda    |
-| ------------------------------------------------------------------------------ | ------------ | -------- |
-| `(arr, val) => [...arr, val]`                                                  |              | `append` |
-| `a?.b?.c`                                                                      | `path`       |          |
-| `n => a[n]`                                                                    | `nth`        | `nth`    |
-| `R.constant(undefined)`                                                        | `noop`       |          |
-| `R.filter((item, index, array) => index === 0 \|\| item !== array[index - 1])` | `sortedUniq` |          |
-| `R.filter(R.isNot(fn))`                                                        | `reject`     | `reject` |
-| `R.filter(R.isTruthy)`                                                         | `compact`    |          |
-| `R.firstBy([fn, "desc"])`                                                      | `maxBy`      | `maxBy`  |
-| `R.firstBy([R.identity, "desc"])`                                              | `max`        | `max`    |
-| `R.firstBy(fn)`                                                                | `minBy`      | `minBy`  |
-| `R.firstBy(R.identity)`                                                        | `min`        | `min`    |
-| `R.fromEntries.strict(R.zip(keys, values))`                                    | `zipObj`     | `zipObj` |
-| `str => str.split(/\s+/)`                                                      | `words`      |          |
-| `x => x.a ?? defaultValue`                                                     | `propOr`     |          |
-| `x => x.a === value`                                                           | `propEq`     |          |
diff --git a/docs/src/pages/migrate/index.md b/docs/src/pages/migrate/index.md
new file mode 100644
index 0000000..acb2e8c
--- /dev/null
+++ b/docs/src/pages/migrate/index.md
@@ -0,0 +1,13 @@
+---
+layout: ../../layouts/wiki.astro
+---
+
+# Migration Guides
+
+If your project is already using a _different_ library for utility functions you
+can follow the function-by-function instructions in the migration guides below
+provided for popular libraries. If you need help with a different library reach
+out on our GitHub page to let us know.
+
+- [Lodash](/migrate/lodash)
+- [Ramda](/migrate/ramda)
diff --git a/docs/src/pages/migrate/lodash.astro b/docs/src/pages/migrate/lodash.astro
new file mode 100644
index 0000000..db62ae1
--- /dev/null
+++ b/docs/src/pages/migrate/lodash.astro
@@ -0,0 +1,25 @@
+---
+import MappingFunctionCard from "@/components/mapping-function-card.astro";
+import DocsPage from "@/layouts/docs-page.astro";
+import { getMappingEntries } from "@/lib/mappings";
+import { entries, flatMap, pipe, sortBy } from "remeda";
+
+const LIBRARY = "lodash";
+
+const mappingEntries = await getMappingEntries(LIBRARY);
+
+const forDisplay = pipe(
+  mappingEntries,
+  entries(),
+  sortBy(([category]) => category),
+  flatMap(([, functions]) => functions),
+);
+---
+
+<DocsPage categorized={mappingEntries}>
+  {
+    forDisplay.map(({ name }) => (
+      <MappingFunctionCard name={name} library={LIBRARY} />
+    ))
+  }
+</DocsPage>
diff --git a/docs/src/pages/migrate/ramda.md b/docs/src/pages/migrate/ramda.md
new file mode 100644
index 0000000..177af8e
--- /dev/null
+++ b/docs/src/pages/migrate/ramda.md
@@ -0,0 +1,112 @@
+---
+layout: ../../layouts/wiki.astro
+---
+
+# Mapping for Ramda
+
+_Remeda functions are not necessarily drop-in replacements for the listed Ramda
+functions; they may behave differently. Be sure to consult Ramda's documentation
+when migrating._
+
+| Ramda                                                              | Remeda                                                            |
+| ------------------------------------------------------------------ | ----------------------------------------------------------------- |
+| [`add`](https://ramdajs.com/docs/#add)                             | [`add`](https://remedajs.com/docs/#add)                           |
+| [`allPass`](https://ramdajs.com/docs/#allPass)                     | [`allPass`](https://remedajs.com/docs/#allPass)                   |
+| [`always`](https://ramdajs.com/docs/#always)                       | [`constant`](https://remedajs.com/docs/#constant)                 |
+| [`anyPass`](https://ramdajs.com/docs/#anyPass)                     | [`anyPass`](https://remedajs.com/docs/#anyPass)                   |
+| [`assoc`](https://ramdajs.com/docs/#assoc)                         | [`set`](https://remedajs.com/docs/#set)                           |
+| [`assocPath`](https://ramdajs.com/docs/#assocPath)                 | [`setPath`](https://remedajs.com/docs/#setPath)                   |
+| [`chain`](https://ramdajs.com/docs/#chain)                         | [`flatMap`](https://remedajs.com/docs/#flatMap)                   |
+| [`clamp`](https://ramdajs.com/docs/#clamp)                         | [`clamp`](https://remedajs.com/docs/#clamp)                       |
+| [`clone`](https://ramdajs.com/docs/#clone)                         | [`clone`](https://remedajs.com/docs/#clone)                       |
+| [`concat`](https://ramdajs.com/docs/#concat)                       | [`concat`](https://remedajs.com/docs/#concat)                     |
+| [`cond`](https://ramdajs.com/docs/#cond)                           | [`conditional`](https://remedajs.com/docs/#conditional)           |
+| [`difference`](https://ramdajs.com/docs/#difference)               | [`difference`](https://remedajs.com/docs/#difference)             |
+| [`differenceWith`](https://ramdajs.com/docs/#differenceWith)       | [`differenceWith`](https://remedajs.com/docs/#differenceWith)     |
+| [`divide`](https://ramdajs.com/docs/#divide)                       | [`divide`](https://remedajs.com/docs/#divide)                     |
+| [`drop`](https://ramdajs.com/docs/#drop)                           | [`drop`](https://remedajs.com/docs/#drop)                         |
+| [`dropLast`](https://ramdajs.com/docs/#dropLast)                   | [`dropLast`](https://remedajs.com/docs/#dropLast)                 |
+| [`dropLastWhile`](https://ramdajs.com/docs/#dropLastWhile)         | [`dropLastWhile`](https://remedajs.com/docs/#dropLastWhile)       |
+| [`dropWhile`](https://ramdajs.com/docs/#dropWhile)                 | [`dropWhile`](https://remedajs.com/docs/#dropWhile)               |
+| [`equals`](https://ramdajs.com/docs/#equals)                       | [`isDeepEqual`](https://remedajs.com/docs/#isDeepEqual)           |
+| [`evolve`](https://ramdajs.com/docs/#evolve)                       | [`evolve`](https://remedajs.com/docs/#evolve)                     |
+| [`filter`](https://ramdajs.com/docs/#filter)                       | [`filter`](https://remedajs.com/docs/#filter)                     |
+| [`find`](https://ramdajs.com/docs/#find)                           | [`find`](https://remedajs.com/docs/#find)                         |
+| [`findIndex`](https://ramdajs.com/docs/#findIndex)                 | [`findIndex`](https://remedajs.com/docs/#findIndex)               |
+| [`findLast`](https://ramdajs.com/docs/#findLast)                   | [`findLast`](https://remedajs.com/docs/#findLast)                 |
+| [`findLastIndex`](https://ramdajs.com/docs/#findLastIndex)         | [`findLastIndex`](https://remedajs.com/docs/#findLastIndex)       |
+| [`flatten`](https://ramdajs.com/docs/#flatten)                     | [`flat`](https://remedajs.com/docs/#flat)                         |
+| [`forEach`](https://ramdajs.com/docs/#forEach)                     | [`forEach`](https://remedajs.com/docs/#forEach)                   |
+| [`forEachObjIndexed`](https://ramdajs.com/docs/#forEachObjIndexed) | [`forEachObj`](https://remedajs.com/docs/#forEachObj)             |
+| [`groupBy`](https://ramdajs.com/docs/#groupBy)                     | [`groupBy`](https://remedajs.com/docs/#groupBy)                   |
+| [`head`](https://ramdajs.com/docs/#head)                           | [`first`](https://remedajs.com/docs/#first)                       |
+| [`identity`](https://ramdajs.com/docs/#identity)                   | [`identity`](https://remedajs.com/docs/#identity)                 |
+| [`indexBy`](https://ramdajs.com/docs/#indexBy)                     | [`indexBy`](https://remedajs.com/docs/#indexBy)                   |
+| [`innerJoin`](https://ramdajs.com/docs/#innerJoin)                 | [`intersectionWith`](https://remedajs.com/docs/#intersectionWith) |
+| [`intersection`](https://ramdajs.com/docs/#intersection)           | [`intersection`](https://remedajs.com/docs/#intersection)         |
+| [`invertObj`](https://ramdajs.com/docs/#invertObj)                 | [`invert`](https://remedajs.com/docs/#invert)                     |
+| [`isEmpty`](https://ramdajs.com/docs/#isEmpty)                     | [`isEmpty`](https://remedajs.com/docs/#isEmpty)                   |
+| [`isNil`](https://ramdajs.com/docs/#isNil)                         | [`isNullish`](https://remedajs.com/docs/#isNullish)               |
+| [`isNotNil`](https://ramdajs.com/docs/#isNotNil)                   | [`isNonNullish`](https://remedajs.com/docs/#isNonNullish)         |
+| [`join`](https://ramdajs.com/docs/#join)                           | [`join`](https://remedajs.com/docs/#join)                         |
+| [`last`](https://ramdajs.com/docs/#last)                           | [`last`](https://remedajs.com/docs/#last)                         |
+| [`length`](https://ramdajs.com/docs/#length)                       | [`length`](https://remedajs.com/docs/#length)                     |
+| [`map`](https://ramdajs.com/docs/#map)                             | [`map`](https://remedajs.com/docs/#map)                           |
+| [`mapObjIndexed`](https://ramdajs.com/docs/#mapObjIndexed)         | [`mapValues`](https://remedajs.com/docs/#mapValues)               |
+| [`merge`](https://ramdajs.com/docs/#merge)                         | [`merge`](https://remedajs.com/docs/#merge)                       |
+| [`mergeAll`](https://ramdajs.com/docs/#mergeAll)                   | [`mergeAll`](https://remedajs.com/docs/#mergeAll)                 |
+| [`mergeDeepRight`](https://ramdajs.com/docs/#mergeDeepRight)       | [`mergeDeep`](https://remedajs.com/docs/#mergeDeep)               |
+| [`multiply`](https://ramdajs.com/docs/#multiply)                   | [`multiply`](https://remedajs.com/docs/#multiply)                 |
+| [`objOf`](https://ramdajs.com/docs/#objOf)                         | [`objOf`](https://remedajs.com/docs/#objOf)                       |
+| [`omit`](https://ramdajs.com/docs/#omit)                           | [`omit`](https://remedajs.com/docs/#omit)                         |
+| [`once`](https://ramdajs.com/docs/#once)                           | [`once`](https://remedajs.com/docs/#once)                         |
+| [`partition`](https://ramdajs.com/docs/#partition)                 | [`partition`](https://remedajs.com/docs/#partition)               |
+| [`pathOr`](https://ramdajs.com/docs/#pathOr)                       | [`pathOr`](https://remedajs.com/docs/#pathOr)                     |
+| [`pick`](https://ramdajs.com/docs/#pick)                           | [`pick`](https://remedajs.com/docs/#pick)                         |
+| [`pickBy`](https://ramdajs.com/docs/#pickBy)                       | [`pickBy`](https://remedajs.com/docs/#pickBy)                     |
+| [`pipe`](https://ramdajs.com/docs/#pipe)                           | [`pipe`](https://remedajs.com/docs/#pipe)                         |
+| [`product`](https://ramdajs.com/docs/#product)                     | [`product`](https://remedajs.com/docs/#product)                   |
+| [`prop`](https://ramdajs.com/docs/#prop)                           | [`prop`](https://remedajs.com/docs/#prop)                         |
+| [`range`](https://ramdajs.com/docs/#range)                         | [`range`](https://remedajs.com/docs/#range)                       |
+| [`reduce`](https://ramdajs.com/docs/#reduce)                       | [`reduce`](https://remedajs.com/docs/#reduce)                     |
+| [`remove`](https://ramdajs.com/docs/#remove)                       | [`splice`](https://remedajs.com/docs/#splice)                     |
+| [`reverse`](https://ramdajs.com/docs/#reverse)                     | [`reverse`](https://remedajs.com/docs/#reverse)                   |
+| [`set`](https://ramdajs.com/docs/#set)                             | [`addProp`](https://remedajs.com/docs/#addProp)                   |
+| [`sort`](https://ramdajs.com/docs/#sort)                           | [`sort`](https://remedajs.com/docs/#sort)                         |
+| [`sortBy`](https://ramdajs.com/docs/#sortBy)                       | [`sortBy`](https://remedajs.com/docs/#sortBy)                     |
+| [`splitAt`](https://ramdajs.com/docs/#splitAt)                     | [`splitAt`](https://remedajs.com/docs/#splitAt)                   |
+| [`splitWhen`](https://ramdajs.com/docs/#splitWhen)                 | [`splitWhen`](https://remedajs.com/docs/#splitWhen)               |
+| [`subtract`](https://ramdajs.com/docs/#subtract)                   | [`subtract`](https://remedajs.com/docs/#subtract)                 |
+| [`sum`](https://ramdajs.com/docs/#sum)                             | [`sum`](https://remedajs.com/docs/#sum)                           |
+| [`swap`](https://ramdajs.com/docs/#swap)                           | [`swapIndices`](https://remedajs.com/docs/#swapIndices)           |
+| [`take`](https://ramdajs.com/docs/#take)                           | [`take`](https://remedajs.com/docs/#take)                         |
+| [`takeLast`](https://ramdajs.com/docs/#takeLast)                   | [`takeLast`](https://remedajs.com/docs/#takeLast)                 |
+| [`takeLastWhile`](https://ramdajs.com/docs/#takeLastWhile)         | [`takeLastWhile`](https://remedajs.com/docs/#takeLastWhile)       |
+| [`takeWhile`](https://ramdajs.com/docs/#takeWhile)                 | [`takeWhile`](https://remedajs.com/docs/#takeWhile)               |
+| [`tap`](https://ramdajs.com/docs/#tap)                             | [`tap`](https://remedajs.com/docs/#tap)                           |
+| [`times`](https://ramdajs.com/docs/#times)                         | [`times`](https://remedajs.com/docs/#times)                       |
+| [`toPairs`](https://ramdajs.com/docs/#toPairs)                     | [`entries`](https://remedajs.com/docs/#entries)                   |
+| [`uniq`](https://ramdajs.com/docs/#uniq)                           | [`unique`](https://remedajs.com/docs/#unique)                     |
+| [`uniqBy`](https://ramdajs.com/docs/#uniqBy)                       | [`uniqueBy`](https://remedajs.com/docs/#uniqueBy)                 |
+| [`uniqWith`](https://ramdajs.com/docs/#uniqWith)                   | [`uniqueWith`](https://remedajs.com/docs/#uniqueWith)             |
+| [`where`](https://ramdajs.com/docs/#where)                         | [`hasSubobject`](https://remedajs.com/docs/#hasSubobject)         |
+
+## Helpful one-liners
+
+Some ramda functions don't have a Remeda equivalent, but can be easily replaced
+with a one-liner in TypeScript. Some of the most common are listed below.
+
+_TypeScript one-liners are not necessarily drop-in replacements that provide the
+exact functionality of the listedRamda functions. Be sure to consult Ramda's
+documentation and to check what features you are relying on when migrating._
+
+| Ramda    | Remeda                                      |
+| -------- | ------------------------------------------- |
+| `append` | `(arr, val) => [...arr, val]`               |
+| `max`    | `R.firstBy([R.identity, "desc"])`           |
+| `maxBy`  | `R.firstBy([fn, "desc"])`                   |
+| `min`    | `R.firstBy(R.identity)`                     |
+| `minBy`  | `R.firstBy(fn)`                             |
+| `nth`    | `n => a[n]`                                 |
+| `reject` | `R.filter(R.isNot(fn))`                     |
+| `zipObj` | `R.fromEntries.strict(R.zip(keys, values))` |
diff --git a/docs/src/pages/v1.astro b/docs/src/pages/v1.astro
index 0481657..8decf13 100644
--- a/docs/src/pages/v1.astro
+++ b/docs/src/pages/v1.astro
@@ -5,10 +5,7 @@ import { CATEGORIZED } from "@/lib/v1/categorized";
 import { flat, map, pipe, values } from "remeda";
 ---
 
-<DocsPage
-  categorized={CATEGORIZED}
-  collectionQuery={({ id }) => id.startsWith("v1/")}
->
+<DocsPage showVersionSelector categorized={CATEGORIZED}>
   {
     pipe(
       CATEGORIZED,
diff --git a/package.json b/package.json
index 5afb04b..63ca0a6 100644
--- a/package.json
+++ b/package.json
@@ -36,14 +36,8 @@
   "type": "module",
   "exports": {
     ".": {
-      "import": {
-        "types": "./dist/index.d.ts",
-        "default": "./dist/index.js"
-      },
-      "require": {
-        "types": "./dist/index.d.cts",
-        "default": "./dist/index.cjs"
-      }
+      "import": "./dist/index.js",
+      "require": "./dist/index.cjs"
     }
   },
   "main": "dist/index.cjs",
diff --git a/src/conditional.ts b/src/conditional.ts
index 48701b5..5c226cc 100644
--- a/src/conditional.ts
+++ b/src/conditional.ts
@@ -7,6 +7,11 @@ type Case<In, Out, Thru extends In = In> = readonly [
   then: (data: Thru) => Out,
 ];
 
+// We package the defaultCase helper into the function itself so that we
+// encapsulate everything into a single export.
+const conditionalPlus = Object.assign(conditional, { defaultCase });
+export { conditionalPlus as conditional };
+
 /**
  * Executes a transformer function based on the first matching predicate,
  * functioning like a series of `if...else if...` statements. It sequentially
@@ -51,7 +56,7 @@ type Case<In, Out, Thru extends In = In> = readonly [
  * @dataLast
  * @category Function
  */
-export function conditional<
+function conditional<
   T,
   Return0,
   Return1 = never,
@@ -141,7 +146,7 @@ export function conditional<
  * @dataFirst
  * @category Function
  */
-export function conditional<
+function conditional<
   T,
   Return0,
   Return1 = never,
@@ -187,7 +192,7 @@ export function conditional<
   | Return8
   | Return9;
 
-export function conditional(...args: ReadonlyArray<unknown>): unknown {
+function conditional(...args: ReadonlyArray<unknown>): unknown {
   return purryOn(isCase, conditionalImplementation, args);
 }
 
@@ -219,37 +224,34 @@ function isCase(maybeCase: unknown): maybeCase is Case<unknown, unknown> {
   );
 }
 
-// eslint-disable-next-line @typescript-eslint/no-namespace
-export namespace conditional {
-  /**
-   * A simplified case that accepts all data. Put this as the last case to
-   * prevent an exception from being thrown when none of the previous cases
-   * match.
-   * If this is not the last case it will short-circuit anything after it.
-   *
-   * @param then - You only need to provide the transformer, the predicate is
-   * implicit. @default () => undefined, which is how Lodash and Ramda handle
-   * the final fallback case.
-   * @example
-   *   const nameOrId = 3 as string | number;
-   *   R.conditional(
-   *     nameOrId,
-   *     [R.isString, (name) => `Hello ${name}`],
-   *     [R.isNumber, (id) => `Hello ID: ${id}`],
-   *     R.conditional.defaultCase(
-   *       (something) => `Hello something (${JSON.stringify(something)})`,
-   *     ),
-   *   ); //=> 'Hello ID: 3'
-   */
-  export function defaultCase(): Case<unknown, undefined>;
-  export function defaultCase<In, Then extends (param: In) => unknown>(
-    then: Then,
-  ): Case<In, ReturnType<Then>>;
-  export function defaultCase(
-    then: (data: unknown) => unknown = trivialDefaultCase,
-  ): Case<unknown, unknown> {
-    return [acceptAnything, then];
-  }
+/**
+ * A simplified case that accepts all data. Put this as the last case to
+ * prevent an exception from being thrown when none of the previous cases
+ * match.
+ * If this is not the last case it will short-circuit anything after it.
+ *
+ * @param then - You only need to provide the transformer, the predicate is
+ * implicit. @default () => undefined, which is how Lodash and Ramda handle
+ * the final fallback case.
+ * @example
+ *   const nameOrId = 3 as string | number;
+ *   R.conditional(
+ *     nameOrId,
+ *     [R.isString, (name) => `Hello ${name}`],
+ *     [R.isNumber, (id) => `Hello ID: ${id}`],
+ *     R.conditional.defaultCase(
+ *       (something) => `Hello something (${JSON.stringify(something)})`,
+ *     ),
+ *   ); //=> 'Hello ID: 3'
+ */
+function defaultCase(): Case<unknown, undefined>;
+function defaultCase<In, Then extends (param: In) => unknown>(
+  then: Then,
+): Case<In, ReturnType<Then>>;
+function defaultCase(
+  then: (data: unknown) => unknown = trivialDefaultCase,
+): Case<unknown, unknown> {
+  return [acceptAnything, then];
 }
 
 // We memoize this so it isn't recreated on every invocation of `defaultCase`.
diff --git a/src/hasSubObject.ts b/src/hasSubObject.ts
index 2d89b78..e5c9533 100644
--- a/src/hasSubObject.ts
+++ b/src/hasSubObject.ts
@@ -1,7 +1,14 @@
 import type { Simplify } from "type-fest";
+import type { Branded } from "./internal/types";
 import { isDeepEqual } from "./isDeepEqual";
 import { purry } from "./purry";
 
+declare const HAS_SUB_OBJECT_BRAND: unique symbol;
+
+type HasSubObjectGuard<T, S> = Simplify<
+  Branded<S & T, typeof HAS_SUB_OBJECT_BRAND>
+>;
+
 /**
  * Checks if `subObject` is a sub-object of `object`, which means for every
  * property and value in `subObject`, there's the same property in `object`
@@ -21,8 +28,7 @@ import { purry } from "./purry";
 export function hasSubObject<T, S extends Partial<T>>(
   data: T,
   subObject: S,
-): // TODO: want to use data is Merge<T, S> here, but it's a typescript error. fix after we allow @ts-expect-error in the build process
-data is Simplify<S & T>;
+): data is HasSubObjectGuard<T, S>;
 
 /**
  * Checks if `subObject` is a sub-object of `object`, which means for every
@@ -41,7 +47,7 @@ data is Simplify<S & T>;
  */
 export function hasSubObject<T, S extends Partial<T>>(
   subObject: S,
-): (data: T) => data is Simplify<S & T>;
+): (data: T) => data is HasSubObjectGuard<T, S>;
 
 export function hasSubObject(...args: ReadonlyArray<unknown>): unknown {
   return purry(hasSubObjectImplementation, args);
@@ -50,7 +56,7 @@ export function hasSubObject(...args: ReadonlyArray<unknown>): unknown {
 function hasSubObjectImplementation<T extends object, S extends Partial<T>>(
   data: T,
   subObject: S,
-): data is Simplify<S & T> {
+): data is HasSubObjectGuard<T, S> {
   for (const [key, value] of Object.entries(subObject)) {
     if (!Object.hasOwn(data, key)) {
       return false;
diff --git a/src/internal/types.ts b/src/internal/types.ts
index 052dc69..078b668 100644
--- a/src/internal/types.ts
+++ b/src/internal/types.ts
@@ -1,5 +1,9 @@
 import type { IsAny, IsLiteral, IsNever, Simplify } from "type-fest";
 
+declare const __brand: unique symbol;
+
+export type Branded<T, Brand extends symbol> = T & { [__brand]: Brand };
+
 export type NonEmptyArray<T> = [T, ...Array<T>];
 
 export type Mapped<T extends IterableContainer, K> = {
diff --git a/src/isNullish.ts b/src/isNullish.ts
index 8e5861c..f5ebf53 100644
--- a/src/isNullish.ts
+++ b/src/isNullish.ts
@@ -1,3 +1,5 @@
+import { type NarrowedTo } from "./internal/types";
+
 /**
  * A function that checks if the passed parameter is either `null` or
  * `undefined` and narrows its type accordingly.
@@ -13,6 +15,8 @@
  *    R.isNullish('somethingElse') //=> false
  * @category Guard
  */
-export function isNullish<T>(data: T): data is Extract<T, null | undefined> {
+export function isNullish<T>(
+  data: T | null | undefined,
+): data is NarrowedTo<T, null | undefined> {
   return data === null || data === undefined;
 }
diff --git a/src/pipe.ts b/src/pipe.ts
index feda29e..b0234ef 100644
--- a/src/pipe.ts
+++ b/src/pipe.ts
@@ -332,7 +332,7 @@ function processItem(
             return true;
           }
         }
-        return false;
+        return isDone;
       }
       currentItem = lazyResult.next;
     }
@@ -348,10 +348,7 @@ function processItem(
   if (lazyResult.hasNext) {
     accumulator.push(currentItem);
   }
-  if (isDone) {
-    return true;
-  }
-  return false;
+  return isDone;
 }
 
 function prepareLazyOperation(op: LazyOp): PreparedLazyOperation {


Test patch:
diff --git a/src/hasSubObject.test.ts b/src/hasSubObject.test.ts
index 2cecea9..764da81 100644
--- a/src/hasSubObject.test.ts
+++ b/src/hasSubObject.test.ts
@@ -2,18 +2,18 @@ import { hasSubObject } from "./hasSubObject";
 import { pipe } from "./pipe";
 
 describe("data first", () => {
-  test("works with empty sub-object", () => {
+  it("works with empty sub-object", () => {
     expect(hasSubObject({ a: 1, b: "b", c: 3 }, {})).toBe(true);
     expect(hasSubObject({}, {})).toBe(true);
   });
 
-  test("works with primitives", () => {
+  it("works with primitives", () => {
     expect(hasSubObject({ a: 1, b: "b", c: 3 }, { a: 1, b: "b" })).toBe(true);
     expect(hasSubObject({ a: 1, b: "c", c: 3 }, { a: 1, b: "b" })).toBe(false);
     expect(hasSubObject({ a: 2, b: "b", c: 3 }, { a: 1, b: "b" })).toBe(false);
   });
 
-  test("works with deep objects", () => {
+  it("works with deep objects", () => {
     expect(hasSubObject({ a: { b: 1, c: 2 } }, { a: { b: 1, c: 2 } })).toBe(
       true,
     );
@@ -22,19 +22,19 @@ describe("data first", () => {
     );
   });
 
-  test("checks for matching key", () => {
+  it("checks for matching key", () => {
     const data = {} as { a?: undefined };
     expect(hasSubObject(data, { a: undefined })).toBe(false);
   });
 });
 
 describe("data last", () => {
-  test("empty sub-object", () => {
+  it("works with empty sub-object", () => {
     expect(pipe({ a: 1, b: 2, c: 3 }, hasSubObject({}))).toBe(true);
     expect(pipe({}, hasSubObject({}))).toBe(true);
   });
 
-  test("works with primitives", () => {
+  it("works with primitives", () => {
     expect(pipe({ a: 1, b: "b", c: 3 }, hasSubObject({ a: 1, b: "b" }))).toBe(
       true,
     );
@@ -46,7 +46,7 @@ describe("data last", () => {
     );
   });
 
-  test("works with deep objects", () => {
+  it("works with deep objects", () => {
     expect(
       pipe({ a: { b: 1, c: 2 } }, hasSubObject({ a: { b: 1, c: 2 } })),
     ).toBe(true);
@@ -59,7 +59,7 @@ describe("data last", () => {
 
 describe("typing", () => {
   describe("data-first", () => {
-    test("must have matching keys and values", () => {
+    it("must have matching keys and values", () => {
       expectTypeOf(hasSubObject({ a: 2 }, { a: 1 })).toEqualTypeOf<boolean>();
 
       // @ts-expect-error [ts2353] - missing a key
@@ -81,7 +81,7 @@ describe("typing", () => {
       hasSubObject({ a: 2 }, { a: 1 } as const);
     });
 
-    test("allows nested objects", () => {
+    it("allows nested objects", () => {
       // ok - nested object has extra keys
       hasSubObject({ a: { b: 4 } }, { a: { b: 1, c: 2 } });
 
@@ -92,25 +92,49 @@ describe("typing", () => {
       hasSubObject({ a: { b: 4, c: "c" } }, { a: { b: 1, c: 2 } });
     });
 
-    test("narrows type", () => {
+    it("narrows with empty object", () => {
       const obj = {} as { a?: string; b?: number };
 
       if (hasSubObject(obj, {})) {
-        expectTypeOf(obj).toEqualTypeOf<{ a?: string; b?: number }>();
+        expectTypeOf(obj).toMatchTypeOf<{ a?: string; b?: number }>();
+      } else {
+        expectTypeOf(obj).toMatchTypeOf<{ a?: string; b?: number }>();
       }
+    });
+
+    it("narrows with same object", () => {
+      const obj = {} as { a?: string; b?: number };
+
+      if (hasSubObject(obj, obj)) {
+        expectTypeOf(obj).toMatchTypeOf<{ a?: string; b?: number }>();
+      } else {
+        expectTypeOf(obj).toMatchTypeOf<{ a?: string; b?: number }>();
+      }
+    });
+
+    it("narrows optional field to required", () => {
+      const obj = {} as { a?: string; b?: number };
 
       if (hasSubObject(obj, { a: "a" })) {
-        expectTypeOf(obj).toEqualTypeOf<{ a: string; b?: number }>();
+        expectTypeOf(obj).toMatchTypeOf<{ a: string; b?: number }>();
+      } else {
+        expectTypeOf(obj).toMatchTypeOf<{ a?: string; b?: number }>();
       }
+    });
+
+    it("narrows field to constant type", () => {
+      const obj = {} as { a?: string; b?: number };
 
       if (hasSubObject(obj, { a: "a" } as const)) {
-        expectTypeOf(obj).toEqualTypeOf<{ a: "a"; b?: number }>();
+        expectTypeOf(obj).toMatchTypeOf<{ readonly a: "a"; b?: number }>();
+      } else {
+        expectTypeOf(obj).toMatchTypeOf<{ a?: string; b?: number }>();
       }
     });
   });
 
   describe("data-last", () => {
-    test("must have matching keys and values", () => {
+    it("must have matching keys and values", () => {
       expectTypeOf(
         pipe({ a: 2 }, hasSubObject({ a: 1 })),
       ).toEqualTypeOf<boolean>();
@@ -134,7 +158,7 @@ describe("typing", () => {
       pipe({ a: 2 }, hasSubObject({ a: 1 } as const));
     });
 
-    test("allows nested objects", () => {
+    it("allows nested objects", () => {
       // ok - nested object has extra keys
       pipe({ a: { b: 4 } }, hasSubObject({ a: { b: 1, c: 2 } }));
 
diff --git a/src/isNullish.test.ts b/src/isNullish.test.ts
index 969f8a0..4f71804 100644
--- a/src/isNullish.test.ts
+++ b/src/isNullish.test.ts
@@ -1,23 +1,92 @@
-import {
-  ALL_TYPES_DATA_PROVIDER,
-  TYPES_DATA_PROVIDER,
-  type AllTypesDataProviderTypes,
-} from "../test/typesDataProvider";
+import { ALL_TYPES_DATA_PROVIDER } from "../test/typesDataProvider";
 import { isNullish } from "./isNullish";
 
-describe("isNullish", () => {
-  it("should work as type guard", () => {
-    const data = TYPES_DATA_PROVIDER.null as AllTypesDataProviderTypes;
+describe("runtime", () => {
+  it("accepts nulls", () => {
+    expect(isNullish(null)).toBe(true);
+  });
+
+  it("accepts undefined", () => {
+    expect(isNullish(undefined)).toBe(true);
+  });
+
+  it("rejects anything else", () => {
+    for (const data of ALL_TYPES_DATA_PROVIDER) {
+      if (data === null || data === undefined) {
+        continue;
+      }
+      expect(isNullish(data)).toBe(false);
+    }
+  });
+});
+
+describe("typing", () => {
+  it("narrows nulls", () => {
+    const data = 123 as number | null;
+    if (isNullish(data)) {
+      expectTypeOf(data).toBeNull();
+    } else {
+      expectTypeOf(data).toBeNumber();
+    }
+  });
+
+  it("narrows undefined", () => {
+    const data = 123 as number | undefined;
+    if (isNullish(data)) {
+      expectTypeOf(data).toBeUndefined();
+    } else {
+      expectTypeOf(data).toBeNumber();
+    }
+  });
+
+  it("narrows on both", () => {
+    const data = 123 as number | null | undefined;
+    if (isNullish(data)) {
+      expectTypeOf(data).toEqualTypeOf<null | undefined>();
+    } else {
+      expectTypeOf(data).toBeNumber();
+    }
+  });
+
+  it("doesn't narrow non-nullables", () => {
+    const data = 123;
+    if (isNullish(data)) {
+      expectTypeOf(data).toBeNever();
+    } else {
+      expectTypeOf(data).toBeNumber();
+    }
+  });
+
+  it("narrows unknowns", () => {
+    const data = 123 as unknown;
+    if (isNullish(data)) {
+      expectTypeOf(data).toEqualTypeOf<null | undefined>();
+    } else {
+      expectTypeOf(data).toBeUnknown();
+    }
+  });
+
+  it("narrows any", () => {
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
+    const data = 123 as any;
+    if (isNullish(data)) {
+      expectTypeOf(data).toEqualTypeOf<null | undefined>();
+    } else {
+      expectTypeOf(data).toBeAny();
+    }
+  });
+
+  it("doesn't expand narrow types", () => {
+    const data = "cat" as "cat" | "dog" | null | undefined;
     if (isNullish(data)) {
-      expect(data).toEqual(null);
       expectTypeOf(data).toEqualTypeOf<null | undefined>();
+    } else {
+      expectTypeOf(data).toEqualTypeOf<"cat" | "dog">();
     }
   });
 
   it("should work as type guard in filter", () => {
-    const data = ALL_TYPES_DATA_PROVIDER.filter(isNullish);
-    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Intentional for the test.
-    expect(data.every((c) => c === null || c === undefined)).toEqual(true);
-    expectTypeOf(data).toEqualTypeOf<Array<null | undefined>>();
+    const result = ALL_TYPES_DATA_PROVIDER.filter(isNullish);
+    expectTypeOf(result).toEqualTypeOf<Array<null | undefined>>();
   });
 });
diff --git a/src/pipe.test.ts b/src/pipe.test.ts
index 97387b9..f4aacf8 100644
--- a/src/pipe.test.ts
+++ b/src/pipe.test.ts
@@ -1,4 +1,5 @@
 import { filter } from "./filter";
+import { flat } from "./flat";
 import { identity } from "./identity";
 import { map } from "./map";
 import { pipe } from "./pipe";
@@ -115,4 +116,17 @@ describe("lazy", () => {
     expect(count2).toHaveBeenCalledTimes(2);
     expect(result).toEqual([100, 200]);
   });
+
+  it("lazy early exit with hasMany", () => {
+    const result = pipe(
+      [
+        [1, 2],
+        [3, 4],
+        [5, 6],
+      ],
+      take(1),
+      flat(),
+    );
+    expect(result).toEqual([1, 2]);
+  });
 });