diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..da1249b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true + diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..662c018 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,21 @@ +name: Deploy + +on: + push: + branches: [main] + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - run: bun install + - run: bun run build + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..50efb07 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,16 @@ +name: Test + +on: + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - run: bun install + - run: bun test diff --git a/.gitignore b/.gitignore index 5b3333b..9b1ee42 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs .idea -.vsocde \ No newline at end of file + +# Finder (MacOS) folder config +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..c72f27e --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# web-tools + +A small static site comprising various tools for web development and beyond, +deployed to GitHub Pages. + +## Requirements + +- [Bun](https://bun.sh) v1.2.0 +- [Typescript](https://www.typescriptlang.org) +- [Decimal.js](https://mikemcl.github.io/decimal.js/) + +The site also makes use of several packages through CDN: + +- [Open Props](https://open-props.style/) +- [HTMX](https://htmx.org/) +- [Alpine.js](https://alpinejs.dev/) + +## Getting started + +To install dependencies: + +```bash +bun install +``` + +To run the dev server: + +```bash +bun run dev +``` + +To bundle the typescript into javascript: + +```bash +bun run build +``` diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..614dd57 --- /dev/null +++ b/bun.lock @@ -0,0 +1,34 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "web-tools", + "dependencies": { + "decimal.js": "^10.6.0", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5.0.0", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="], + + "@types/node": ["@types/node@24.3.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g=="], + + "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], + + "bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + + "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], + + "undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], + } +} diff --git a/docs/PULL_REQUEST_TEMPLATE/dev_main.md b/docs/PULL_REQUEST_TEMPLATE/dev_main.md deleted file mode 100644 index 164bf15..0000000 --- a/docs/PULL_REQUEST_TEMPLATE/dev_main.md +++ /dev/null @@ -1 +0,0 @@ -# dev-main \ No newline at end of file diff --git a/docs/PULL_REQUEST_TEMPLATE/ticket_dev.md b/docs/PULL_REQUEST_TEMPLATE/ticket_dev.md deleted file mode 100644 index 2aaa344..0000000 --- a/docs/PULL_REQUEST_TEMPLATE/ticket_dev.md +++ /dev/null @@ -1,11 +0,0 @@ -# dank-xxxx - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. - -## Visual Changes - -## Code - -```javascript -const rollD = (faces) => Math.floor(Math.random() * faces) + 1; -``` \ No newline at end of file diff --git a/examples/accordion.html b/examples/accordion.html deleted file mode 100644 index 469801e..0000000 --- a/examples/accordion.html +++ /dev/null @@ -1,11 +0,0 @@ -

Accordion To Some

-

This is the HTML native <details> tag combined with the <summary> - tag for the heading

-
- Click me to open or close -

This is a heck of a lot of text you might not want displayed by default. If you do want it displayed - by default you can add the open property to the details tag, and it will be displayed. -

-

A smidgen of CSS has been added to the <summary> tag to make it clear when you - hover over it that it can be interacted with.

-
\ No newline at end of file diff --git a/examples/autocomplete.html b/examples/autocomplete.html deleted file mode 100644 index 69eec93..0000000 --- a/examples/autocomplete.html +++ /dev/null @@ -1,13 +0,0 @@ -

HTML, you Autocomplete Me

-

An autocomplete input using only HTML, specifically the <datalist> tag

-
- - - - - - - -
\ No newline at end of file diff --git a/examples/chip.html b/examples/chip.html deleted file mode 100644 index 5c74a19..0000000 --- a/examples/chip.html +++ /dev/null @@ -1,7 +0,0 @@ -

Chip off the Old Block

-

A replication of the - Chip - - - component found in many ui libraries. -

\ No newline at end of file diff --git a/examples/colour.html b/examples/colour.html deleted file mode 100644 index 14c9d30..0000000 --- a/examples/colour.html +++ /dev/null @@ -1,6 +0,0 @@ -

All the Colours of the Rainbow

-

This is just an <input> of type 'color'. No canvas, it's browser native, and you can - even select a colour that is on the page, which can't be done with a JavaScript approach.

-
- -
\ No newline at end of file diff --git a/examples/dialog.html b/examples/dialog.html deleted file mode 100644 index f6a91b6..0000000 --- a/examples/dialog.html +++ /dev/null @@ -1,19 +0,0 @@ -

Start a Dialog

-

This is a native HTML element <dialog>. This does require a bit of JavaScript to make - use of the dialog api, but significantly less than a framework or package would make you use.

-
- -
-

* This dialog will open with a simple animation if you're using firefox or chrome/chromium. - Hopefully this will be added to all browsers soon.

- -
-

A simple dialog with a <form>

-
- - -
-
-
\ No newline at end of file diff --git a/examples/footnote.html b/examples/footnote.html deleted file mode 100644 index dbcb334..0000000 --- a/examples/footnote.html +++ /dev/null @@ -1,16 +0,0 @@ -

I Got a Note in my Foot

-

A footnote [1] can be - achieved with a combination of htmx and our friend the - details tag. -

-
- -
-
-

- [1] This <details> tag can be opened from a footnote with this attribute added to it: -

- _="on click get the next <details/> then toggle @open on it" -
-

-
\ No newline at end of file diff --git a/examples/gradient.html b/examples/gradient.html deleted file mode 100644 index bc2a785..0000000 --- a/examples/gradient.html +++ /dev/null @@ -1,9 +0,0 @@ -

Making The Gradient

-

- With the linear-gradient property you can make a gradient background for any element. This - usually doesn't work with text, but when combined with background-clip: text and - color: transparent you can make headings that will break your eyes. -

-
-

Richard Of York Gave Battle In Vain

-
\ No newline at end of file diff --git a/examples/navbar.html b/examples/navbar.html deleted file mode 100644 index caac975..0000000 --- a/examples/navbar.html +++ /dev/null @@ -1,3 +0,0 @@ -

- Nav -

\ No newline at end of file diff --git a/examples/new-morphism.html b/examples/new-morphism.html deleted file mode 100644 index 39c2d8f..0000000 --- a/examples/new-morphism.html +++ /dev/null @@ -1,10 +0,0 @@ -

New Morphism, New You

-

- You can make hoverable elements have a shadow effect using the hover pseudo-selector and - setting its box-shadow property with two values. -

-
-
-

Hover

-
-
\ No newline at end of file diff --git a/examples/resize.html b/examples/resize.html deleted file mode 100644 index 3c5f43c..0000000 --- a/examples/resize.html +++ /dev/null @@ -1,16 +0,0 @@ -

Size Doesn't Matter

-

- With resize you can make any inline element for which the overflow property - is set to hidden resizable, across either axis or both. -

-
-
-

Vertical

-
-
-

Horizontal

-
-
-

Both

-
-
\ No newline at end of file diff --git a/examples/switch.html b/examples/switch.html deleted file mode 100644 index 8415123..0000000 --- a/examples/switch.html +++ /dev/null @@ -1,18 +0,0 @@ -

Switch it Up

-

A modern approach to the switch with piddly - [1] JS is used to - toggle between light and dark mode in. This replicates the switch type that is currently only available in safari, - but hopefully will be added to other browsers soon. -

-

-

- -
-
-

- [1] This switch does use a bit of JavaScript to set dark or light mode, but it comes to less - than 14 lines. It is possible to toggle dark mode on or off with only css as per Coding2GO's video. - This implementation, while requiring some code, checks if the user has a preference for dark mode and - defaults the page accordingly while still allowing the user to flip between light and dark. -

-
\ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index 937384f..0000000 --- a/index.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - Web Components - - - - - - - - -
- - -
- -
-
-

Without Frameworks

-

- JavaScript frameworks are great. They allow you to create web apps with rich functionality and complex - components, but they come at a cost: bloating your countless packages and arcane methods of bundling - and transpiling. -

-

- Many of these 'complex' components can be built with just the Holy Trinity™ of web development - (HTML, CSS and JavaScript). A lot of the time you don't even need JavaScript, or at best only a - smidgen. With a canny use of only the first two you can create some use functionality without - installing a single package. -

-

- When something is too complex for just HTML and CSS to achieve, there still might not be any need for - javascript (kind of) thanks to a new kid on the block with some old school ideas: - htmx, and it's friend - hyperscript. Routin, state management and DOM - Manipulation without having to write a line of javascript -

-

- These components are mainly inspired by - this talk by Kilian Valkhof - and this video by the - youtuber Coding2GO, but also other places across the web where I've been able to take inspiration - [1] from - others. -

-
- -
-
-

- [1] Read: nicked. I've tried to credit where I've taken inspiration from, but if I've - missed - something please let me know. -

-
-
-
- - - - - \ No newline at end of file diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..3913b44 --- /dev/null +++ b/index.ts @@ -0,0 +1,24 @@ +const PORT = 3000; +const BASE_PATH = "./public"; +const INDEX = "/index.html"; + +const serveFile = (path: string) => { + const filePath = BASE_PATH + path; + const file = Bun.file(filePath); + return new Response(file); +} + +Bun.serve({ + port: PORT, + async fetch(req) { + console.log(`Serving on port: ${PORT}`); + const path = new URL(req.url).pathname; + if (path === "/") { + return serveFile(INDEX); + } + return serveFile(path); + }, + error() { + return new Response(null, { status: 404 }); + }, +}); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c8487fa --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "web-tools", + "module": "index.ts", + "type": "module", + "scripts": { + "dev": "bun index.ts", + "build": "bun build src/index.ts --outdir=public/static" + }, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "decimal.js": "^10.6.0" + } +} diff --git a/public/colour-picker.html b/public/colour-picker.html new file mode 100644 index 0000000..a87d7e4 --- /dev/null +++ b/public/colour-picker.html @@ -0,0 +1,29 @@ +
+

Colour Picker

+ +
+ + +
+ +
+

Colour Values

+

Hex:

+

RGB:

+

HSL:

+
+
diff --git a/public/home.html b/public/home.html new file mode 100644 index 0000000..f95e432 --- /dev/null +++ b/public/home.html @@ -0,0 +1,40 @@ +
+

About Web Tools

+

+ This is a suite of tools made to assist with common tasks when making web + apps. This isn't ground breaking stuff, many of these tools are available + elsewhere, but I wanted to test my abilities and also have all the tools + I commonly use in one place. +

+ +

+ This site is made with the unholy trinity of the web; HTML; CSS and + ECMAScript. There's handful of other packages and tools under the hood + that makes it work: +

+ +
diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..30f5a35 --- /dev/null +++ b/public/index.html @@ -0,0 +1,117 @@ + + + + + + + + Web Tools + + + + + + + + +
+

Web Tools

+ + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • +
    +
    + +
    +
    + +
    + + + + + diff --git a/public/numbers.html b/public/numbers.html new file mode 100644 index 0000000..7d37075 --- /dev/null +++ b/public/numbers.html @@ -0,0 +1,44 @@ +
    +

    Numbers

    + +
    + Scientific Notation Converter +
    + + +
    + +
    +
    + + +
    + +
    + + +
    +
    + + + +
    + + +
    +
    + +
    diff --git a/public/static/index.js b/public/static/index.js new file mode 100644 index 0000000..3495e10 --- /dev/null +++ b/public/static/index.js @@ -0,0 +1,2997 @@ +// src/timestamps/ecma.ts +var getHoursFromMilliseconds = (milliseconds) => { + return Math.floor(milliseconds / (1000 * 60 * 60)); +}; +var getDaysFromMilliseconds = (milliseconds) => { + return Math.floor(milliseconds / (1000 * 60 * 60 * 24)); +}; +var convertEcmaTimestamp = (ecmaTimestamp, ecmaConversionOption) => { + if (!ecmaTimestamp || isNaN(ecmaTimestamp)) { + return "Please enter a valid timestamp"; + } + const date = new Date(ecmaTimestamp); + switch (ecmaConversionOption) { + case "iso": + return date.toISOString(); + case "utc": + return date.toUTCString(); + case "locale": + return date.toLocaleString(); + case "seconds": + return Math.floor(ecmaTimestamp / 1000); + case "hours": + return getHoursFromMilliseconds(ecmaTimestamp); + case "days": + return getDaysFromMilliseconds(ecmaTimestamp); + default: + return "Invalid conversion option"; + } +}; +// src/timestamps/unix.ts +var getHoursFromSeconds = (seconds) => { + return Math.floor(seconds / (60 * 60)); +}; +var getDaysFromSeconds = (seconds) => { + return Math.floor(seconds / (60 * 60 * 24)); +}; +var convertUnixTimestamp = (unixTimestamp, conversionOption) => { + if (!unixTimestamp || isNaN(unixTimestamp)) { + return "Please enter a valid timestamp"; + } + const date = new Date(unixTimestamp * 1000); + switch (conversionOption) { + case "iso": + return date.toISOString(); + case "utc": + return date.toUTCString(); + case "locale": + return date.toLocaleString(); + case "milliseconds": + return date.getTime(); + case "hours": + return getHoursFromSeconds(date.getTime() / 1000); + case "days": + return getDaysFromSeconds(date.getTime() / 1000); + default: + return "Invalid conversion option"; + } +}; +// src/timestamps/iso-string.ts +var convertIsoString = (isoString, conversionType) => { + const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/; + if (!isoString || !isoDateRegex.test(isoString)) { + return "Please enter a valid ISO string"; + } + const date = new Date(isoString); + if (isNaN(date.getTime())) { + return "Invalid ISO string"; + } + switch (conversionType) { + case "unix": + return Math.floor(date.getTime() / 1000); + case "ecma": + return date.getTime(); + default: + return "Invalid conversion type"; + } +}; +// src/colour/hex-to-rgb.ts +var hexToRgb = (hex) => { + const red = parseInt(hex.slice(1, 3), 16); + const green = parseInt(hex.slice(3, 5), 16); + const blue = parseInt(hex.slice(5, 7), 16); + return `rgb(${red}, ${green}, ${blue})`; +}; +// node_modules/decimal.js/decimal.mjs +/*! + * decimal.js v10.6.0 + * An arbitrary-precision Decimal type for JavaScript. + * https://github.com/MikeMcl/decimal.js + * Copyright (c) 2025 Michael Mclaughlin + * MIT Licence + */ +var EXP_LIMIT = 9000000000000000; +var MAX_DIGITS = 1e9; +var NUMERALS = "0123456789abcdef"; +var LN10 = "2.3025850929940456840179914546843642076011014886287729760333279009675726096773524802359972050895982983419677840422862486334095254650828067566662873690987816894829072083255546808437998948262331985283935053089653777326288461633662222876982198867465436674744042432743651550489343149393914796194044002221051017141748003688084012647080685567743216228355220114804663715659121373450747856947683463616792101806445070648000277502684916746550586856935673420670581136429224554405758925724208241314695689016758940256776311356919292033376587141660230105703089634572075440370847469940168269282808481184289314848524948644871927809676271275775397027668605952496716674183485704422507197965004714951050492214776567636938662976979522110718264549734772662425709429322582798502585509785265383207606726317164309505995087807523710333101197857547331541421808427543863591778117054309827482385045648019095610299291824318237525357709750539565187697510374970888692180205189339507238539205144634197265287286965110862571492198849978748873771345686209167058"; +var PI = "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632789"; +var DEFAULTS = { + precision: 20, + rounding: 4, + modulo: 1, + toExpNeg: -7, + toExpPos: 21, + minE: -EXP_LIMIT, + maxE: EXP_LIMIT, + crypto: false +}; +var inexact; +var quadrant; +var external = true; +var decimalError = "[DecimalError] "; +var invalidArgument = decimalError + "Invalid argument: "; +var precisionLimitExceeded = decimalError + "Precision limit exceeded"; +var cryptoUnavailable = decimalError + "crypto unavailable"; +var tag = "[object Decimal]"; +var mathfloor = Math.floor; +var mathpow = Math.pow; +var isBinary = /^0b([01]+(\.[01]*)?|\.[01]+)(p[+-]?\d+)?$/i; +var isHex = /^0x([0-9a-f]+(\.[0-9a-f]*)?|\.[0-9a-f]+)(p[+-]?\d+)?$/i; +var isOctal = /^0o([0-7]+(\.[0-7]*)?|\.[0-7]+)(p[+-]?\d+)?$/i; +var isDecimal = /^(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i; +var BASE = 1e7; +var LOG_BASE = 7; +var MAX_SAFE_INTEGER = 9007199254740991; +var LN10_PRECISION = LN10.length - 1; +var PI_PRECISION = PI.length - 1; +var P = { toStringTag: tag }; +P.absoluteValue = P.abs = function() { + var x = new this.constructor(this); + if (x.s < 0) + x.s = 1; + return finalise(x); +}; +P.ceil = function() { + return finalise(new this.constructor(this), this.e + 1, 2); +}; +P.clampedTo = P.clamp = function(min, max) { + var k, x = this, Ctor = x.constructor; + min = new Ctor(min); + max = new Ctor(max); + if (!min.s || !max.s) + return new Ctor(NaN); + if (min.gt(max)) + throw Error(invalidArgument + max); + k = x.cmp(min); + return k < 0 ? min : x.cmp(max) > 0 ? max : new Ctor(x); +}; +P.comparedTo = P.cmp = function(y) { + var i, j, xdL, ydL, x = this, xd = x.d, yd = (y = new x.constructor(y)).d, xs = x.s, ys = y.s; + if (!xd || !yd) { + return !xs || !ys ? NaN : xs !== ys ? xs : xd === yd ? 0 : !xd ^ xs < 0 ? 1 : -1; + } + if (!xd[0] || !yd[0]) + return xd[0] ? xs : yd[0] ? -ys : 0; + if (xs !== ys) + return xs; + if (x.e !== y.e) + return x.e > y.e ^ xs < 0 ? 1 : -1; + xdL = xd.length; + ydL = yd.length; + for (i = 0, j = xdL < ydL ? xdL : ydL;i < j; ++i) { + if (xd[i] !== yd[i]) + return xd[i] > yd[i] ^ xs < 0 ? 1 : -1; + } + return xdL === ydL ? 0 : xdL > ydL ^ xs < 0 ? 1 : -1; +}; +P.cosine = P.cos = function() { + var pr, rm, x = this, Ctor = x.constructor; + if (!x.d) + return new Ctor(NaN); + if (!x.d[0]) + return new Ctor(1); + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + Math.max(x.e, x.sd()) + LOG_BASE; + Ctor.rounding = 1; + x = cosine(Ctor, toLessThanHalfPi(Ctor, x)); + Ctor.precision = pr; + Ctor.rounding = rm; + return finalise(quadrant == 2 || quadrant == 3 ? x.neg() : x, pr, rm, true); +}; +P.cubeRoot = P.cbrt = function() { + var e, m, n, r, rep, s, sd, t, t3, t3plusx, x = this, Ctor = x.constructor; + if (!x.isFinite() || x.isZero()) + return new Ctor(x); + external = false; + s = x.s * mathpow(x.s * x, 1 / 3); + if (!s || Math.abs(s) == 1 / 0) { + n = digitsToString(x.d); + e = x.e; + if (s = (e - n.length + 1) % 3) + n += s == 1 || s == -2 ? "0" : "00"; + s = mathpow(n, 1 / 3); + e = mathfloor((e + 1) / 3) - (e % 3 == (e < 0 ? -1 : 2)); + if (s == 1 / 0) { + n = "5e" + e; + } else { + n = s.toExponential(); + n = n.slice(0, n.indexOf("e") + 1) + e; + } + r = new Ctor(n); + r.s = x.s; + } else { + r = new Ctor(s.toString()); + } + sd = (e = Ctor.precision) + 3; + for (;; ) { + t = r; + t3 = t.times(t).times(t); + t3plusx = t3.plus(x); + r = divide(t3plusx.plus(x).times(t), t3plusx.plus(t3), sd + 2, 1); + if (digitsToString(t.d).slice(0, sd) === (n = digitsToString(r.d)).slice(0, sd)) { + n = n.slice(sd - 3, sd + 1); + if (n == "9999" || !rep && n == "4999") { + if (!rep) { + finalise(t, e + 1, 0); + if (t.times(t).times(t).eq(x)) { + r = t; + break; + } + } + sd += 4; + rep = 1; + } else { + if (!+n || !+n.slice(1) && n.charAt(0) == "5") { + finalise(r, e + 1, 1); + m = !r.times(r).times(r).eq(x); + } + break; + } + } + } + external = true; + return finalise(r, e, Ctor.rounding, m); +}; +P.decimalPlaces = P.dp = function() { + var w, d = this.d, n = NaN; + if (d) { + w = d.length - 1; + n = (w - mathfloor(this.e / LOG_BASE)) * LOG_BASE; + w = d[w]; + if (w) + for (;w % 10 == 0; w /= 10) + n--; + if (n < 0) + n = 0; + } + return n; +}; +P.dividedBy = P.div = function(y) { + return divide(this, new this.constructor(y)); +}; +P.dividedToIntegerBy = P.divToInt = function(y) { + var x = this, Ctor = x.constructor; + return finalise(divide(x, new Ctor(y), 0, 1, 1), Ctor.precision, Ctor.rounding); +}; +P.equals = P.eq = function(y) { + return this.cmp(y) === 0; +}; +P.floor = function() { + return finalise(new this.constructor(this), this.e + 1, 3); +}; +P.greaterThan = P.gt = function(y) { + return this.cmp(y) > 0; +}; +P.greaterThanOrEqualTo = P.gte = function(y) { + var k = this.cmp(y); + return k == 1 || k === 0; +}; +P.hyperbolicCosine = P.cosh = function() { + var k, n, pr, rm, len, x = this, Ctor = x.constructor, one = new Ctor(1); + if (!x.isFinite()) + return new Ctor(x.s ? 1 / 0 : NaN); + if (x.isZero()) + return one; + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + Math.max(x.e, x.sd()) + 4; + Ctor.rounding = 1; + len = x.d.length; + if (len < 32) { + k = Math.ceil(len / 3); + n = (1 / tinyPow(4, k)).toString(); + } else { + k = 16; + n = "2.3283064365386962890625e-10"; + } + x = taylorSeries(Ctor, 1, x.times(n), new Ctor(1), true); + var cosh2_x, i = k, d8 = new Ctor(8); + for (;i--; ) { + cosh2_x = x.times(x); + x = one.minus(cosh2_x.times(d8.minus(cosh2_x.times(d8)))); + } + return finalise(x, Ctor.precision = pr, Ctor.rounding = rm, true); +}; +P.hyperbolicSine = P.sinh = function() { + var k, pr, rm, len, x = this, Ctor = x.constructor; + if (!x.isFinite() || x.isZero()) + return new Ctor(x); + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + Math.max(x.e, x.sd()) + 4; + Ctor.rounding = 1; + len = x.d.length; + if (len < 3) { + x = taylorSeries(Ctor, 2, x, x, true); + } else { + k = 1.4 * Math.sqrt(len); + k = k > 16 ? 16 : k | 0; + x = x.times(1 / tinyPow(5, k)); + x = taylorSeries(Ctor, 2, x, x, true); + var sinh2_x, d5 = new Ctor(5), d16 = new Ctor(16), d20 = new Ctor(20); + for (;k--; ) { + sinh2_x = x.times(x); + x = x.times(d5.plus(sinh2_x.times(d16.times(sinh2_x).plus(d20)))); + } + } + Ctor.precision = pr; + Ctor.rounding = rm; + return finalise(x, pr, rm, true); +}; +P.hyperbolicTangent = P.tanh = function() { + var pr, rm, x = this, Ctor = x.constructor; + if (!x.isFinite()) + return new Ctor(x.s); + if (x.isZero()) + return new Ctor(x); + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + 7; + Ctor.rounding = 1; + return divide(x.sinh(), x.cosh(), Ctor.precision = pr, Ctor.rounding = rm); +}; +P.inverseCosine = P.acos = function() { + var x = this, Ctor = x.constructor, k = x.abs().cmp(1), pr = Ctor.precision, rm = Ctor.rounding; + if (k !== -1) { + return k === 0 ? x.isNeg() ? getPi(Ctor, pr, rm) : new Ctor(0) : new Ctor(NaN); + } + if (x.isZero()) + return getPi(Ctor, pr + 4, rm).times(0.5); + Ctor.precision = pr + 6; + Ctor.rounding = 1; + x = new Ctor(1).minus(x).div(x.plus(1)).sqrt().atan(); + Ctor.precision = pr; + Ctor.rounding = rm; + return x.times(2); +}; +P.inverseHyperbolicCosine = P.acosh = function() { + var pr, rm, x = this, Ctor = x.constructor; + if (x.lte(1)) + return new Ctor(x.eq(1) ? 0 : NaN); + if (!x.isFinite()) + return new Ctor(x); + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + Math.max(Math.abs(x.e), x.sd()) + 4; + Ctor.rounding = 1; + external = false; + x = x.times(x).minus(1).sqrt().plus(x); + external = true; + Ctor.precision = pr; + Ctor.rounding = rm; + return x.ln(); +}; +P.inverseHyperbolicSine = P.asinh = function() { + var pr, rm, x = this, Ctor = x.constructor; + if (!x.isFinite() || x.isZero()) + return new Ctor(x); + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + 2 * Math.max(Math.abs(x.e), x.sd()) + 6; + Ctor.rounding = 1; + external = false; + x = x.times(x).plus(1).sqrt().plus(x); + external = true; + Ctor.precision = pr; + Ctor.rounding = rm; + return x.ln(); +}; +P.inverseHyperbolicTangent = P.atanh = function() { + var pr, rm, wpr, xsd, x = this, Ctor = x.constructor; + if (!x.isFinite()) + return new Ctor(NaN); + if (x.e >= 0) + return new Ctor(x.abs().eq(1) ? x.s / 0 : x.isZero() ? x : NaN); + pr = Ctor.precision; + rm = Ctor.rounding; + xsd = x.sd(); + if (Math.max(xsd, pr) < 2 * -x.e - 1) + return finalise(new Ctor(x), pr, rm, true); + Ctor.precision = wpr = xsd - x.e; + x = divide(x.plus(1), new Ctor(1).minus(x), wpr + pr, 1); + Ctor.precision = pr + 4; + Ctor.rounding = 1; + x = x.ln(); + Ctor.precision = pr; + Ctor.rounding = rm; + return x.times(0.5); +}; +P.inverseSine = P.asin = function() { + var halfPi, k, pr, rm, x = this, Ctor = x.constructor; + if (x.isZero()) + return new Ctor(x); + k = x.abs().cmp(1); + pr = Ctor.precision; + rm = Ctor.rounding; + if (k !== -1) { + if (k === 0) { + halfPi = getPi(Ctor, pr + 4, rm).times(0.5); + halfPi.s = x.s; + return halfPi; + } + return new Ctor(NaN); + } + Ctor.precision = pr + 6; + Ctor.rounding = 1; + x = x.div(new Ctor(1).minus(x.times(x)).sqrt().plus(1)).atan(); + Ctor.precision = pr; + Ctor.rounding = rm; + return x.times(2); +}; +P.inverseTangent = P.atan = function() { + var i, j, k, n, px, t, r, wpr, x2, x = this, Ctor = x.constructor, pr = Ctor.precision, rm = Ctor.rounding; + if (!x.isFinite()) { + if (!x.s) + return new Ctor(NaN); + if (pr + 4 <= PI_PRECISION) { + r = getPi(Ctor, pr + 4, rm).times(0.5); + r.s = x.s; + return r; + } + } else if (x.isZero()) { + return new Ctor(x); + } else if (x.abs().eq(1) && pr + 4 <= PI_PRECISION) { + r = getPi(Ctor, pr + 4, rm).times(0.25); + r.s = x.s; + return r; + } + Ctor.precision = wpr = pr + 10; + Ctor.rounding = 1; + k = Math.min(28, wpr / LOG_BASE + 2 | 0); + for (i = k;i; --i) + x = x.div(x.times(x).plus(1).sqrt().plus(1)); + external = false; + j = Math.ceil(wpr / LOG_BASE); + n = 1; + x2 = x.times(x); + r = new Ctor(x); + px = x; + for (;i !== -1; ) { + px = px.times(x2); + t = r.minus(px.div(n += 2)); + px = px.times(x2); + r = t.plus(px.div(n += 2)); + if (r.d[j] !== undefined) + for (i = j;r.d[i] === t.d[i] && i--; ) + ; + } + if (k) + r = r.times(2 << k - 1); + external = true; + return finalise(r, Ctor.precision = pr, Ctor.rounding = rm, true); +}; +P.isFinite = function() { + return !!this.d; +}; +P.isInteger = P.isInt = function() { + return !!this.d && mathfloor(this.e / LOG_BASE) > this.d.length - 2; +}; +P.isNaN = function() { + return !this.s; +}; +P.isNegative = P.isNeg = function() { + return this.s < 0; +}; +P.isPositive = P.isPos = function() { + return this.s > 0; +}; +P.isZero = function() { + return !!this.d && this.d[0] === 0; +}; +P.lessThan = P.lt = function(y) { + return this.cmp(y) < 0; +}; +P.lessThanOrEqualTo = P.lte = function(y) { + return this.cmp(y) < 1; +}; +P.logarithm = P.log = function(base) { + var isBase10, d, denominator, k, inf, num, sd, r, arg = this, Ctor = arg.constructor, pr = Ctor.precision, rm = Ctor.rounding, guard = 5; + if (base == null) { + base = new Ctor(10); + isBase10 = true; + } else { + base = new Ctor(base); + d = base.d; + if (base.s < 0 || !d || !d[0] || base.eq(1)) + return new Ctor(NaN); + isBase10 = base.eq(10); + } + d = arg.d; + if (arg.s < 0 || !d || !d[0] || arg.eq(1)) { + return new Ctor(d && !d[0] ? -1 / 0 : arg.s != 1 ? NaN : d ? 0 : 1 / 0); + } + if (isBase10) { + if (d.length > 1) { + inf = true; + } else { + for (k = d[0];k % 10 === 0; ) + k /= 10; + inf = k !== 1; + } + } + external = false; + sd = pr + guard; + num = naturalLogarithm(arg, sd); + denominator = isBase10 ? getLn10(Ctor, sd + 10) : naturalLogarithm(base, sd); + r = divide(num, denominator, sd, 1); + if (checkRoundingDigits(r.d, k = pr, rm)) { + do { + sd += 10; + num = naturalLogarithm(arg, sd); + denominator = isBase10 ? getLn10(Ctor, sd + 10) : naturalLogarithm(base, sd); + r = divide(num, denominator, sd, 1); + if (!inf) { + if (+digitsToString(r.d).slice(k + 1, k + 15) + 1 == 100000000000000) { + r = finalise(r, pr + 1, 0); + } + break; + } + } while (checkRoundingDigits(r.d, k += 10, rm)); + } + external = true; + return finalise(r, pr, rm); +}; +P.minus = P.sub = function(y) { + var d, e, i, j, k, len, pr, rm, xd, xe, xLTy, yd, x = this, Ctor = x.constructor; + y = new Ctor(y); + if (!x.d || !y.d) { + if (!x.s || !y.s) + y = new Ctor(NaN); + else if (x.d) + y.s = -y.s; + else + y = new Ctor(y.d || x.s !== y.s ? x : NaN); + return y; + } + if (x.s != y.s) { + y.s = -y.s; + return x.plus(y); + } + xd = x.d; + yd = y.d; + pr = Ctor.precision; + rm = Ctor.rounding; + if (!xd[0] || !yd[0]) { + if (yd[0]) + y.s = -y.s; + else if (xd[0]) + y = new Ctor(x); + else + return new Ctor(rm === 3 ? -0 : 0); + return external ? finalise(y, pr, rm) : y; + } + e = mathfloor(y.e / LOG_BASE); + xe = mathfloor(x.e / LOG_BASE); + xd = xd.slice(); + k = xe - e; + if (k) { + xLTy = k < 0; + if (xLTy) { + d = xd; + k = -k; + len = yd.length; + } else { + d = yd; + e = xe; + len = xd.length; + } + i = Math.max(Math.ceil(pr / LOG_BASE), len) + 2; + if (k > i) { + k = i; + d.length = 1; + } + d.reverse(); + for (i = k;i--; ) + d.push(0); + d.reverse(); + } else { + i = xd.length; + len = yd.length; + xLTy = i < len; + if (xLTy) + len = i; + for (i = 0;i < len; i++) { + if (xd[i] != yd[i]) { + xLTy = xd[i] < yd[i]; + break; + } + } + k = 0; + } + if (xLTy) { + d = xd; + xd = yd; + yd = d; + y.s = -y.s; + } + len = xd.length; + for (i = yd.length - len;i > 0; --i) + xd[len++] = 0; + for (i = yd.length;i > k; ) { + if (xd[--i] < yd[i]) { + for (j = i;j && xd[--j] === 0; ) + xd[j] = BASE - 1; + --xd[j]; + xd[i] += BASE; + } + xd[i] -= yd[i]; + } + for (;xd[--len] === 0; ) + xd.pop(); + for (;xd[0] === 0; xd.shift()) + --e; + if (!xd[0]) + return new Ctor(rm === 3 ? -0 : 0); + y.d = xd; + y.e = getBase10Exponent(xd, e); + return external ? finalise(y, pr, rm) : y; +}; +P.modulo = P.mod = function(y) { + var q, x = this, Ctor = x.constructor; + y = new Ctor(y); + if (!x.d || !y.s || y.d && !y.d[0]) + return new Ctor(NaN); + if (!y.d || x.d && !x.d[0]) { + return finalise(new Ctor(x), Ctor.precision, Ctor.rounding); + } + external = false; + if (Ctor.modulo == 9) { + q = divide(x, y.abs(), 0, 3, 1); + q.s *= y.s; + } else { + q = divide(x, y, 0, Ctor.modulo, 1); + } + q = q.times(y); + external = true; + return x.minus(q); +}; +P.naturalExponential = P.exp = function() { + return naturalExponential(this); +}; +P.naturalLogarithm = P.ln = function() { + return naturalLogarithm(this); +}; +P.negated = P.neg = function() { + var x = new this.constructor(this); + x.s = -x.s; + return finalise(x); +}; +P.plus = P.add = function(y) { + var carry, d, e, i, k, len, pr, rm, xd, yd, x = this, Ctor = x.constructor; + y = new Ctor(y); + if (!x.d || !y.d) { + if (!x.s || !y.s) + y = new Ctor(NaN); + else if (!x.d) + y = new Ctor(y.d || x.s === y.s ? x : NaN); + return y; + } + if (x.s != y.s) { + y.s = -y.s; + return x.minus(y); + } + xd = x.d; + yd = y.d; + pr = Ctor.precision; + rm = Ctor.rounding; + if (!xd[0] || !yd[0]) { + if (!yd[0]) + y = new Ctor(x); + return external ? finalise(y, pr, rm) : y; + } + k = mathfloor(x.e / LOG_BASE); + e = mathfloor(y.e / LOG_BASE); + xd = xd.slice(); + i = k - e; + if (i) { + if (i < 0) { + d = xd; + i = -i; + len = yd.length; + } else { + d = yd; + e = k; + len = xd.length; + } + k = Math.ceil(pr / LOG_BASE); + len = k > len ? k + 1 : len + 1; + if (i > len) { + i = len; + d.length = 1; + } + d.reverse(); + for (;i--; ) + d.push(0); + d.reverse(); + } + len = xd.length; + i = yd.length; + if (len - i < 0) { + i = len; + d = yd; + yd = xd; + xd = d; + } + for (carry = 0;i; ) { + carry = (xd[--i] = xd[i] + yd[i] + carry) / BASE | 0; + xd[i] %= BASE; + } + if (carry) { + xd.unshift(carry); + ++e; + } + for (len = xd.length;xd[--len] == 0; ) + xd.pop(); + y.d = xd; + y.e = getBase10Exponent(xd, e); + return external ? finalise(y, pr, rm) : y; +}; +P.precision = P.sd = function(z) { + var k, x = this; + if (z !== undefined && z !== !!z && z !== 1 && z !== 0) + throw Error(invalidArgument + z); + if (x.d) { + k = getPrecision(x.d); + if (z && x.e + 1 > k) + k = x.e + 1; + } else { + k = NaN; + } + return k; +}; +P.round = function() { + var x = this, Ctor = x.constructor; + return finalise(new Ctor(x), x.e + 1, Ctor.rounding); +}; +P.sine = P.sin = function() { + var pr, rm, x = this, Ctor = x.constructor; + if (!x.isFinite()) + return new Ctor(NaN); + if (x.isZero()) + return new Ctor(x); + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + Math.max(x.e, x.sd()) + LOG_BASE; + Ctor.rounding = 1; + x = sine(Ctor, toLessThanHalfPi(Ctor, x)); + Ctor.precision = pr; + Ctor.rounding = rm; + return finalise(quadrant > 2 ? x.neg() : x, pr, rm, true); +}; +P.squareRoot = P.sqrt = function() { + var m, n, sd, r, rep, t, x = this, d = x.d, e = x.e, s = x.s, Ctor = x.constructor; + if (s !== 1 || !d || !d[0]) { + return new Ctor(!s || s < 0 && (!d || d[0]) ? NaN : d ? x : 1 / 0); + } + external = false; + s = Math.sqrt(+x); + if (s == 0 || s == 1 / 0) { + n = digitsToString(d); + if ((n.length + e) % 2 == 0) + n += "0"; + s = Math.sqrt(n); + e = mathfloor((e + 1) / 2) - (e < 0 || e % 2); + if (s == 1 / 0) { + n = "5e" + e; + } else { + n = s.toExponential(); + n = n.slice(0, n.indexOf("e") + 1) + e; + } + r = new Ctor(n); + } else { + r = new Ctor(s.toString()); + } + sd = (e = Ctor.precision) + 3; + for (;; ) { + t = r; + r = t.plus(divide(x, t, sd + 2, 1)).times(0.5); + if (digitsToString(t.d).slice(0, sd) === (n = digitsToString(r.d)).slice(0, sd)) { + n = n.slice(sd - 3, sd + 1); + if (n == "9999" || !rep && n == "4999") { + if (!rep) { + finalise(t, e + 1, 0); + if (t.times(t).eq(x)) { + r = t; + break; + } + } + sd += 4; + rep = 1; + } else { + if (!+n || !+n.slice(1) && n.charAt(0) == "5") { + finalise(r, e + 1, 1); + m = !r.times(r).eq(x); + } + break; + } + } + } + external = true; + return finalise(r, e, Ctor.rounding, m); +}; +P.tangent = P.tan = function() { + var pr, rm, x = this, Ctor = x.constructor; + if (!x.isFinite()) + return new Ctor(NaN); + if (x.isZero()) + return new Ctor(x); + pr = Ctor.precision; + rm = Ctor.rounding; + Ctor.precision = pr + 10; + Ctor.rounding = 1; + x = x.sin(); + x.s = 1; + x = divide(x, new Ctor(1).minus(x.times(x)).sqrt(), pr + 10, 0); + Ctor.precision = pr; + Ctor.rounding = rm; + return finalise(quadrant == 2 || quadrant == 4 ? x.neg() : x, pr, rm, true); +}; +P.times = P.mul = function(y) { + var carry, e, i, k, r, rL, t, xdL, ydL, x = this, Ctor = x.constructor, xd = x.d, yd = (y = new Ctor(y)).d; + y.s *= x.s; + if (!xd || !xd[0] || !yd || !yd[0]) { + return new Ctor(!y.s || xd && !xd[0] && !yd || yd && !yd[0] && !xd ? NaN : !xd || !yd ? y.s / 0 : y.s * 0); + } + e = mathfloor(x.e / LOG_BASE) + mathfloor(y.e / LOG_BASE); + xdL = xd.length; + ydL = yd.length; + if (xdL < ydL) { + r = xd; + xd = yd; + yd = r; + rL = xdL; + xdL = ydL; + ydL = rL; + } + r = []; + rL = xdL + ydL; + for (i = rL;i--; ) + r.push(0); + for (i = ydL;--i >= 0; ) { + carry = 0; + for (k = xdL + i;k > i; ) { + t = r[k] + yd[i] * xd[k - i - 1] + carry; + r[k--] = t % BASE | 0; + carry = t / BASE | 0; + } + r[k] = (r[k] + carry) % BASE | 0; + } + for (;!r[--rL]; ) + r.pop(); + if (carry) + ++e; + else + r.shift(); + y.d = r; + y.e = getBase10Exponent(r, e); + return external ? finalise(y, Ctor.precision, Ctor.rounding) : y; +}; +P.toBinary = function(sd, rm) { + return toStringBinary(this, 2, sd, rm); +}; +P.toDecimalPlaces = P.toDP = function(dp, rm) { + var x = this, Ctor = x.constructor; + x = new Ctor(x); + if (dp === undefined) + return x; + checkInt32(dp, 0, MAX_DIGITS); + if (rm === undefined) + rm = Ctor.rounding; + else + checkInt32(rm, 0, 8); + return finalise(x, dp + x.e + 1, rm); +}; +P.toExponential = function(dp, rm) { + var str, x = this, Ctor = x.constructor; + if (dp === undefined) { + str = finiteToString(x, true); + } else { + checkInt32(dp, 0, MAX_DIGITS); + if (rm === undefined) + rm = Ctor.rounding; + else + checkInt32(rm, 0, 8); + x = finalise(new Ctor(x), dp + 1, rm); + str = finiteToString(x, true, dp + 1); + } + return x.isNeg() && !x.isZero() ? "-" + str : str; +}; +P.toFixed = function(dp, rm) { + var str, y, x = this, Ctor = x.constructor; + if (dp === undefined) { + str = finiteToString(x); + } else { + checkInt32(dp, 0, MAX_DIGITS); + if (rm === undefined) + rm = Ctor.rounding; + else + checkInt32(rm, 0, 8); + y = finalise(new Ctor(x), dp + x.e + 1, rm); + str = finiteToString(y, false, dp + y.e + 1); + } + return x.isNeg() && !x.isZero() ? "-" + str : str; +}; +P.toFraction = function(maxD) { + var d, d0, d1, d2, e, k, n, n0, n1, pr, q, r, x = this, xd = x.d, Ctor = x.constructor; + if (!xd) + return new Ctor(x); + n1 = d0 = new Ctor(1); + d1 = n0 = new Ctor(0); + d = new Ctor(d1); + e = d.e = getPrecision(xd) - x.e - 1; + k = e % LOG_BASE; + d.d[0] = mathpow(10, k < 0 ? LOG_BASE + k : k); + if (maxD == null) { + maxD = e > 0 ? d : n1; + } else { + n = new Ctor(maxD); + if (!n.isInt() || n.lt(n1)) + throw Error(invalidArgument + n); + maxD = n.gt(d) ? e > 0 ? d : n1 : n; + } + external = false; + n = new Ctor(digitsToString(xd)); + pr = Ctor.precision; + Ctor.precision = e = xd.length * LOG_BASE * 2; + for (;; ) { + q = divide(n, d, 0, 1, 1); + d2 = d0.plus(q.times(d1)); + if (d2.cmp(maxD) == 1) + break; + d0 = d1; + d1 = d2; + d2 = n1; + n1 = n0.plus(q.times(d2)); + n0 = d2; + d2 = d; + d = n.minus(q.times(d2)); + n = d2; + } + d2 = divide(maxD.minus(d0), d1, 0, 1, 1); + n0 = n0.plus(d2.times(n1)); + d0 = d0.plus(d2.times(d1)); + n0.s = n1.s = x.s; + r = divide(n1, d1, e, 1).minus(x).abs().cmp(divide(n0, d0, e, 1).minus(x).abs()) < 1 ? [n1, d1] : [n0, d0]; + Ctor.precision = pr; + external = true; + return r; +}; +P.toHexadecimal = P.toHex = function(sd, rm) { + return toStringBinary(this, 16, sd, rm); +}; +P.toNearest = function(y, rm) { + var x = this, Ctor = x.constructor; + x = new Ctor(x); + if (y == null) { + if (!x.d) + return x; + y = new Ctor(1); + rm = Ctor.rounding; + } else { + y = new Ctor(y); + if (rm === undefined) { + rm = Ctor.rounding; + } else { + checkInt32(rm, 0, 8); + } + if (!x.d) + return y.s ? x : y; + if (!y.d) { + if (y.s) + y.s = x.s; + return y; + } + } + if (y.d[0]) { + external = false; + x = divide(x, y, 0, rm, 1).times(y); + external = true; + finalise(x); + } else { + y.s = x.s; + x = y; + } + return x; +}; +P.toNumber = function() { + return +this; +}; +P.toOctal = function(sd, rm) { + return toStringBinary(this, 8, sd, rm); +}; +P.toPower = P.pow = function(y) { + var e, k, pr, r, rm, s, x = this, Ctor = x.constructor, yn = +(y = new Ctor(y)); + if (!x.d || !y.d || !x.d[0] || !y.d[0]) + return new Ctor(mathpow(+x, yn)); + x = new Ctor(x); + if (x.eq(1)) + return x; + pr = Ctor.precision; + rm = Ctor.rounding; + if (y.eq(1)) + return finalise(x, pr, rm); + e = mathfloor(y.e / LOG_BASE); + if (e >= y.d.length - 1 && (k = yn < 0 ? -yn : yn) <= MAX_SAFE_INTEGER) { + r = intPow(Ctor, x, k, pr); + return y.s < 0 ? new Ctor(1).div(r) : finalise(r, pr, rm); + } + s = x.s; + if (s < 0) { + if (e < y.d.length - 1) + return new Ctor(NaN); + if ((y.d[e] & 1) == 0) + s = 1; + if (x.e == 0 && x.d[0] == 1 && x.d.length == 1) { + x.s = s; + return x; + } + } + k = mathpow(+x, yn); + e = k == 0 || !isFinite(k) ? mathfloor(yn * (Math.log("0." + digitsToString(x.d)) / Math.LN10 + x.e + 1)) : new Ctor(k + "").e; + if (e > Ctor.maxE + 1 || e < Ctor.minE - 1) + return new Ctor(e > 0 ? s / 0 : 0); + external = false; + Ctor.rounding = x.s = 1; + k = Math.min(12, (e + "").length); + r = naturalExponential(y.times(naturalLogarithm(x, pr + k)), pr); + if (r.d) { + r = finalise(r, pr + 5, 1); + if (checkRoundingDigits(r.d, pr, rm)) { + e = pr + 10; + r = finalise(naturalExponential(y.times(naturalLogarithm(x, e + k)), e), e + 5, 1); + if (+digitsToString(r.d).slice(pr + 1, pr + 15) + 1 == 100000000000000) { + r = finalise(r, pr + 1, 0); + } + } + } + r.s = s; + external = true; + Ctor.rounding = rm; + return finalise(r, pr, rm); +}; +P.toPrecision = function(sd, rm) { + var str, x = this, Ctor = x.constructor; + if (sd === undefined) { + str = finiteToString(x, x.e <= Ctor.toExpNeg || x.e >= Ctor.toExpPos); + } else { + checkInt32(sd, 1, MAX_DIGITS); + if (rm === undefined) + rm = Ctor.rounding; + else + checkInt32(rm, 0, 8); + x = finalise(new Ctor(x), sd, rm); + str = finiteToString(x, sd <= x.e || x.e <= Ctor.toExpNeg, sd); + } + return x.isNeg() && !x.isZero() ? "-" + str : str; +}; +P.toSignificantDigits = P.toSD = function(sd, rm) { + var x = this, Ctor = x.constructor; + if (sd === undefined) { + sd = Ctor.precision; + rm = Ctor.rounding; + } else { + checkInt32(sd, 1, MAX_DIGITS); + if (rm === undefined) + rm = Ctor.rounding; + else + checkInt32(rm, 0, 8); + } + return finalise(new Ctor(x), sd, rm); +}; +P.toString = function() { + var x = this, Ctor = x.constructor, str = finiteToString(x, x.e <= Ctor.toExpNeg || x.e >= Ctor.toExpPos); + return x.isNeg() && !x.isZero() ? "-" + str : str; +}; +P.truncated = P.trunc = function() { + return finalise(new this.constructor(this), this.e + 1, 1); +}; +P.valueOf = P.toJSON = function() { + var x = this, Ctor = x.constructor, str = finiteToString(x, x.e <= Ctor.toExpNeg || x.e >= Ctor.toExpPos); + return x.isNeg() ? "-" + str : str; +}; +function digitsToString(d) { + var i, k, ws, indexOfLastWord = d.length - 1, str = "", w = d[0]; + if (indexOfLastWord > 0) { + str += w; + for (i = 1;i < indexOfLastWord; i++) { + ws = d[i] + ""; + k = LOG_BASE - ws.length; + if (k) + str += getZeroString(k); + str += ws; + } + w = d[i]; + ws = w + ""; + k = LOG_BASE - ws.length; + if (k) + str += getZeroString(k); + } else if (w === 0) { + return "0"; + } + for (;w % 10 === 0; ) + w /= 10; + return str + w; +} +function checkInt32(i, min, max) { + if (i !== ~~i || i < min || i > max) { + throw Error(invalidArgument + i); + } +} +function checkRoundingDigits(d, i, rm, repeating) { + var di, k, r, rd; + for (k = d[0];k >= 10; k /= 10) + --i; + if (--i < 0) { + i += LOG_BASE; + di = 0; + } else { + di = Math.ceil((i + 1) / LOG_BASE); + i %= LOG_BASE; + } + k = mathpow(10, LOG_BASE - i); + rd = d[di] % k | 0; + if (repeating == null) { + if (i < 3) { + if (i == 0) + rd = rd / 100 | 0; + else if (i == 1) + rd = rd / 10 | 0; + r = rm < 4 && rd == 99999 || rm > 3 && rd == 49999 || rd == 50000 || rd == 0; + } else { + r = (rm < 4 && rd + 1 == k || rm > 3 && rd + 1 == k / 2) && (d[di + 1] / k / 100 | 0) == mathpow(10, i - 2) - 1 || (rd == k / 2 || rd == 0) && (d[di + 1] / k / 100 | 0) == 0; + } + } else { + if (i < 4) { + if (i == 0) + rd = rd / 1000 | 0; + else if (i == 1) + rd = rd / 100 | 0; + else if (i == 2) + rd = rd / 10 | 0; + r = (repeating || rm < 4) && rd == 9999 || !repeating && rm > 3 && rd == 4999; + } else { + r = ((repeating || rm < 4) && rd + 1 == k || !repeating && rm > 3 && rd + 1 == k / 2) && (d[di + 1] / k / 1000 | 0) == mathpow(10, i - 3) - 1; + } + } + return r; +} +function convertBase(str, baseIn, baseOut) { + var j, arr = [0], arrL, i = 0, strL = str.length; + for (;i < strL; ) { + for (arrL = arr.length;arrL--; ) + arr[arrL] *= baseIn; + arr[0] += NUMERALS.indexOf(str.charAt(i++)); + for (j = 0;j < arr.length; j++) { + if (arr[j] > baseOut - 1) { + if (arr[j + 1] === undefined) + arr[j + 1] = 0; + arr[j + 1] += arr[j] / baseOut | 0; + arr[j] %= baseOut; + } + } + } + return arr.reverse(); +} +function cosine(Ctor, x) { + var k, len, y; + if (x.isZero()) + return x; + len = x.d.length; + if (len < 32) { + k = Math.ceil(len / 3); + y = (1 / tinyPow(4, k)).toString(); + } else { + k = 16; + y = "2.3283064365386962890625e-10"; + } + Ctor.precision += k; + x = taylorSeries(Ctor, 1, x.times(y), new Ctor(1)); + for (var i = k;i--; ) { + var cos2x = x.times(x); + x = cos2x.times(cos2x).minus(cos2x).times(8).plus(1); + } + Ctor.precision -= k; + return x; +} +var divide = function() { + function multiplyInteger(x, k, base) { + var temp, carry = 0, i = x.length; + for (x = x.slice();i--; ) { + temp = x[i] * k + carry; + x[i] = temp % base | 0; + carry = temp / base | 0; + } + if (carry) + x.unshift(carry); + return x; + } + function compare(a, b, aL, bL) { + var i, r; + if (aL != bL) { + r = aL > bL ? 1 : -1; + } else { + for (i = r = 0;i < aL; i++) { + if (a[i] != b[i]) { + r = a[i] > b[i] ? 1 : -1; + break; + } + } + } + return r; + } + function subtract(a, b, aL, base) { + var i = 0; + for (;aL--; ) { + a[aL] -= i; + i = a[aL] < b[aL] ? 1 : 0; + a[aL] = i * base + a[aL] - b[aL]; + } + for (;!a[0] && a.length > 1; ) + a.shift(); + } + return function(x, y, pr, rm, dp, base) { + var cmp, e, i, k, logBase, more, prod, prodL, q, qd, rem, remL, rem0, sd, t, xi, xL, yd0, yL, yz, Ctor = x.constructor, sign = x.s == y.s ? 1 : -1, xd = x.d, yd = y.d; + if (!xd || !xd[0] || !yd || !yd[0]) { + return new Ctor(!x.s || !y.s || (xd ? yd && xd[0] == yd[0] : !yd) ? NaN : xd && xd[0] == 0 || !yd ? sign * 0 : sign / 0); + } + if (base) { + logBase = 1; + e = x.e - y.e; + } else { + base = BASE; + logBase = LOG_BASE; + e = mathfloor(x.e / logBase) - mathfloor(y.e / logBase); + } + yL = yd.length; + xL = xd.length; + q = new Ctor(sign); + qd = q.d = []; + for (i = 0;yd[i] == (xd[i] || 0); i++) + ; + if (yd[i] > (xd[i] || 0)) + e--; + if (pr == null) { + sd = pr = Ctor.precision; + rm = Ctor.rounding; + } else if (dp) { + sd = pr + (x.e - y.e) + 1; + } else { + sd = pr; + } + if (sd < 0) { + qd.push(1); + more = true; + } else { + sd = sd / logBase + 2 | 0; + i = 0; + if (yL == 1) { + k = 0; + yd = yd[0]; + sd++; + for (;(i < xL || k) && sd--; i++) { + t = k * base + (xd[i] || 0); + qd[i] = t / yd | 0; + k = t % yd | 0; + } + more = k || i < xL; + } else { + k = base / (yd[0] + 1) | 0; + if (k > 1) { + yd = multiplyInteger(yd, k, base); + xd = multiplyInteger(xd, k, base); + yL = yd.length; + xL = xd.length; + } + xi = yL; + rem = xd.slice(0, yL); + remL = rem.length; + for (;remL < yL; ) + rem[remL++] = 0; + yz = yd.slice(); + yz.unshift(0); + yd0 = yd[0]; + if (yd[1] >= base / 2) + ++yd0; + do { + k = 0; + cmp = compare(yd, rem, yL, remL); + if (cmp < 0) { + rem0 = rem[0]; + if (yL != remL) + rem0 = rem0 * base + (rem[1] || 0); + k = rem0 / yd0 | 0; + if (k > 1) { + if (k >= base) + k = base - 1; + prod = multiplyInteger(yd, k, base); + prodL = prod.length; + remL = rem.length; + cmp = compare(prod, rem, prodL, remL); + if (cmp == 1) { + k--; + subtract(prod, yL < prodL ? yz : yd, prodL, base); + } + } else { + if (k == 0) + cmp = k = 1; + prod = yd.slice(); + } + prodL = prod.length; + if (prodL < remL) + prod.unshift(0); + subtract(rem, prod, remL, base); + if (cmp == -1) { + remL = rem.length; + cmp = compare(yd, rem, yL, remL); + if (cmp < 1) { + k++; + subtract(rem, yL < remL ? yz : yd, remL, base); + } + } + remL = rem.length; + } else if (cmp === 0) { + k++; + rem = [0]; + } + qd[i++] = k; + if (cmp && rem[0]) { + rem[remL++] = xd[xi] || 0; + } else { + rem = [xd[xi]]; + remL = 1; + } + } while ((xi++ < xL || rem[0] !== undefined) && sd--); + more = rem[0] !== undefined; + } + if (!qd[0]) + qd.shift(); + } + if (logBase == 1) { + q.e = e; + inexact = more; + } else { + for (i = 1, k = qd[0];k >= 10; k /= 10) + i++; + q.e = i + e * logBase - 1; + finalise(q, dp ? pr + q.e + 1 : pr, rm, more); + } + return q; + }; +}(); +function finalise(x, sd, rm, isTruncated) { + var digits, i, j, k, rd, roundUp, w, xd, xdi, Ctor = x.constructor; + out: + if (sd != null) { + xd = x.d; + if (!xd) + return x; + for (digits = 1, k = xd[0];k >= 10; k /= 10) + digits++; + i = sd - digits; + if (i < 0) { + i += LOG_BASE; + j = sd; + w = xd[xdi = 0]; + rd = w / mathpow(10, digits - j - 1) % 10 | 0; + } else { + xdi = Math.ceil((i + 1) / LOG_BASE); + k = xd.length; + if (xdi >= k) { + if (isTruncated) { + for (;k++ <= xdi; ) + xd.push(0); + w = rd = 0; + digits = 1; + i %= LOG_BASE; + j = i - LOG_BASE + 1; + } else { + break out; + } + } else { + w = k = xd[xdi]; + for (digits = 1;k >= 10; k /= 10) + digits++; + i %= LOG_BASE; + j = i - LOG_BASE + digits; + rd = j < 0 ? 0 : w / mathpow(10, digits - j - 1) % 10 | 0; + } + } + isTruncated = isTruncated || sd < 0 || xd[xdi + 1] !== undefined || (j < 0 ? w : w % mathpow(10, digits - j - 1)); + roundUp = rm < 4 ? (rd || isTruncated) && (rm == 0 || rm == (x.s < 0 ? 3 : 2)) : rd > 5 || rd == 5 && (rm == 4 || isTruncated || rm == 6 && (i > 0 ? j > 0 ? w / mathpow(10, digits - j) : 0 : xd[xdi - 1]) % 10 & 1 || rm == (x.s < 0 ? 8 : 7)); + if (sd < 1 || !xd[0]) { + xd.length = 0; + if (roundUp) { + sd -= x.e + 1; + xd[0] = mathpow(10, (LOG_BASE - sd % LOG_BASE) % LOG_BASE); + x.e = -sd || 0; + } else { + xd[0] = x.e = 0; + } + return x; + } + if (i == 0) { + xd.length = xdi; + k = 1; + xdi--; + } else { + xd.length = xdi + 1; + k = mathpow(10, LOG_BASE - i); + xd[xdi] = j > 0 ? (w / mathpow(10, digits - j) % mathpow(10, j) | 0) * k : 0; + } + if (roundUp) { + for (;; ) { + if (xdi == 0) { + for (i = 1, j = xd[0];j >= 10; j /= 10) + i++; + j = xd[0] += k; + for (k = 1;j >= 10; j /= 10) + k++; + if (i != k) { + x.e++; + if (xd[0] == BASE) + xd[0] = 1; + } + break; + } else { + xd[xdi] += k; + if (xd[xdi] != BASE) + break; + xd[xdi--] = 0; + k = 1; + } + } + } + for (i = xd.length;xd[--i] === 0; ) + xd.pop(); + } + if (external) { + if (x.e > Ctor.maxE) { + x.d = null; + x.e = NaN; + } else if (x.e < Ctor.minE) { + x.e = 0; + x.d = [0]; + } + } + return x; +} +function finiteToString(x, isExp, sd) { + if (!x.isFinite()) + return nonFiniteToString(x); + var k, e = x.e, str = digitsToString(x.d), len = str.length; + if (isExp) { + if (sd && (k = sd - len) > 0) { + str = str.charAt(0) + "." + str.slice(1) + getZeroString(k); + } else if (len > 1) { + str = str.charAt(0) + "." + str.slice(1); + } + str = str + (x.e < 0 ? "e" : "e+") + x.e; + } else if (e < 0) { + str = "0." + getZeroString(-e - 1) + str; + if (sd && (k = sd - len) > 0) + str += getZeroString(k); + } else if (e >= len) { + str += getZeroString(e + 1 - len); + if (sd && (k = sd - e - 1) > 0) + str = str + "." + getZeroString(k); + } else { + if ((k = e + 1) < len) + str = str.slice(0, k) + "." + str.slice(k); + if (sd && (k = sd - len) > 0) { + if (e + 1 === len) + str += "."; + str += getZeroString(k); + } + } + return str; +} +function getBase10Exponent(digits, e) { + var w = digits[0]; + for (e *= LOG_BASE;w >= 10; w /= 10) + e++; + return e; +} +function getLn10(Ctor, sd, pr) { + if (sd > LN10_PRECISION) { + external = true; + if (pr) + Ctor.precision = pr; + throw Error(precisionLimitExceeded); + } + return finalise(new Ctor(LN10), sd, 1, true); +} +function getPi(Ctor, sd, rm) { + if (sd > PI_PRECISION) + throw Error(precisionLimitExceeded); + return finalise(new Ctor(PI), sd, rm, true); +} +function getPrecision(digits) { + var w = digits.length - 1, len = w * LOG_BASE + 1; + w = digits[w]; + if (w) { + for (;w % 10 == 0; w /= 10) + len--; + for (w = digits[0];w >= 10; w /= 10) + len++; + } + return len; +} +function getZeroString(k) { + var zs = ""; + for (;k--; ) + zs += "0"; + return zs; +} +function intPow(Ctor, x, n, pr) { + var isTruncated, r = new Ctor(1), k = Math.ceil(pr / LOG_BASE + 4); + external = false; + for (;; ) { + if (n % 2) { + r = r.times(x); + if (truncate(r.d, k)) + isTruncated = true; + } + n = mathfloor(n / 2); + if (n === 0) { + n = r.d.length - 1; + if (isTruncated && r.d[n] === 0) + ++r.d[n]; + break; + } + x = x.times(x); + truncate(x.d, k); + } + external = true; + return r; +} +function isOdd(n) { + return n.d[n.d.length - 1] & 1; +} +function maxOrMin(Ctor, args, n) { + var k, y, x = new Ctor(args[0]), i = 0; + for (;++i < args.length; ) { + y = new Ctor(args[i]); + if (!y.s) { + x = y; + break; + } + k = x.cmp(y); + if (k === n || k === 0 && x.s === n) { + x = y; + } + } + return x; +} +function naturalExponential(x, sd) { + var denominator, guard, j, pow, sum, t, wpr, rep = 0, i = 0, k = 0, Ctor = x.constructor, rm = Ctor.rounding, pr = Ctor.precision; + if (!x.d || !x.d[0] || x.e > 17) { + return new Ctor(x.d ? !x.d[0] ? 1 : x.s < 0 ? 0 : 1 / 0 : x.s ? x.s < 0 ? 0 : x : 0 / 0); + } + if (sd == null) { + external = false; + wpr = pr; + } else { + wpr = sd; + } + t = new Ctor(0.03125); + while (x.e > -2) { + x = x.times(t); + k += 5; + } + guard = Math.log(mathpow(2, k)) / Math.LN10 * 2 + 5 | 0; + wpr += guard; + denominator = pow = sum = new Ctor(1); + Ctor.precision = wpr; + for (;; ) { + pow = finalise(pow.times(x), wpr, 1); + denominator = denominator.times(++i); + t = sum.plus(divide(pow, denominator, wpr, 1)); + if (digitsToString(t.d).slice(0, wpr) === digitsToString(sum.d).slice(0, wpr)) { + j = k; + while (j--) + sum = finalise(sum.times(sum), wpr, 1); + if (sd == null) { + if (rep < 3 && checkRoundingDigits(sum.d, wpr - guard, rm, rep)) { + Ctor.precision = wpr += 10; + denominator = pow = t = new Ctor(1); + i = 0; + rep++; + } else { + return finalise(sum, Ctor.precision = pr, rm, external = true); + } + } else { + Ctor.precision = pr; + return sum; + } + } + sum = t; + } +} +function naturalLogarithm(y, sd) { + var c, c0, denominator, e, numerator, rep, sum, t, wpr, x1, x2, n = 1, guard = 10, x = y, xd = x.d, Ctor = x.constructor, rm = Ctor.rounding, pr = Ctor.precision; + if (x.s < 0 || !xd || !xd[0] || !x.e && xd[0] == 1 && xd.length == 1) { + return new Ctor(xd && !xd[0] ? -1 / 0 : x.s != 1 ? NaN : xd ? 0 : x); + } + if (sd == null) { + external = false; + wpr = pr; + } else { + wpr = sd; + } + Ctor.precision = wpr += guard; + c = digitsToString(xd); + c0 = c.charAt(0); + if (Math.abs(e = x.e) < 1500000000000000) { + while (c0 < 7 && c0 != 1 || c0 == 1 && c.charAt(1) > 3) { + x = x.times(y); + c = digitsToString(x.d); + c0 = c.charAt(0); + n++; + } + e = x.e; + if (c0 > 1) { + x = new Ctor("0." + c); + e++; + } else { + x = new Ctor(c0 + "." + c.slice(1)); + } + } else { + t = getLn10(Ctor, wpr + 2, pr).times(e + ""); + x = naturalLogarithm(new Ctor(c0 + "." + c.slice(1)), wpr - guard).plus(t); + Ctor.precision = pr; + return sd == null ? finalise(x, pr, rm, external = true) : x; + } + x1 = x; + sum = numerator = x = divide(x.minus(1), x.plus(1), wpr, 1); + x2 = finalise(x.times(x), wpr, 1); + denominator = 3; + for (;; ) { + numerator = finalise(numerator.times(x2), wpr, 1); + t = sum.plus(divide(numerator, new Ctor(denominator), wpr, 1)); + if (digitsToString(t.d).slice(0, wpr) === digitsToString(sum.d).slice(0, wpr)) { + sum = sum.times(2); + if (e !== 0) + sum = sum.plus(getLn10(Ctor, wpr + 2, pr).times(e + "")); + sum = divide(sum, new Ctor(n), wpr, 1); + if (sd == null) { + if (checkRoundingDigits(sum.d, wpr - guard, rm, rep)) { + Ctor.precision = wpr += guard; + t = numerator = x = divide(x1.minus(1), x1.plus(1), wpr, 1); + x2 = finalise(x.times(x), wpr, 1); + denominator = rep = 1; + } else { + return finalise(sum, Ctor.precision = pr, rm, external = true); + } + } else { + Ctor.precision = pr; + return sum; + } + } + sum = t; + denominator += 2; + } +} +function nonFiniteToString(x) { + return String(x.s * x.s / 0); +} +function parseDecimal(x, str) { + var e, i, len; + if ((e = str.indexOf(".")) > -1) + str = str.replace(".", ""); + if ((i = str.search(/e/i)) > 0) { + if (e < 0) + e = i; + e += +str.slice(i + 1); + str = str.substring(0, i); + } else if (e < 0) { + e = str.length; + } + for (i = 0;str.charCodeAt(i) === 48; i++) + ; + for (len = str.length;str.charCodeAt(len - 1) === 48; --len) + ; + str = str.slice(i, len); + if (str) { + len -= i; + x.e = e = e - i - 1; + x.d = []; + i = (e + 1) % LOG_BASE; + if (e < 0) + i += LOG_BASE; + if (i < len) { + if (i) + x.d.push(+str.slice(0, i)); + for (len -= LOG_BASE;i < len; ) + x.d.push(+str.slice(i, i += LOG_BASE)); + str = str.slice(i); + i = LOG_BASE - str.length; + } else { + i -= len; + } + for (;i--; ) + str += "0"; + x.d.push(+str); + if (external) { + if (x.e > x.constructor.maxE) { + x.d = null; + x.e = NaN; + } else if (x.e < x.constructor.minE) { + x.e = 0; + x.d = [0]; + } + } + } else { + x.e = 0; + x.d = [0]; + } + return x; +} +function parseOther(x, str) { + var base, Ctor, divisor, i, isFloat, len, p, xd, xe; + if (str.indexOf("_") > -1) { + str = str.replace(/(\d)_(?=\d)/g, "$1"); + if (isDecimal.test(str)) + return parseDecimal(x, str); + } else if (str === "Infinity" || str === "NaN") { + if (!+str) + x.s = NaN; + x.e = NaN; + x.d = null; + return x; + } + if (isHex.test(str)) { + base = 16; + str = str.toLowerCase(); + } else if (isBinary.test(str)) { + base = 2; + } else if (isOctal.test(str)) { + base = 8; + } else { + throw Error(invalidArgument + str); + } + i = str.search(/p/i); + if (i > 0) { + p = +str.slice(i + 1); + str = str.substring(2, i); + } else { + str = str.slice(2); + } + i = str.indexOf("."); + isFloat = i >= 0; + Ctor = x.constructor; + if (isFloat) { + str = str.replace(".", ""); + len = str.length; + i = len - i; + divisor = intPow(Ctor, new Ctor(base), i, i * 2); + } + xd = convertBase(str, base, BASE); + xe = xd.length - 1; + for (i = xe;xd[i] === 0; --i) + xd.pop(); + if (i < 0) + return new Ctor(x.s * 0); + x.e = getBase10Exponent(xd, xe); + x.d = xd; + external = false; + if (isFloat) + x = divide(x, divisor, len * 4); + if (p) + x = x.times(Math.abs(p) < 54 ? mathpow(2, p) : Decimal.pow(2, p)); + external = true; + return x; +} +function sine(Ctor, x) { + var k, len = x.d.length; + if (len < 3) { + return x.isZero() ? x : taylorSeries(Ctor, 2, x, x); + } + k = 1.4 * Math.sqrt(len); + k = k > 16 ? 16 : k | 0; + x = x.times(1 / tinyPow(5, k)); + x = taylorSeries(Ctor, 2, x, x); + var sin2_x, d5 = new Ctor(5), d16 = new Ctor(16), d20 = new Ctor(20); + for (;k--; ) { + sin2_x = x.times(x); + x = x.times(d5.plus(sin2_x.times(d16.times(sin2_x).minus(d20)))); + } + return x; +} +function taylorSeries(Ctor, n, x, y, isHyperbolic) { + var j, t, u, x2, i = 1, pr = Ctor.precision, k = Math.ceil(pr / LOG_BASE); + external = false; + x2 = x.times(x); + u = new Ctor(y); + for (;; ) { + t = divide(u.times(x2), new Ctor(n++ * n++), pr, 1); + u = isHyperbolic ? y.plus(t) : y.minus(t); + y = divide(t.times(x2), new Ctor(n++ * n++), pr, 1); + t = u.plus(y); + if (t.d[k] !== undefined) { + for (j = k;t.d[j] === u.d[j] && j--; ) + ; + if (j == -1) + break; + } + j = u; + u = y; + y = t; + t = j; + i++; + } + external = true; + t.d.length = k + 1; + return t; +} +function tinyPow(b, e) { + var n = b; + while (--e) + n *= b; + return n; +} +function toLessThanHalfPi(Ctor, x) { + var t, isNeg = x.s < 0, pi = getPi(Ctor, Ctor.precision, 1), halfPi = pi.times(0.5); + x = x.abs(); + if (x.lte(halfPi)) { + quadrant = isNeg ? 4 : 1; + return x; + } + t = x.divToInt(pi); + if (t.isZero()) { + quadrant = isNeg ? 3 : 2; + } else { + x = x.minus(t.times(pi)); + if (x.lte(halfPi)) { + quadrant = isOdd(t) ? isNeg ? 2 : 3 : isNeg ? 4 : 1; + return x; + } + quadrant = isOdd(t) ? isNeg ? 1 : 4 : isNeg ? 3 : 2; + } + return x.minus(pi).abs(); +} +function toStringBinary(x, baseOut, sd, rm) { + var base, e, i, k, len, roundUp, str, xd, y, Ctor = x.constructor, isExp = sd !== undefined; + if (isExp) { + checkInt32(sd, 1, MAX_DIGITS); + if (rm === undefined) + rm = Ctor.rounding; + else + checkInt32(rm, 0, 8); + } else { + sd = Ctor.precision; + rm = Ctor.rounding; + } + if (!x.isFinite()) { + str = nonFiniteToString(x); + } else { + str = finiteToString(x); + i = str.indexOf("."); + if (isExp) { + base = 2; + if (baseOut == 16) { + sd = sd * 4 - 3; + } else if (baseOut == 8) { + sd = sd * 3 - 2; + } + } else { + base = baseOut; + } + if (i >= 0) { + str = str.replace(".", ""); + y = new Ctor(1); + y.e = str.length - i; + y.d = convertBase(finiteToString(y), 10, base); + y.e = y.d.length; + } + xd = convertBase(str, 10, base); + e = len = xd.length; + for (;xd[--len] == 0; ) + xd.pop(); + if (!xd[0]) { + str = isExp ? "0p+0" : "0"; + } else { + if (i < 0) { + e--; + } else { + x = new Ctor(x); + x.d = xd; + x.e = e; + x = divide(x, y, sd, rm, 0, base); + xd = x.d; + e = x.e; + roundUp = inexact; + } + i = xd[sd]; + k = base / 2; + roundUp = roundUp || xd[sd + 1] !== undefined; + roundUp = rm < 4 ? (i !== undefined || roundUp) && (rm === 0 || rm === (x.s < 0 ? 3 : 2)) : i > k || i === k && (rm === 4 || roundUp || rm === 6 && xd[sd - 1] & 1 || rm === (x.s < 0 ? 8 : 7)); + xd.length = sd; + if (roundUp) { + for (;++xd[--sd] > base - 1; ) { + xd[sd] = 0; + if (!sd) { + ++e; + xd.unshift(1); + } + } + } + for (len = xd.length;!xd[len - 1]; --len) + ; + for (i = 0, str = "";i < len; i++) + str += NUMERALS.charAt(xd[i]); + if (isExp) { + if (len > 1) { + if (baseOut == 16 || baseOut == 8) { + i = baseOut == 16 ? 4 : 3; + for (--len;len % i; len++) + str += "0"; + xd = convertBase(str, base, baseOut); + for (len = xd.length;!xd[len - 1]; --len) + ; + for (i = 1, str = "1.";i < len; i++) + str += NUMERALS.charAt(xd[i]); + } else { + str = str.charAt(0) + "." + str.slice(1); + } + } + str = str + (e < 0 ? "p" : "p+") + e; + } else if (e < 0) { + for (;++e; ) + str = "0" + str; + str = "0." + str; + } else { + if (++e > len) + for (e -= len;e--; ) + str += "0"; + else if (e < len) + str = str.slice(0, e) + "." + str.slice(e); + } + } + str = (baseOut == 16 ? "0x" : baseOut == 2 ? "0b" : baseOut == 8 ? "0o" : "") + str; + } + return x.s < 0 ? "-" + str : str; +} +function truncate(arr, len) { + if (arr.length > len) { + arr.length = len; + return true; + } +} +function abs(x) { + return new this(x).abs(); +} +function acos(x) { + return new this(x).acos(); +} +function acosh(x) { + return new this(x).acosh(); +} +function add(x, y) { + return new this(x).plus(y); +} +function asin(x) { + return new this(x).asin(); +} +function asinh(x) { + return new this(x).asinh(); +} +function atan(x) { + return new this(x).atan(); +} +function atanh(x) { + return new this(x).atanh(); +} +function atan2(y, x) { + y = new this(y); + x = new this(x); + var r, pr = this.precision, rm = this.rounding, wpr = pr + 4; + if (!y.s || !x.s) { + r = new this(NaN); + } else if (!y.d && !x.d) { + r = getPi(this, wpr, 1).times(x.s > 0 ? 0.25 : 0.75); + r.s = y.s; + } else if (!x.d || y.isZero()) { + r = x.s < 0 ? getPi(this, pr, rm) : new this(0); + r.s = y.s; + } else if (!y.d || x.isZero()) { + r = getPi(this, wpr, 1).times(0.5); + r.s = y.s; + } else if (x.s < 0) { + this.precision = wpr; + this.rounding = 1; + r = this.atan(divide(y, x, wpr, 1)); + x = getPi(this, wpr, 1); + this.precision = pr; + this.rounding = rm; + r = y.s < 0 ? r.minus(x) : r.plus(x); + } else { + r = this.atan(divide(y, x, wpr, 1)); + } + return r; +} +function cbrt(x) { + return new this(x).cbrt(); +} +function ceil(x) { + return finalise(x = new this(x), x.e + 1, 2); +} +function clamp(x, min, max) { + return new this(x).clamp(min, max); +} +function config(obj) { + if (!obj || typeof obj !== "object") + throw Error(decimalError + "Object expected"); + var i, p, v, useDefaults = obj.defaults === true, ps = [ + "precision", + 1, + MAX_DIGITS, + "rounding", + 0, + 8, + "toExpNeg", + -EXP_LIMIT, + 0, + "toExpPos", + 0, + EXP_LIMIT, + "maxE", + 0, + EXP_LIMIT, + "minE", + -EXP_LIMIT, + 0, + "modulo", + 0, + 9 + ]; + for (i = 0;i < ps.length; i += 3) { + if (p = ps[i], useDefaults) + this[p] = DEFAULTS[p]; + if ((v = obj[p]) !== undefined) { + if (mathfloor(v) === v && v >= ps[i + 1] && v <= ps[i + 2]) + this[p] = v; + else + throw Error(invalidArgument + p + ": " + v); + } + } + if (p = "crypto", useDefaults) + this[p] = DEFAULTS[p]; + if ((v = obj[p]) !== undefined) { + if (v === true || v === false || v === 0 || v === 1) { + if (v) { + if (typeof crypto != "undefined" && crypto && (crypto.getRandomValues || crypto.randomBytes)) { + this[p] = true; + } else { + throw Error(cryptoUnavailable); + } + } else { + this[p] = false; + } + } else { + throw Error(invalidArgument + p + ": " + v); + } + } + return this; +} +function cos(x) { + return new this(x).cos(); +} +function cosh(x) { + return new this(x).cosh(); +} +function clone(obj) { + var i, p, ps; + function Decimal(v) { + var e, i2, t, x = this; + if (!(x instanceof Decimal)) + return new Decimal(v); + x.constructor = Decimal; + if (isDecimalInstance(v)) { + x.s = v.s; + if (external) { + if (!v.d || v.e > Decimal.maxE) { + x.e = NaN; + x.d = null; + } else if (v.e < Decimal.minE) { + x.e = 0; + x.d = [0]; + } else { + x.e = v.e; + x.d = v.d.slice(); + } + } else { + x.e = v.e; + x.d = v.d ? v.d.slice() : v.d; + } + return; + } + t = typeof v; + if (t === "number") { + if (v === 0) { + x.s = 1 / v < 0 ? -1 : 1; + x.e = 0; + x.d = [0]; + return; + } + if (v < 0) { + v = -v; + x.s = -1; + } else { + x.s = 1; + } + if (v === ~~v && v < 1e7) { + for (e = 0, i2 = v;i2 >= 10; i2 /= 10) + e++; + if (external) { + if (e > Decimal.maxE) { + x.e = NaN; + x.d = null; + } else if (e < Decimal.minE) { + x.e = 0; + x.d = [0]; + } else { + x.e = e; + x.d = [v]; + } + } else { + x.e = e; + x.d = [v]; + } + return; + } + if (v * 0 !== 0) { + if (!v) + x.s = NaN; + x.e = NaN; + x.d = null; + return; + } + return parseDecimal(x, v.toString()); + } + if (t === "string") { + if ((i2 = v.charCodeAt(0)) === 45) { + v = v.slice(1); + x.s = -1; + } else { + if (i2 === 43) + v = v.slice(1); + x.s = 1; + } + return isDecimal.test(v) ? parseDecimal(x, v) : parseOther(x, v); + } + if (t === "bigint") { + if (v < 0) { + v = -v; + x.s = -1; + } else { + x.s = 1; + } + return parseDecimal(x, v.toString()); + } + throw Error(invalidArgument + v); + } + Decimal.prototype = P; + Decimal.ROUND_UP = 0; + Decimal.ROUND_DOWN = 1; + Decimal.ROUND_CEIL = 2; + Decimal.ROUND_FLOOR = 3; + Decimal.ROUND_HALF_UP = 4; + Decimal.ROUND_HALF_DOWN = 5; + Decimal.ROUND_HALF_EVEN = 6; + Decimal.ROUND_HALF_CEIL = 7; + Decimal.ROUND_HALF_FLOOR = 8; + Decimal.EUCLID = 9; + Decimal.config = Decimal.set = config; + Decimal.clone = clone; + Decimal.isDecimal = isDecimalInstance; + Decimal.abs = abs; + Decimal.acos = acos; + Decimal.acosh = acosh; + Decimal.add = add; + Decimal.asin = asin; + Decimal.asinh = asinh; + Decimal.atan = atan; + Decimal.atanh = atanh; + Decimal.atan2 = atan2; + Decimal.cbrt = cbrt; + Decimal.ceil = ceil; + Decimal.clamp = clamp; + Decimal.cos = cos; + Decimal.cosh = cosh; + Decimal.div = div; + Decimal.exp = exp; + Decimal.floor = floor; + Decimal.hypot = hypot; + Decimal.ln = ln; + Decimal.log = log; + Decimal.log10 = log10; + Decimal.log2 = log2; + Decimal.max = max; + Decimal.min = min; + Decimal.mod = mod; + Decimal.mul = mul; + Decimal.pow = pow; + Decimal.random = random; + Decimal.round = round; + Decimal.sign = sign; + Decimal.sin = sin; + Decimal.sinh = sinh; + Decimal.sqrt = sqrt; + Decimal.sub = sub; + Decimal.sum = sum; + Decimal.tan = tan; + Decimal.tanh = tanh; + Decimal.trunc = trunc; + if (obj === undefined) + obj = {}; + if (obj) { + if (obj.defaults !== true) { + ps = ["precision", "rounding", "toExpNeg", "toExpPos", "maxE", "minE", "modulo", "crypto"]; + for (i = 0;i < ps.length; ) + if (!obj.hasOwnProperty(p = ps[i++])) + obj[p] = this[p]; + } + } + Decimal.config(obj); + return Decimal; +} +function div(x, y) { + return new this(x).div(y); +} +function exp(x) { + return new this(x).exp(); +} +function floor(x) { + return finalise(x = new this(x), x.e + 1, 3); +} +function hypot() { + var i, n, t = new this(0); + external = false; + for (i = 0;i < arguments.length; ) { + n = new this(arguments[i++]); + if (!n.d) { + if (n.s) { + external = true; + return new this(1 / 0); + } + t = n; + } else if (t.d) { + t = t.plus(n.times(n)); + } + } + external = true; + return t.sqrt(); +} +function isDecimalInstance(obj) { + return obj instanceof Decimal || obj && obj.toStringTag === tag || false; +} +function ln(x) { + return new this(x).ln(); +} +function log(x, y) { + return new this(x).log(y); +} +function log2(x) { + return new this(x).log(2); +} +function log10(x) { + return new this(x).log(10); +} +function max() { + return maxOrMin(this, arguments, -1); +} +function min() { + return maxOrMin(this, arguments, 1); +} +function mod(x, y) { + return new this(x).mod(y); +} +function mul(x, y) { + return new this(x).mul(y); +} +function pow(x, y) { + return new this(x).pow(y); +} +function random(sd) { + var d, e, k, n, i = 0, r = new this(1), rd = []; + if (sd === undefined) + sd = this.precision; + else + checkInt32(sd, 1, MAX_DIGITS); + k = Math.ceil(sd / LOG_BASE); + if (!this.crypto) { + for (;i < k; ) + rd[i++] = Math.random() * 1e7 | 0; + } else if (crypto.getRandomValues) { + d = crypto.getRandomValues(new Uint32Array(k)); + for (;i < k; ) { + n = d[i]; + if (n >= 4290000000) { + d[i] = crypto.getRandomValues(new Uint32Array(1))[0]; + } else { + rd[i++] = n % 1e7; + } + } + } else if (crypto.randomBytes) { + d = crypto.randomBytes(k *= 4); + for (;i < k; ) { + n = d[i] + (d[i + 1] << 8) + (d[i + 2] << 16) + ((d[i + 3] & 127) << 24); + if (n >= 2140000000) { + crypto.randomBytes(4).copy(d, i); + } else { + rd.push(n % 1e7); + i += 4; + } + } + i = k / 4; + } else { + throw Error(cryptoUnavailable); + } + k = rd[--i]; + sd %= LOG_BASE; + if (k && sd) { + n = mathpow(10, LOG_BASE - sd); + rd[i] = (k / n | 0) * n; + } + for (;rd[i] === 0; i--) + rd.pop(); + if (i < 0) { + e = 0; + rd = [0]; + } else { + e = -1; + for (;rd[0] === 0; e -= LOG_BASE) + rd.shift(); + for (k = 1, n = rd[0];n >= 10; n /= 10) + k++; + if (k < LOG_BASE) + e -= LOG_BASE - k; + } + r.e = e; + r.d = rd; + return r; +} +function round(x) { + return finalise(x = new this(x), x.e + 1, this.rounding); +} +function sign(x) { + x = new this(x); + return x.d ? x.d[0] ? x.s : 0 * x.s : x.s || NaN; +} +function sin(x) { + return new this(x).sin(); +} +function sinh(x) { + return new this(x).sinh(); +} +function sqrt(x) { + return new this(x).sqrt(); +} +function sub(x, y) { + return new this(x).sub(y); +} +function sum() { + var i = 0, args = arguments, x = new this(args[i]); + external = false; + for (;x.s && ++i < args.length; ) + x = x.plus(args[i]); + external = true; + return finalise(x, this.precision, this.rounding); +} +function tan(x) { + return new this(x).tan(); +} +function tanh(x) { + return new this(x).tanh(); +} +function trunc(x) { + return finalise(x = new this(x), x.e + 1, 1); +} +P[Symbol.for("nodejs.util.inspect.custom")] = P.toString; +P[Symbol.toStringTag] = "Decimal"; +var Decimal = P.constructor = clone(DEFAULTS); +LN10 = new Decimal(LN10); +PI = new Decimal(PI); +var decimal_default = Decimal; + +// src/colour/hex-to-hsl.ts +var hexToHsl = (hex) => { + const red = new decimal_default(parseInt(hex.slice(1, 3), 16)).div(255); + const green = new decimal_default(parseInt(hex.slice(3, 5), 16)).div(255); + const blue = new decimal_default(parseInt(hex.slice(5, 7), 16)).div(255); + const cmin = decimal_default.min(red, green, blue); + const cmax = decimal_default.max(red, green, blue); + const delta = cmax.minus(cmin); + let h = new decimal_default(0); + let s = new decimal_default(0); + let l = cmax.plus(cmin).div(2); + if (!delta.isZero()) { + if (cmax.equals(red)) { + h = green.minus(blue).div(delta).mod(6); + } else if (cmax.equals(green)) { + h = blue.minus(red).div(delta).plus(2); + } else { + h = red.minus(green).div(delta).plus(4); + } + h = h.times(60); + if (h.isNegative()) + h = h.plus(360); + } + if (!delta.isZero()) { + s = delta.div(new decimal_default(1).minus(l.times(2).minus(1).abs())); + } + const hFinal = h.toDecimalPlaces(0, decimal_default.ROUND_HALF_UP).toNumber(); + const sFinal = s.times(100).toDecimalPlaces(0, decimal_default.ROUND_HALF_UP).toNumber(); + const lFinal = l.times(100).toDecimalPlaces(0, decimal_default.ROUND_HALF_UP).toNumber(); + return `hsl(${hFinal}, ${sFinal}%, ${lFinal}%)`; +}; +// src/numbers/scientific-notation.ts +var toScientificNotation = (num, precision = 0) => { + const exponential = precision > 0 ? Number(num).toExponential(precision) : Number(num).toExponential(0); + const match = exponential.match(/^(-?\d+(?:\.\d+)?)e([+-]?\d+)$/); + if (!match) { + return num.toString(); + } + let coefficient = match[1]; + const exponent = parseInt(match[2], 10).toString(); + if (precision === 0) { + coefficient = Math.round(Number(coefficient)).toString(); + } + return `${coefficient} × 10${exponent}`; +}; +// src/units/temperatre-converter.ts +class TemperatureConverter { + static CELSIUS_TO_FAHRENHEIT = 1.8; + static FAHRENHEIT_TO_CELSIUS = 0.5555555555555556; + static celsiusToFahrenheit(celsius) { + return celsius * this.CELSIUS_TO_FAHRENHEIT + 32; + } + static fahrenheitToCelsius(fahrenheit) { + return (fahrenheit - 32) * this.FAHRENHEIT_TO_CELSIUS; + } + static celsiusFanOvenToFahrenheit(celsiusFanOven) { + const celsius = new Decimal(celsiusFanOven).div(0.9); + return this.celsiusToFahrenheit(celsius.toNumber()); + } + static fahrenheitToCelsiusFanOven(fahrenheit) { + const celsius = new Decimal(this.fahrenheitToCelsius(fahrenheit)); + return celsius.mul(0.9).toNumber(); + } + static celsiusToCelsiusFanOven(celsius) { + const c = new Decimal(celsius); + return c.mul(0.9).toNumber(); + } + static celsiusFanOvenToCelsius(celsiusFanOven) { + const c = new Decimal(celsiusFanOven); + return c.div(0.9).toNumber(); + } +} + +// src/units/mass-converter.ts +class MassConverter { + static GRAMS_TO_OUNCES = new Decimal(0.035274); + static KILOGRAMS_TO_POUNDS = new Decimal(2.20462); + static KILOGRAMS_TO_OUNCES = new Decimal(35.274); + static kilogramsToPounds(kilos) { + const result = new Decimal(kilos).mul(this.KILOGRAMS_TO_POUNDS); + return Number(result.toFixed(5)); + } + static poundsToKilograms(pounds) { + const result = new Decimal(pounds).div(this.KILOGRAMS_TO_POUNDS); + return Number(result.toFixed(5)); + } + static gramsToKilograms(grams) { + const result = new Decimal(grams).div(1000); + return Number(result.toFixed(5)); + } + static kilogramsToGrams(kilos) { + const result = new Decimal(kilos).mul(1000); + return Number(result.toFixed(5)); + } + static gramsToOunces(grams) { + const result = new Decimal(grams).mul(this.GRAMS_TO_OUNCES); + return Number(result.toFixed(5)); + } + static ouncesToGrams(ounces) { + const result = new Decimal(ounces).div(this.GRAMS_TO_OUNCES); + return Number(result.toFixed(5)); + } + static ouncesToPounds(ounces) { + const result = new Decimal(ounces).div(16); + return Number(result.toFixed(5)); + } + static poundsToOunces(pounds) { + const result = new Decimal(pounds).mul(16); + return Number(result.toFixed(5)); + } + static gramsToPounds(grams) { + const result = new Decimal(grams).mul(this.KILOGRAMS_TO_POUNDS).div(1000); + return Number(result.toFixed(5)); + } + static poundsToGrams(pounds) { + const result = new Decimal(pounds).div(this.KILOGRAMS_TO_POUNDS).mul(1000); + return Number(result.toFixed(5)); + } + static kilogramsToOunces(kilos) { + const result = new Decimal(kilos).mul(this.KILOGRAMS_TO_OUNCES); + return Number(result.toFixed(5)); + } + static ouncesToKilograms(ounces) { + const result = new Decimal(ounces).div(this.KILOGRAMS_TO_OUNCES); + return Number(result.toFixed(5)); + } +} + +// src/units/volume-converter.ts +class VolumeConverter { + static LITRES_TO_PINTS = new Decimal(1.75975); + static PINTS_TO_LITRES = new Decimal(0.568261); + static LITRES_TO_FLUID_OUNCES = new Decimal(33.814); + static FLUID_OUNCES_TO_LITRES = new Decimal(0.0295735); + static GALLONS_TO_PINTS = new Decimal(8); + static PINTS_TO_GALLONS = new Decimal(0.125); + static GALLONS_TO_MILLILITRES = new Decimal(3785.41); + static MILLILITRES_TO_GALLONS = new Decimal(0.000264172); + static PINTS_TO_FLUID_OUNCES = new Decimal(20); + static FLUID_OUNCES_TO_PINTS = new Decimal(0.05); + static MILLILITRE_TO_FLUID_OUNCE = new Decimal(0.033814); + static LITRE_TO_GALLON = new Decimal(0.264172); + static MILLILITRE_TO_PINT = new Decimal(0.00176); + static litresToPints(litres) { + const result = new Decimal(litres).mul(this.LITRES_TO_PINTS); + return Number(result.toFixed(5)); + } + static pintsToLitres(pints) { + const result = new Decimal(pints).mul(this.PINTS_TO_LITRES); + return Number(result.toFixed(5)); + } + static litresToFluidOunces(litres) { + const result = new Decimal(litres).mul(this.LITRES_TO_FLUID_OUNCES); + return Number(result.toFixed(5)); + } + static fluidOuncesToLitres(fluidOunces) { + const result = new Decimal(fluidOunces).mul(this.FLUID_OUNCES_TO_LITRES); + return Number(result.toFixed(5)); + } + static gallonsToPints(gallons) { + const result = new Decimal(gallons).mul(this.GALLONS_TO_PINTS); + return Number(result.toFixed(5)); + } + static pintsToGallons(pints) { + const result = new Decimal(pints).mul(this.PINTS_TO_GALLONS); + return Number(result.toFixed(5)); + } + static gallonsToMillilitres(gallons) { + const result = new Decimal(gallons).mul(this.GALLONS_TO_MILLILITRES); + return Number(result.toFixed(5)); + } + static millilitresToGallons(millilitres) { + const result = new Decimal(millilitres).mul(this.MILLILITRES_TO_GALLONS); + return Number(result.toFixed(5)); + } + static pintsToFluidOunces(pints) { + const result = new Decimal(pints).mul(this.PINTS_TO_FLUID_OUNCES); + return Number(result.toFixed(5)); + } + static fluidOuncesToPints(fluidOunces) { + const result = new Decimal(fluidOunces).mul(this.FLUID_OUNCES_TO_PINTS); + return Number(result.toFixed(5)); + } + static millilitresToFluidOunces(millilitres) { + const result = new Decimal(millilitres).mul(this.MILLILITRE_TO_FLUID_OUNCE); + return Number(result.toFixed(5)); + } + static fluidOuncesToMillilitres(fluidOunces) { + const result = new Decimal(fluidOunces).div(this.MILLILITRE_TO_FLUID_OUNCE); + return Number(result.toFixed(5)); + } + static litresToGallons(litres) { + const result = new Decimal(litres).mul(this.LITRE_TO_GALLON); + return Number(result.toFixed(5)); + } + static litresToMillilitres(litres) { + const result = new Decimal(litres).mul(1000); + return Number(result.toFixed(5)); + } + static millilitresToLitres(millilitres) { + const result = new Decimal(millilitres).div(1000); + return Number(result.toFixed(5)); + } + static gallonsToLitres(gallons) { + const result = new Decimal(gallons).div(this.LITRE_TO_GALLON); + return Number(result.toFixed(5)); + } + static millilitresToPints(millilitres) { + const result = new Decimal(millilitres).mul(this.MILLILITRE_TO_PINT); + return Number(result.toFixed(5)); + } + static pintsToMillilitres(pints) { + const result = new Decimal(pints).div(this.MILLILITRE_TO_PINT); + return Number(result.toFixed(5)); + } + static gallonsToFluidOunces(gallons) { + const litres = this.gallonsToLitres(gallons); + const millilitres = this.litresToMillilitres(litres); + return this.millilitresToFluidOunces(millilitres); + } + static fluidOuncesToGallons(fluidOunces) { + const millilitres = this.fluidOuncesToMillilitres(fluidOunces); + const litres = this.millilitresToLitres(millilitres); + return this.litresToGallons(litres); + } +} + +// src/units/model.ts +var MASS_UNITS = { + GRAM: { long: "gram", short: "g" }, + KILOGRAM: { long: "kilogram", short: "kg" }, + OUNCE: { long: "ounce", short: "oz" }, + POUND: { long: "pound", short: "lb" } +}; +var TEMPERATURE_UNITS = { + CELSIUS: { long: "celsius", short: "c" }, + CELSIUS_FAN_OVEN: { long: "celsius (fan oven)", short: "c (fan)" }, + FAHRENHEIT: { long: "fahrenheit", short: "f" } +}; +var VOLUME_UNITS = { + MILLILITRE: { long: "millilitre", short: "ml" }, + FLUID_OUNCE: { long: "fluid ounce", short: "fl oz" }, + LITRE: { long: "litre", short: "l" }, + GALLON: { long: "gallon", short: "gal" }, + PINT: { long: "pint", short: "pt" } +}; +var DISTANCE_UNITS = { + CENTIMETRE: { long: "centimetre", short: "cm" }, + METRE: { long: "metre", short: "m" }, + KILOMETRE: { long: "kilometre", short: "km" }, + INCH: { long: "inch", short: "in" }, + FOOT: { long: "foot", short: "ft" }, + YARD: { long: "yard", short: "yd" }, + MILE: { long: "mile", short: "mi" } +}; +function isMassUnit(unit) { + return Object.values(MASS_UNITS).some((u) => u.short === unit); +} +function isTemperatureUnit(unit) { + return Object.values(TEMPERATURE_UNITS).some((u) => u.short === unit); +} +function isVolumeUnit(unit) { + return Object.values(VOLUME_UNITS).some((u) => u.short === unit); +} +function isDistanceUnit(unit) { + return Object.values(DISTANCE_UNITS).some((u) => u.short === unit); +} +var unitMap = { + ...MASS_UNITS, + ...TEMPERATURE_UNITS, + ...VOLUME_UNITS, + ...DISTANCE_UNITS +}; + +// src/units/distance-converter.ts +class DistanceConverter { + static METRES_TO_YARDS = new Decimal(1.09361); + static KILOMETRES_TO_MILES = new Decimal(0.621371); + static CENTIMETRES_TO_INCHES = new Decimal(0.393701); + static METRES_TO_FEET = new Decimal(3.28084); + static CENTIMETRES_TO_FEET = new Decimal(0.0328084); + static FEET_TO_INCHES = new Decimal(12); + static metresToYards(metres) { + const result = new Decimal(metres).mul(this.METRES_TO_YARDS); + return Number(result.toFixed(5)); + } + static metresToFeet(metres) { + const result = new Decimal(metres).mul(this.METRES_TO_FEET); + return Number(result.toFixed(5)); + } + static feetToMetres(feet) { + const result = new Decimal(feet).div(this.METRES_TO_FEET); + return Number(result.toFixed(5)); + } + static yardsToMetres(yards) { + const result = new Decimal(yards).div(this.METRES_TO_YARDS); + return Number(result.toFixed(5)); + } + static kilometresToMiles(kilometres) { + const result = new Decimal(kilometres).mul(this.KILOMETRES_TO_MILES); + return Number(result.toFixed(5)); + } + static milesToKilometres(miles) { + const result = new Decimal(miles).div(this.KILOMETRES_TO_MILES); + return Number(result.toFixed(5)); + } + static centimetresToInches(centimetres) { + const result = new Decimal(centimetres).mul(this.CENTIMETRES_TO_INCHES); + return Number(result.toFixed(5)); + } + static centimetresToFeet(centimetres) { + const result = new Decimal(centimetres).mul(this.CENTIMETRES_TO_FEET); + return Number(result.toFixed(5)); + } + static feetToCentimetres(feet) { + const result = new Decimal(feet).div(this.CENTIMETRES_TO_FEET); + return Number(result.toFixed(5)); + } + static inchesToCentimetres(inches) { + const result = new Decimal(inches).div(this.CENTIMETRES_TO_INCHES); + return Number(result.toFixed(5)); + } + static feetToInches(feet) { + const result = new Decimal(feet).mul(this.FEET_TO_INCHES); + return Number(result.toFixed(5)); + } + static inchesToFeet(inches) { + const result = new Decimal(inches).div(this.FEET_TO_INCHES); + return Number(result.toFixed(5)); + } + static centimetresToMetres(centimetres) { + const result = new Decimal(centimetres).div(100); + return Number(result.toFixed(5)); + } + static metresToCentimetres(metres) { + const result = new Decimal(metres).mul(100); + return Number(result.toFixed(5)); + } + static kilometresToMetres(kilometres) { + const result = new Decimal(kilometres).mul(1000); + return Number(result.toFixed(5)); + } + static metresToKilometres(metres) { + const result = new Decimal(metres).div(1000); + return Number(result.toFixed(5)); + } + static kilometresToCentimetres(kilometres) { + const result = new Decimal(kilometres).mul(1e5); + return Number(result.toFixed(5)); + } + static centimetresToKilometres(centimetres) { + const result = new Decimal(centimetres).div(1e5); + return Number(result.toFixed(5)); + } + static metresToInches(metres) { + const result = new Decimal(metres).mul(39.3701); + return Number(result.toFixed(5)); + } + static inchesToMetres(inches) { + const result = new Decimal(inches).mul(0.0254); + return Number(result.toFixed(5)); + } + static kilometresToYards(kilometres) { + const result = new Decimal(kilometres).mul(1093.61); + return Number(result.toFixed(5)); + } + static yardsToKilometres(yards) { + const result = new Decimal(yards).mul(0.0009144); + return Number(result.toFixed(5)); + } + static milesToYards(miles) { + const result = new Decimal(miles).mul(1760); + return Number(result.toFixed(5)); + } + static yardsToMiles(yards) { + const result = new Decimal(yards).mul(0.000568182); + return Number(result.toFixed(5)); + } + static milesToFeet(miles) { + const result = new Decimal(miles).mul(5280); + return Number(result.toFixed(5)); + } + static feetToMiles(feet) { + const result = new Decimal(feet).mul(0.000189394); + return Number(result.toFixed(5)); + } + static milesToInches(miles) { + const result = new Decimal(miles).mul(63360); + return Number(result.toFixed(5)); + } + static inchesToMiles(inches) { + const result = new Decimal(inches).mul(0.0000157828); + return Number(result.toFixed(8)); + } + static yardsToFeet(yards) { + const result = new Decimal(yards).mul(3); + return Number(result.toFixed(5)); + } + static feetToYards(feet) { + const result = new Decimal(feet).div(3); + return Number(result.toFixed(5)); + } +} + +// src/units/convert.ts +var conversionErrorString = (fromUnit, toUnit) => { + return `Cannot convert from ${fromUnit} to ${toUnit}`; +}; +var convertMass = (value, fromUnit, toUnit) => { + if (!isMassUnit(toUnit) || !isMassUnit(fromUnit)) { + return conversionErrorString(fromUnit, toUnit); + } + if (fromUnit === MASS_UNITS.KILOGRAM.short && toUnit === MASS_UNITS.POUND.short) { + return MassConverter.kilogramsToPounds(value); + } else if (fromUnit === MASS_UNITS.POUND.short && toUnit === MASS_UNITS.KILOGRAM.short) { + return MassConverter.poundsToKilograms(value); + } else if (fromUnit === MASS_UNITS.GRAM.short && toUnit === MASS_UNITS.KILOGRAM.short) { + return MassConverter.gramsToKilograms(value); + } else if (fromUnit === MASS_UNITS.KILOGRAM.short && toUnit === MASS_UNITS.GRAM.short) { + return MassConverter.kilogramsToGrams(value); + } else if (fromUnit === MASS_UNITS.GRAM.short && toUnit === MASS_UNITS.OUNCE.short) { + return MassConverter.gramsToOunces(value); + } else if (fromUnit === MASS_UNITS.OUNCE.short && toUnit === MASS_UNITS.GRAM.short) { + return MassConverter.ouncesToGrams(value); + } else if (fromUnit === MASS_UNITS.OUNCE.short && toUnit === MASS_UNITS.POUND.short) { + return MassConverter.ouncesToPounds(value); + } else if (fromUnit === MASS_UNITS.POUND.short && toUnit === MASS_UNITS.OUNCE.short) { + return MassConverter.poundsToOunces(value); + } else if (fromUnit === MASS_UNITS.GRAM.short && toUnit === MASS_UNITS.POUND.short) { + return MassConverter.gramsToPounds(value); + } else if (fromUnit === MASS_UNITS.POUND.short && toUnit === MASS_UNITS.GRAM.short) { + return MassConverter.poundsToGrams(value); + } else if (fromUnit === MASS_UNITS.KILOGRAM.short && toUnit === MASS_UNITS.OUNCE.short) { + return MassConverter.kilogramsToOunces(value); + } else if (fromUnit === MASS_UNITS.OUNCE.short && toUnit === MASS_UNITS.KILOGRAM.short) { + return MassConverter.ouncesToKilograms(value); + } + return conversionErrorString(fromUnit, toUnit); +}; +var convertTemperature = (value, fromUnit, toUnit) => { + if (!isTemperatureUnit(toUnit) || !isTemperatureUnit(fromUnit)) { + return conversionErrorString(fromUnit, toUnit); + } + if (fromUnit === TEMPERATURE_UNITS.CELSIUS.short && toUnit === TEMPERATURE_UNITS.FAHRENHEIT.short) { + return TemperatureConverter.celsiusToFahrenheit(value); + } else if (fromUnit === TEMPERATURE_UNITS.FAHRENHEIT.short && toUnit === TEMPERATURE_UNITS.CELSIUS.short) { + return TemperatureConverter.fahrenheitToCelsius(value); + } else if (fromUnit === TEMPERATURE_UNITS.CELSIUS_FAN_OVEN.short && toUnit === TEMPERATURE_UNITS.FAHRENHEIT.short) { + return TemperatureConverter.celsiusFanOvenToFahrenheit(value); + } else if (fromUnit === TEMPERATURE_UNITS.FAHRENHEIT.short && toUnit === TEMPERATURE_UNITS.CELSIUS_FAN_OVEN.short) { + return TemperatureConverter.fahrenheitToCelsiusFanOven(value); + } else if (fromUnit === TEMPERATURE_UNITS.CELSIUS.short && toUnit === TEMPERATURE_UNITS.CELSIUS_FAN_OVEN.short) { + return TemperatureConverter.celsiusToCelsiusFanOven(value); + } else if (fromUnit === TEMPERATURE_UNITS.CELSIUS_FAN_OVEN.short && toUnit === TEMPERATURE_UNITS.CELSIUS.short) { + return TemperatureConverter.celsiusFanOvenToCelsius(value); + } + return conversionErrorString(fromUnit, toUnit); +}; +var convertVolume = (value, fromUnit, toUnit) => { + if (!isVolumeUnit(toUnit) || !isVolumeUnit(fromUnit)) { + return conversionErrorString(fromUnit, toUnit); + } + if (fromUnit === VOLUME_UNITS.LITRE.short && toUnit === VOLUME_UNITS.PINT.short) { + return VolumeConverter.litresToPints(value); + } else if (fromUnit === VOLUME_UNITS.PINT.short && toUnit === VOLUME_UNITS.LITRE.short) { + return VolumeConverter.pintsToLitres(value); + } else if (fromUnit === VOLUME_UNITS.LITRE.short && toUnit === VOLUME_UNITS.FLUID_OUNCE.short) { + return VolumeConverter.litresToFluidOunces(value); + } else if (fromUnit === VOLUME_UNITS.FLUID_OUNCE.short && toUnit === VOLUME_UNITS.LITRE.short) { + return VolumeConverter.fluidOuncesToLitres(value); + } else if (fromUnit === VOLUME_UNITS.GALLON.short && toUnit === VOLUME_UNITS.PINT.short) { + return VolumeConverter.gallonsToPints(value); + } else if (fromUnit === VOLUME_UNITS.PINT.short && toUnit === VOLUME_UNITS.GALLON.short) { + return VolumeConverter.pintsToGallons(value); + } else if (fromUnit === VOLUME_UNITS.GALLON.short && toUnit === VOLUME_UNITS.MILLILITRE.short) { + return VolumeConverter.gallonsToMillilitres(value); + } else if (fromUnit === VOLUME_UNITS.MILLILITRE.short && toUnit === VOLUME_UNITS.GALLON.short) { + return VolumeConverter.millilitresToGallons(value); + } else if (fromUnit === VOLUME_UNITS.PINT.short && toUnit === VOLUME_UNITS.FLUID_OUNCE.short) { + return VolumeConverter.pintsToFluidOunces(value); + } else if (fromUnit === VOLUME_UNITS.FLUID_OUNCE.short && toUnit === VOLUME_UNITS.PINT.short) { + return VolumeConverter.fluidOuncesToPints(value); + } else if (fromUnit === VOLUME_UNITS.MILLILITRE.short && toUnit === VOLUME_UNITS.FLUID_OUNCE.short) { + return VolumeConverter.millilitresToFluidOunces(value); + } else if (fromUnit === VOLUME_UNITS.FLUID_OUNCE.short && toUnit === VOLUME_UNITS.MILLILITRE.short) { + return VolumeConverter.fluidOuncesToMillilitres(value); + } else if (fromUnit === VOLUME_UNITS.LITRE.short && toUnit === VOLUME_UNITS.GALLON.short) { + return VolumeConverter.litresToGallons(value); + } else if (fromUnit === VOLUME_UNITS.LITRE.short && toUnit === VOLUME_UNITS.MILLILITRE.short) { + return VolumeConverter.litresToMillilitres(value); + } else if (fromUnit === VOLUME_UNITS.MILLILITRE.short && toUnit === VOLUME_UNITS.LITRE.short) { + return VolumeConverter.millilitresToLitres(value); + } else if (fromUnit === VOLUME_UNITS.GALLON.short && toUnit === VOLUME_UNITS.LITRE.short) { + return VolumeConverter.gallonsToLitres(value); + } else if (fromUnit === VOLUME_UNITS.MILLILITRE.short && toUnit === VOLUME_UNITS.PINT.short) { + return VolumeConverter.millilitresToPints(value); + } else if (fromUnit === VOLUME_UNITS.PINT.short && toUnit === VOLUME_UNITS.MILLILITRE.short) { + return VolumeConverter.pintsToMillilitres(value); + } else if (fromUnit === VOLUME_UNITS.GALLON.short && toUnit === VOLUME_UNITS.FLUID_OUNCE.short) { + return VolumeConverter.gallonsToFluidOunces(value); + } else if (fromUnit === VOLUME_UNITS.FLUID_OUNCE.short && toUnit === VOLUME_UNITS.GALLON.short) { + return VolumeConverter.fluidOuncesToGallons(value); + } + return conversionErrorString(fromUnit, toUnit); +}; +var convertDistance = (value, fromUnit, toUnit) => { + if (!isDistanceUnit(toUnit) || !isDistanceUnit(fromUnit)) { + return conversionErrorString(fromUnit, toUnit); + } + if (fromUnit === DISTANCE_UNITS.METRE.short && toUnit === DISTANCE_UNITS.YARD.short) { + return DistanceConverter.metresToYards(value); + } else if (fromUnit === DISTANCE_UNITS.METRE.short && toUnit === DISTANCE_UNITS.FOOT.short) { + return DistanceConverter.metresToFeet(value); + } else if (fromUnit === DISTANCE_UNITS.FOOT.short && toUnit === DISTANCE_UNITS.METRE.short) { + return DistanceConverter.feetToMetres(value); + } else if (fromUnit === DISTANCE_UNITS.YARD.short && toUnit === DISTANCE_UNITS.METRE.short) { + return DistanceConverter.yardsToMetres(value); + } else if (fromUnit === DISTANCE_UNITS.KILOMETRE.short && toUnit === DISTANCE_UNITS.MILE.short) { + return DistanceConverter.kilometresToMiles(value); + } else if (fromUnit === DISTANCE_UNITS.MILE.short && toUnit === DISTANCE_UNITS.KILOMETRE.short) { + return DistanceConverter.milesToKilometres(value); + } else if (fromUnit === DISTANCE_UNITS.CENTIMETRE.short && toUnit === DISTANCE_UNITS.INCH.short) { + return DistanceConverter.centimetresToInches(value); + } else if (fromUnit === DISTANCE_UNITS.CENTIMETRE.short && toUnit === DISTANCE_UNITS.INCH.short) { + return DistanceConverter.centimetresToFeet(value); + } else if (fromUnit === DISTANCE_UNITS.FOOT.short && toUnit === DISTANCE_UNITS.CENTIMETRE.short) { + return DistanceConverter.feetToCentimetres(value); + } else if (fromUnit === DISTANCE_UNITS.INCH.short && toUnit === DISTANCE_UNITS.CENTIMETRE.short) { + return DistanceConverter.inchesToCentimetres(value); + } else if (fromUnit === DISTANCE_UNITS.FOOT.short && toUnit === DISTANCE_UNITS.INCH.short) { + return DistanceConverter.feetToInches(value); + } else if (fromUnit === DISTANCE_UNITS.INCH.short && toUnit === DISTANCE_UNITS.FOOT.short) { + return DistanceConverter.inchesToFeet(value); + } else if (fromUnit === DISTANCE_UNITS.CENTIMETRE.short && toUnit == DISTANCE_UNITS.METRE.short) { + return DistanceConverter.centimetresToMetres(value); + } else if (fromUnit === DISTANCE_UNITS.METRE.short && toUnit === DISTANCE_UNITS.CENTIMETRE.short) { + return DistanceConverter.metresToCentimetres(value); + } else if (fromUnit === DISTANCE_UNITS.KILOMETRE.short && toUnit === DISTANCE_UNITS.METRE.short) { + return DistanceConverter.kilometresToMetres(value); + } else if (fromUnit === DISTANCE_UNITS.METRE.short && toUnit === DISTANCE_UNITS.KILOMETRE.short) { + return DistanceConverter.metresToKilometres(value); + } else if (fromUnit === DISTANCE_UNITS.KILOMETRE.short && toUnit === DISTANCE_UNITS.CENTIMETRE.short) { + return DistanceConverter.kilometresToCentimetres(value); + } else if (fromUnit === DISTANCE_UNITS.CENTIMETRE.short && toUnit === DISTANCE_UNITS.KILOMETRE.short) { + return DistanceConverter.centimetresToKilometres(value); + } else if (fromUnit === DISTANCE_UNITS.METRE.short && toUnit === DISTANCE_UNITS.INCH.short) { + return DistanceConverter.metresToInches(value); + } else if (fromUnit === DISTANCE_UNITS.INCH.short && toUnit === DISTANCE_UNITS.METRE.short) { + return DistanceConverter.inchesToMetres(value); + } else if (fromUnit === DISTANCE_UNITS.KILOMETRE.short && toUnit === DISTANCE_UNITS.YARD.short) { + return DistanceConverter.kilometresToYards(value); + } else if (fromUnit === DISTANCE_UNITS.YARD.short && toUnit === DISTANCE_UNITS.KILOMETRE.short) { + return DistanceConverter.yardsToKilometres(value); + } else if (fromUnit === DISTANCE_UNITS.MILE.short && toUnit === DISTANCE_UNITS.YARD.short) { + return DistanceConverter.milesToYards(value); + } else if (fromUnit === DISTANCE_UNITS.YARD.short && toUnit === DISTANCE_UNITS.MILE.short) { + return DistanceConverter.yardsToMiles(value); + } else if (fromUnit === DISTANCE_UNITS.MILE.short && toUnit === DISTANCE_UNITS.FOOT.short) { + return DistanceConverter.milesToFeet(value); + } else if (fromUnit === DISTANCE_UNITS.FOOT.short && toUnit === DISTANCE_UNITS.MILE.short) { + return DistanceConverter.feetToMiles(value); + } else if (fromUnit === DISTANCE_UNITS.MILE.short && toUnit === DISTANCE_UNITS.INCH.short) { + return DistanceConverter.milesToInches(value); + } else if (fromUnit === DISTANCE_UNITS.INCH.short && toUnit === DISTANCE_UNITS.MILE.short) { + return DistanceConverter.inchesToMiles(value); + } else if (fromUnit === DISTANCE_UNITS.YARD.short && toUnit === DISTANCE_UNITS.FOOT.short) { + return DistanceConverter.yardsToFeet(value); + } else if (fromUnit === DISTANCE_UNITS.FOOT.short && toUnit === DISTANCE_UNITS.YARD.short) { + return DistanceConverter.feetToYards(value); + } + return conversionErrorString(fromUnit, toUnit); +}; +var convertUnit = (value, fromUnit, toUnit) => { + let result; + if (fromUnit === toUnit) { + return conversionErrorString(fromUnit, toUnit); + } + if (isMassUnit(fromUnit) && isMassUnit(toUnit)) { + result = convertMass(value, fromUnit, toUnit); + } else if (isTemperatureUnit(fromUnit) && isTemperatureUnit(toUnit)) { + result = convertTemperature(value, fromUnit, toUnit); + } else if (isVolumeUnit(fromUnit) && isVolumeUnit(toUnit)) { + result = convertVolume(value, fromUnit, toUnit); + } else if (isDistanceUnit(fromUnit) && isDistanceUnit(toUnit)) { + result = convertDistance(value, fromUnit, toUnit); + } else { + return conversionErrorString(fromUnit, toUnit); + } + const rounded = new decimal_default(result).toDecimalPlaces(4); + return rounded.isInteger() ? rounded.toNumber() : Number(rounded); +}; +export { + toScientificNotation, + hexToRgb, + hexToHsl, + convertUnixTimestamp, + convertUnit, + convertIsoString, + convertEcmaTimestamp +}; diff --git a/public/static/style.css b/public/static/style.css new file mode 100644 index 0000000..953fecc --- /dev/null +++ b/public/static/style.css @@ -0,0 +1,209 @@ +@import "https://unpkg.com/open-props"; +@import "https://unpkg.com/open-props/normalize.min.css"; +@import "https://unpkg.com/open-props/buttons.min.css"; + +*, +::after, +::before { + box-sizing: border-box; + max-width: 100%; +} + +html { + font-family: var(--font-industrial); +} + +body { + margin: 0 auto; + max-width: clamp(350px, 80vw, 600px); + + @media (max-width: 685px) { + max-width: 90vw; + } +} + +header#main-header { + position: sticky; + background-color: var(--surface-1); + z-index: 1; + top: 0; + display: grid; + grid-template-columns: 1fr auto; + width: 100%; + align-items: center; + + h1 { + font-size: var(--font-size-fluid-3) + } + + button { + margin: var(--size-2); + } +} + +#main-menu-button { + position: relative; + anchor-name: --main-menu-button; + width: var(--size-12); + left: -.5rem; +} + +#main-menu { + &:not(:popover-open) { + display: none + } + + &:popover-open { + position-anchor: --main-menu-button; + position: fixed; + position-area: bottom; + width: var(--size-12); + background: var(--surface-2); + border-radius: var(--radius-2); + box-shadow: var(--shadow-3); + list-style: none; + z-index: 2; + display: flex; + flex-direction: column; + gap: var(--size-2); + margin: var(--size-2) 0 var(--size-2) 0; + padding: 0; + + li { + margin: 0; + padding: 0; + + button { + width: 100%; + margin: 0; + } + } + } +} + +main { + overflow-y: scroll; + height: 100%; + position: relative; + padding-bottom: var(--size-8) +} + +footer#main-footer { + width: 100%; + background: var(--surface-1); + position: fixed; + height: var(--size-8); + left: 0; + bottom: 0; + z-index: 10; + + p { + position: relative; + text-align: center; + z-index: 1; + margin: var(--size-2) 0; + } +} + +h2, +h3 { + text-align: center; +} + +@layer card { + .card { + border-radius: var(--radius-2); + padding: var(--size-fluid-3); + box-shadow: var(--shadow-2); + margin: 0 auto; + + &:hover { + box-shadow: var(--shadow-3); + } + + @media (--motionOK) { + animation: var(--animation-fade-in); + } + } + + .card-1 { + background: var(--surface-2); + } + + .card-2 { + background: var(--surface-3); + } + + .colour-card { + background: var(--gradient-21); + } +} + +.form-input { + display: grid; + gap: var(--size-2); + grid-template-rows: 1fr 1fr; + align-items: center; +} + +.form-input:has([type="checkbox"]) { + grid-template-columns: auto 1fr; + grid-template-rows: 1fr; +} + +.full-width { + grid-column: 1 / -1; +} + +.form-grid { + display: grid; + grid-template-columns: auto auto; + gap: var(--size-2); + + @media (max-width: 600px) { + grid-template-columns: 1fr; + grid-template-rows: auto; + } +} + +fieldset { + display: grid; + gap: var(--size-2); + grid-template-columns: 1fr 1fr; + + @media (max-width: 435px) { + grid-template-columns: 1fr; + grid-template-rows: auto; + } +} + +#colour-picker { + .form-input { + margin: 0 auto; + max-width: fit-content; + margin-bottom: var(--size-2); + + input { + justify-self: center; + } + } + + #colour-values { + margin-inline: var(--size-fluid-3); + + p { + display: grid; + gap: var(--size-1); + grid-template-columns: 1fr 1fr; + margin-bottom: var(--size-2); + } + } +} + +#timestamps { + #fieldsets-container { + display: grid; + grid-template-rows: repeat(2, 1fr); + gap: var(--size-2); + } +} diff --git a/public/timestamps.html b/public/timestamps.html new file mode 100644 index 0000000..0344c9d --- /dev/null +++ b/public/timestamps.html @@ -0,0 +1,108 @@ +
    +

    Timestamps

    + +
    +
    + ISO String + +
    + + +
    + +
    + + +
    + + + +
    + + +
    + +
    + UNIX Timestamps +
    + + +
    + +
    + + +
    + + + +
    + + +
    +
    + +
    + ECMAScript Timestamps +
    + + +
    + +
    + + +
    + + + +
    + + +
    +
    +
    +
    diff --git a/public/units.html b/public/units.html new file mode 100644 index 0000000..4b8250a --- /dev/null +++ b/public/units.html @@ -0,0 +1,139 @@ +
    +

    Units Converter

    + +
    + Units + +
    + + +
    + +
    + + +
    + +
    + + + + + + + + + +
    + +
    + + + + + + + + + +
    + + + +
    + + +
    +
    + +
    diff --git a/src/colour/hex-to-hsl.test.ts b/src/colour/hex-to-hsl.test.ts new file mode 100644 index 0000000..5d772e0 --- /dev/null +++ b/src/colour/hex-to-hsl.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'bun:test'; +import { hexToHsl } from './hex-to-hsl'; + +describe('hexToHsl', () => { + const testCases = [ + { hex: '#FFFFFF', expected: 'hsl(0, 0%, 100%)' }, + { hex: '#000000', expected: 'hsl(0, 0%, 0%)' }, + { hex: '#FF5733', expected: 'hsl(11, 100%, 60%)' }, + { hex: '#33FF57', expected: 'hsl(131, 100%, 60%)' }, + { hex: '#3357FF', expected: 'hsl(229, 100%, 60%)' }, + { hex: '#123456', expected: 'hsl(210, 65%, 20%)' }, + { hex: '#ABCDEF', expected: 'hsl(210, 68%, 80%)' }, + ]; + + testCases.forEach(({ hex, expected }) => { + it(`should convert ${hex} to ${expected}`, () => { + expect(hexToHsl(hex)).toBe(expected); + }); + }); +}); diff --git a/src/colour/hex-to-hsl.ts b/src/colour/hex-to-hsl.ts new file mode 100644 index 0000000..54dff75 --- /dev/null +++ b/src/colour/hex-to-hsl.ts @@ -0,0 +1,38 @@ +import Decimal from 'decimal.js'; + +export const hexToHsl = (hex: string): string => { + const red = new Decimal(parseInt(hex.slice(1, 3), 16)).div(255); + const green = new Decimal(parseInt(hex.slice(3, 5), 16)).div(255); + const blue = new Decimal(parseInt(hex.slice(5, 7), 16)).div(255); + + const cmin = Decimal.min(red, green, blue); + const cmax = Decimal.max(red, green, blue); + const delta = cmax.minus(cmin); + + let h = new Decimal(0); + let s = new Decimal(0); + let l = cmax.plus(cmin).div(2); + + if (!delta.isZero()) { + if (cmax.equals(red)) { + h = green.minus(blue).div(delta).mod(6); + } else if (cmax.equals(green)) { + h = blue.minus(red).div(delta).plus(2); + } else { + h = red.minus(green).div(delta).plus(4); + } + h = h.times(60); + if (h.isNegative()) h = h.plus(360); + } + + if (!delta.isZero()) { + s = delta.div(new Decimal(1).minus((l.times(2).minus(1)).abs())); + } + + // Use rounding after all calculations + const hFinal = h.toDecimalPlaces(0, Decimal.ROUND_HALF_UP).toNumber(); + const sFinal = s.times(100).toDecimalPlaces(0, Decimal.ROUND_HALF_UP).toNumber(); + const lFinal = l.times(100).toDecimalPlaces(0, Decimal.ROUND_HALF_UP).toNumber(); + + return `hsl(${hFinal}, ${sFinal}%, ${lFinal}%)`; +}; diff --git a/src/colour/hex-to-rgb.test.ts b/src/colour/hex-to-rgb.test.ts new file mode 100644 index 0000000..b0f4e08 --- /dev/null +++ b/src/colour/hex-to-rgb.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'bun:test'; +import { hexToRgb } from './hex-to-rgb'; + +describe('hexToRgb', () => { + const testCases = [ + { hex: '#FFFFFF', expected: 'rgb(255, 255, 255)' }, + { hex: '#000000', expected: 'rgb(0, 0, 0)' }, + { hex: '#FF5733', expected: 'rgb(255, 87, 51)' }, + { hex: '#33FF57', expected: 'rgb(51, 255, 87)' }, + { hex: '#3357FF', expected: 'rgb(51, 87, 255)' }, + { hex: '#123456', expected: 'rgb(18, 52, 86)' }, + { hex: '#ABCDEF', expected: 'rgb(171, 205, 239)' }, + ]; + + testCases.forEach(({ hex, expected }) => { + it(`should convert ${hex} to ${expected}`, () => { + expect(hexToRgb(hex)).toBe(expected); + }); + }); +}); diff --git a/src/colour/hex-to-rgb.ts b/src/colour/hex-to-rgb.ts new file mode 100644 index 0000000..ac96d7b --- /dev/null +++ b/src/colour/hex-to-rgb.ts @@ -0,0 +1,7 @@ +export const hexToRgb = (hex: string): string => { + const red = parseInt(hex.slice(1, 3), 16); + const green = parseInt(hex.slice(3, 5), 16); + const blue = parseInt(hex.slice(5, 7), 16); + return `rgb(${red}, ${green}, ${blue})`; +}; + diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..5694bfb --- /dev/null +++ b/src/index.ts @@ -0,0 +1,7 @@ +export { convertEcmaTimestamp } from './timestamps/ecma'; +export { convertUnixTimestamp } from './timestamps/unix'; +export { convertIsoString } from './timestamps/iso-string'; +export { hexToRgb } from './colour/hex-to-rgb'; +export { hexToHsl } from './colour/hex-to-hsl'; +export { toScientificNotation } from './numbers/scientific-notation'; +export { convertUnit } from './units/convert-unit'; diff --git a/src/numbers/scientific-notation.test.ts b/src/numbers/scientific-notation.test.ts new file mode 100644 index 0000000..663ab9b --- /dev/null +++ b/src/numbers/scientific-notation.test.ts @@ -0,0 +1,49 @@ +import {describe, expect, test} from 'bun:test' +import { toScientificNotation } from './scientific-notation'; + +describe("toScientificNotation", () => { + test("should convert numbers to exponential HTML format without decimals", () => { + // Arrange + const input = 345600000000; + + // Act + const result = toScientificNotation(input); + + // Assert + expect(result).toBe("3 × 1011"); + }); + + test("should convert numbers to exponential HTML format to specified precision", () => { + // Arrange + const input = 345600000000; + const precision = 4; + + // Act + const result = toScientificNotation(input, precision); + + // Assert + expect(result).toBe("3.4560 × 1011"); + }); + + test("should handle negative exponents", () => { + // Arrange + const input = 0.00003456; + + // Act + const result = toScientificNotation(input); + + // Assert + expect(result).toBe("3 × 10-5"); + }); + + test("should fallback to the original string if parsing fails", () => { + // Arrange + const input = "not a number"; + + // Act + const result = toScientificNotation(input as unknown as number); + + // Assert + expect(result).toBe("not a number"); + }); +}); diff --git a/src/numbers/scientific-notation.ts b/src/numbers/scientific-notation.ts new file mode 100644 index 0000000..0f1ca9d --- /dev/null +++ b/src/numbers/scientific-notation.ts @@ -0,0 +1,20 @@ +export const toScientificNotation = (num: number, precision = 0): string => { + // Convert to exponential notation + const exponential = precision > 0 ? Number(num).toExponential(precision) : Number(num).toExponential(0); + + // Match both integer and decimal coefficients + const match = exponential.match(/^(-?\d+(?:\.\d+)?)e([+-]?\d+)$/); + + if (!match) { + return num.toString(); + } + + let coefficient = match[1]; + const exponent = parseInt(match[2], 10).toString(); + + if (precision === 0) { + coefficient = Math.round(Number(coefficient)).toString(); + } + + return `${coefficient} × 10${exponent}`; +}; diff --git a/src/timestamps/ecma.test.ts b/src/timestamps/ecma.test.ts new file mode 100644 index 0000000..1b969d3 --- /dev/null +++ b/src/timestamps/ecma.test.ts @@ -0,0 +1,113 @@ +import { describe, expect, it } from "bun:test"; +import { convertEcmaTimestamp } from "./ecma"; + +describe('convertEcmaTimestamp', () => { + const ivalidTimestamps = [ + 0, + null, + undefined, + 'blorgen', + ]; + ivalidTimestamps.forEach((invalidTimestamp) => { + it('should return "Please enter a valid timestamp" for invalid input', () => { + // Arrange + const conversionOption = 'iso'; + + // Act + const result = convertEcmaTimestamp(invalidTimestamp as number, conversionOption); + + // Assert + expect(result).toBe('Please enter a valid timestamp'); + }); + }); + + it('should convert a valid ECMA timestamp to ISO string', () => { + // Arrange + const ecmaTimestamp = 1633046400000; + const conversionOption = 'iso'; + const expectedIsoString = '2021-10-01T00:00:00.000Z'; + + // Act + const result = convertEcmaTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe(expectedIsoString); + }); + + it('should convert a valid ECMA timestamp to UTC string', () => { + // Arrange + const ecmaTimestamp = 1633046400000; + const conversionOption = 'utc'; + const expectedUtcString = 'Fri, 01 Oct 2021 00:00:00 GMT'; + + // Act + const result = convertEcmaTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe(expectedUtcString); + }); + + it('should convert a valid ECMA timestamp to Locale string', () => { + // Arrange + const ecmaTimestamp = 1633046400000; + const conversionOption = 'locale'; + const expectedLocaleString = '10/1/2021, 12:00:00 AM'; // Without a browser the default local is en-US + + // Act + const result = convertEcmaTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe(expectedLocaleString); + }); + + it('should convert a valid ECMA timestamp to seconds', () => { + // Arrange + const ecmaTimestamp = 1633046400000; + const conversionOption = 'seconds'; + const expectedSeconds = 1633046400; + + // Act + const result = convertEcmaTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe(expectedSeconds); + }); + + it('should convert a valid ECMA timestamp to hours', () => { + // Arrange + const ecmaTimestamp = 7200000; // 2 hours in milliseconds + const conversionOption = 'hours'; + const expectedHours = 2; + + // Act + const result = convertEcmaTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe(expectedHours); + }); + + it('should convert a valid ECMA timestamp to days', () => { + // Arrange + const ecmaTimestamp = 172800000; // 2 days in milliseconds + const conversionOption = 'days'; + const expectedDays = 2; + + // Act + const result = convertEcmaTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe(expectedDays); + }); + + it('should return "Invalid conversion option" for an invalid conversion option', () => { + // Arrange + const ecmaTimestamp = 1633046400000; + const conversionOption = 'invalid-option' as any; + + // Act + const result = convertEcmaTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe('Invalid conversion option'); + }); +}); diff --git a/src/timestamps/ecma.ts b/src/timestamps/ecma.ts new file mode 100644 index 0000000..a540ee0 --- /dev/null +++ b/src/timestamps/ecma.ts @@ -0,0 +1,34 @@ +type EcmaConversionOption = 'iso' | 'utc' | 'locale' | 'seconds' | 'hours' | 'days'; + +const getHoursFromMilliseconds = (milliseconds: number): number => { + return Math.floor(milliseconds / (1000 * 60 * 60)); +}; + +const getDaysFromMilliseconds = (milliseconds: number): number => { + return Math.floor(milliseconds / (1000 * 60 * 60 * 24)); +}; + +export const convertEcmaTimestamp = (ecmaTimestamp: number, ecmaConversionOption: EcmaConversionOption): string | number => { + if (!ecmaTimestamp || isNaN(ecmaTimestamp)) { + return 'Please enter a valid timestamp'; + } + + const date = new Date(ecmaTimestamp); + + switch (ecmaConversionOption) { + case 'iso': + return date.toISOString(); + case 'utc': + return date.toUTCString(); + case 'locale': + return date.toLocaleString(); + case 'seconds': + return Math.floor(ecmaTimestamp / 1000); + case 'hours': + return getHoursFromMilliseconds(ecmaTimestamp); + case 'days': + return getDaysFromMilliseconds(ecmaTimestamp); + default: + return 'Invalid conversion option'; + } +}; diff --git a/src/timestamps/iso-string.test.ts b/src/timestamps/iso-string.test.ts new file mode 100644 index 0000000..33e2861 --- /dev/null +++ b/src/timestamps/iso-string.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from "bun:test"; +import { convertIsoString } from "./iso-string"; + +describe('convertIsoString', () => { + const invalidIsoStringPatterns = [ + null, + undefined, + '', + 'not-an-iso-string', + '2021-12-01 23:59:59Z', // Missing 'T' + ]; + invalidIsoStringPatterns.forEach((invalidIsoString) => { + it(`should return "Please enter a valid ISO string" for input that doesn't match the regex pattern "${invalidIsoString}"`, () => { + // Arrange + const conversionType = 'unix'; + + // Act + const result = convertIsoString(invalidIsoString as string, conversionType); + + // Assert + expect(result).toBe('Please enter a valid ISO string'); + }); + }); + + const invalidIsoStrings = [ + '2021-13-01T00:00:00Z', // Invalid month + '2021-00-01T00:00:00Z', // Invalid month + '2021-12-32T00:00:00Z', // Invalid day + '2021-12-01T24:00:00Z', // Invalid hour + '2021-12-01T00:60:00Z', // Invalid minute + '2021-12-01T00:00:60Z', // Invalid second + ]; + invalidIsoStringPatterns.forEach((invalidIsoString) => { + it(`should return "Invalid ISO String" for input that has invalid properties "${invalidIsoString}"`, () => { + // Arrange + const conversionType = 'unix'; + + // Act + const result = convertIsoString(invalidIsoString as string, conversionType); + + // Assert + expect(result).toBe('Please enter a valid ISO string'); + }); + }); + + it('should convert a valid ISO string to UNIX timestamp', () => { + // Arrange + const isoString = '2021-10-01T00:00:00Z'; + const conversionType = 'unix'; + const expectedUnixTimestamp = 1633046400; + + // Act + const result = convertIsoString(isoString, conversionType); + + // Assert + expect(result).toBe(expectedUnixTimestamp); + }); + + it('should convert a valid ISO string to ECMA timestamp', () => { + // Arrange + const isoString = '2021-10-01T00:00:00Z'; + const conversionType = 'ecma'; + const expectedEcmaTimestamp = 1633046400000; + + // Act + const result = convertIsoString(isoString, conversionType); + + // Assert + expect(result).toBe(expectedEcmaTimestamp); + }); + + it('should return "Invalid conversion type" for an unsupported conversion type', () => { + // Arrange + const isoString = '2021-10-01T00:00:00Z'; + const conversionType = 'invalid-type' as 'unix'; + + // Act + const result = convertIsoString(isoString, conversionType); + + // Assert + expect(result).toBe('Invalid conversion type'); + }); +}); diff --git a/src/timestamps/iso-string.ts b/src/timestamps/iso-string.ts new file mode 100644 index 0000000..f42f086 --- /dev/null +++ b/src/timestamps/iso-string.ts @@ -0,0 +1,22 @@ +type IsoStringConversions = 'unix' | 'ecma'; + +export const convertIsoString = (isoString: string, conversionType: IsoStringConversions): number | string => { + const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/; + if (!isoString || !isoDateRegex.test(isoString)) { + return 'Please enter a valid ISO string'; + } + + const date = new Date(isoString); + if (isNaN(date.getTime())) { + return 'Invalid ISO string'; + } + + switch (conversionType) { + case 'unix': + return Math.floor(date.getTime() / 1000); + case 'ecma': + return date.getTime(); + default: + return 'Invalid conversion type'; + } +}; diff --git a/src/timestamps/unix.test.ts b/src/timestamps/unix.test.ts new file mode 100644 index 0000000..6276755 --- /dev/null +++ b/src/timestamps/unix.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, it } from "bun:test"; + +import { convertUnixTimestamp } from "./unix"; + +describe('convertUnixTimestamp', () => { + const ivalidTimestamps = [ + 0, + null, + undefined, + 'blorgen', + ]; + ivalidTimestamps.forEach((invalidTimestamp) => { + it('should return "Please enter a valid timestamp" for invalid input', () => { + // Arrange + const conversionOption = 'iso'; + + // Act + const result = convertUnixTimestamp(invalidTimestamp as number, conversionOption); + + // Assert + expect(result).toBe('Please enter a valid timestamp'); + }); + }); + + it('should convert a valid UNIX timestamp to ISO string', () => { + // Arrange + const ecmaTimestamp = 1633046400; + const conversionOption = 'iso'; + const expectedIsoString = '2021-10-01T00:00:00.000Z'; + + // Act + const result = convertUnixTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe(expectedIsoString); + }); + + it('should convert a valid UNIX timestamp to UTC string', () => { + // Arrange + const ecmaTimestamp = 1633046400; + const conversionOption = 'utc'; + const expectedUtcString = 'Fri, 01 Oct 2021 00:00:00 GMT'; + + // Act + const result = convertUnixTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe(expectedUtcString); + }); + + it('should convert a valid UNIX timestamp to Locale string', () => { + // Arrange + const ecmaTimestamp = 1633046400; + const conversionOption = 'locale'; + const expectedLocaleString = '10/1/2021, 12:00:00 AM'; // Without a browser the default local is en-US + + // Act + const result = convertUnixTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe(expectedLocaleString); + }); + + it('should convert a valid UNIX timestamp to milliseconds', () => { + // Arrange + const ecmaTimestamp = 163304640; + const conversionOption = 'milliseconds'; + const expectedSeconds = 163304640000; + + // Act + const result = convertUnixTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe(expectedSeconds); + }); + + it('should convert a valid UNIX timestamp to hours', () => { + // Arrange + const ecmaTimestamp = 7200; // 2 hours in seconds + const conversionOption = 'hours'; + const expectedHours = 2; + + // Act + const result = convertUnixTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe(expectedHours); + }); + + it('should convert a valid UNIX timestamp to days', () => { + // Arrange + const ecmaTimestamp = 172800; // 2 days in seconds + const conversionOption = 'days'; + const expectedDays = 2; + + // Act + const result = convertUnixTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe(expectedDays); + }); + + it('should return "Invalid conversion option" for an invalid conversion option', () => { + // Arrange + const ecmaTimestamp = 1633046400; + const conversionOption = 'invalid-option' as any; + + // Act + const result = convertUnixTimestamp(ecmaTimestamp, conversionOption); + + // Assert + expect(result).toBe('Invalid conversion option'); + }); +}); diff --git a/src/timestamps/unix.ts b/src/timestamps/unix.ts new file mode 100644 index 0000000..26c41d9 --- /dev/null +++ b/src/timestamps/unix.ts @@ -0,0 +1,34 @@ +type UnixConversionOption = 'iso' | 'utc' | 'locale' | 'milliseconds' | 'hours' | 'days'; + +const getHoursFromSeconds = (seconds: number): number => { + return Math.floor(seconds / (60 * 60)); +}; + +const getDaysFromSeconds = (seconds: number): number => { + return Math.floor(seconds / (60 * 60 * 24)); +}; + +export const convertUnixTimestamp = (unixTimestamp: number, conversionOption: UnixConversionOption): string | number => { + if (!unixTimestamp || isNaN(unixTimestamp)) { + return 'Please enter a valid timestamp'; + } + + const date = new Date(unixTimestamp * 1000); + + switch (conversionOption) { + case 'iso': + return date.toISOString(); + case 'utc': + return date.toUTCString(); + case 'locale': + return date.toLocaleString(); + case 'milliseconds': + return date.getTime(); + case 'hours': + return getHoursFromSeconds(date.getTime() / 1000); + case 'days': + return getDaysFromSeconds(date.getTime() / 1000); + default: + return 'Invalid conversion option'; + } +}; diff --git a/src/units/convert-unit.test.ts b/src/units/convert-unit.test.ts new file mode 100644 index 0000000..6111e45 --- /dev/null +++ b/src/units/convert-unit.test.ts @@ -0,0 +1,109 @@ +import { describe, expect, test } from 'bun:test'; +import { convertUnit } from './convert-unit'; +import type { UnitShort } from './model'; + +describe('convertUnit', () => { + test('should return an error string if the from and to units are the same', () => { + const testCases = [ + 'm', + 'km', + 'ft', + 'yd', + 'mi', + 'cm', + 'mm', + 'in', + 'f', + 'c', + 'c (fan)', + 'k', + 'kg', + 'g', + 'lb', + 'oz', + 'l', + 'ml', + 'gal', + 'pt', + ]; + + testCases.forEach((unit) => { + // Arrange + const value = 100; + const expectedErrorString = `Cannot convert from ${unit} to ${unit}`; + + // Act + const result = convertUnit(value, unit, unit); + + // Assert + expect(result).toBe(expectedErrorString); + }); + }); + + test('should return an error string if the from or to unit is not supported', () => { + const testCases = [ + { value: 100, fromUnit: 'm', toUnit: 'xyz' }, + { value: 50, fromUnit: 'abc', toUnit: 'km' }, + { value: 75, fromUnit: 'foo', toUnit: 'bar' }, + ]; + + testCases.forEach(({ value, fromUnit, toUnit }) => { + // Arrange + const expectedErrorString = `Cannot convert from ${fromUnit} to ${toUnit}`; + + // Act + const result = convertUnit(value, fromUnit as UnitShort, toUnit as UnitShort); + + // Assert + expect(result).toBe(expectedErrorString); + }); + }); + + test('should return an error string if the units an incompatible', () => { + const testCases = [ + { value: 100, fromUnit: 'm', toUnit: 'kg' }, + { value: 50, fromUnit: 'c', toUnit: 'l' }, + { value: 75, fromUnit: 'g', toUnit: 'ft' }, + ]; + + testCases.forEach(({ value, fromUnit, toUnit }) => { + // Arrange + const expectedErrorString = `Cannot convert from ${fromUnit} to ${toUnit}`; + + // Act + const result = convertUnit(value, fromUnit as UnitShort, toUnit as UnitShort); + + // Assert + expect(result).toBe(expectedErrorString); + }); + }); + + test('should correctly convert between supported units', () => { + const testCases = [ + { value: 1000, fromUnit: 'g', toUnit: 'kg', expected: 1 }, + { value: 5, fromUnit: 'kg', toUnit: 'g', expected: 5000 }, + { value: 16, fromUnit: 'oz', toUnit: 'lb', expected: 1 }, + { value: 2.20462, fromUnit: 'lb', toUnit: 'kg', expected: 1 }, + { value: 100, fromUnit: 'cm', toUnit: 'm', expected: 1 }, + { value: 1, fromUnit: 'km', toUnit: 'm', expected: 1000 }, + { value: 39.3701, fromUnit: 'in', toUnit: 'm', expected: 1 }, + { value: 3.28084, fromUnit: 'ft', toUnit: 'm', expected: 1 }, + { value: 1.09361, fromUnit: 'yd', toUnit: 'm', expected: 1 }, + { value: 0.000621371, fromUnit: 'mi', toUnit: 'm', expected: 1 }, + { value: 1000, fromUnit: 'ml', toUnit: 'l', expected: 1 }, + { value: 3.78541, fromUnit: 'l', toUnit: 'gal', expected: 1 }, + { value: 8, fromUnit: 'pt', toUnit: 'l', expected: 4.54609 }, + { value: 32, fromUnit: 'f', toUnit: 'c', expected: 0 }, + { value: 100, fromUnit: 'c', toUnit: 'f', expected: 212 }, + { value: 180, fromUnit: 'c (fan)', toUnit: 'f', expected: 392 }, + ]; + + testCases.forEach(({ value, fromUnit, toUnit, expected }) => { + // Act + const result = convertUnit(value, fromUnit as UnitShort, toUnit as UnitShort); + + // Assert + expect(result).toBe(expected); + }); + }); +}); diff --git a/src/units/convert-unit.ts b/src/units/convert-unit.ts new file mode 100644 index 0000000..4e5654e --- /dev/null +++ b/src/units/convert-unit.ts @@ -0,0 +1,211 @@ +import { TemperatureConverter } from "./temperatre-converter"; +import { MassConverter } from "./mass-converter"; +import { VolumeConverter } from "./volume-converter"; +import { MASS_UNITS, VOLUME_UNITS, TEMPERATURE_UNITS, type UnitShort, isMassUnit, isTemperatureUnit, isVolumeUnit, isDistanceUnit, DISTANCE_UNITS } from "./model"; +import Decimal from "decimal.js"; +import { DistanceConverter } from "./distance-converter"; + +const conversionErrorString = (fromUnit: UnitShort, toUnit: UnitShort): string => { + return `Cannot convert from ${fromUnit} to ${toUnit}`; +} + +const convertMass = (value: number, fromUnit: UnitShort, toUnit: UnitShort): number | string => { + if (!isMassUnit(toUnit) || !isMassUnit(fromUnit)) { + return conversionErrorString(fromUnit, toUnit); + } + + if (fromUnit === MASS_UNITS.KILOGRAM.short && toUnit === MASS_UNITS.POUND.short) { + return MassConverter.kilogramsToPounds(value); + } else if (fromUnit === MASS_UNITS.POUND.short && toUnit === MASS_UNITS.KILOGRAM.short) { + return MassConverter.poundsToKilograms(value); + } else if (fromUnit === MASS_UNITS.GRAM.short && toUnit === MASS_UNITS.KILOGRAM.short) { + return MassConverter.gramsToKilograms(value); + } else if (fromUnit === MASS_UNITS.KILOGRAM.short && toUnit === MASS_UNITS.GRAM.short) { + return MassConverter.kilogramsToGrams(value); + } else if (fromUnit === MASS_UNITS.GRAM.short && toUnit === MASS_UNITS.OUNCE.short) { + return MassConverter.gramsToOunces(value); + } else if (fromUnit === MASS_UNITS.OUNCE.short && toUnit === MASS_UNITS.GRAM.short) { + return MassConverter.ouncesToGrams(value); + } else if (fromUnit === MASS_UNITS.OUNCE.short && toUnit === MASS_UNITS.POUND.short) { + return MassConverter.ouncesToPounds(value); + } else if (fromUnit === MASS_UNITS.POUND.short && toUnit === MASS_UNITS.OUNCE.short) { + return MassConverter.poundsToOunces(value); + } else if (fromUnit === MASS_UNITS.GRAM.short && toUnit === MASS_UNITS.POUND.short) { + return MassConverter.gramsToPounds(value); + } else if (fromUnit === MASS_UNITS.POUND.short && toUnit === MASS_UNITS.GRAM.short) { + return MassConverter.poundsToGrams(value); + } else if (fromUnit === MASS_UNITS.KILOGRAM.short && toUnit === MASS_UNITS.OUNCE.short) { + return MassConverter.kilogramsToOunces(value); + } else if (fromUnit === MASS_UNITS.OUNCE.short && toUnit === MASS_UNITS.KILOGRAM.short) { + return MassConverter.ouncesToKilograms(value); + } + + return conversionErrorString(fromUnit, toUnit); +} + +const convertTemperature = (value: number, fromUnit: UnitShort, toUnit: UnitShort): number | string => { + if (!isTemperatureUnit(toUnit) || !isTemperatureUnit(fromUnit)) { + return conversionErrorString(fromUnit, toUnit); + } + + if (fromUnit === TEMPERATURE_UNITS.CELSIUS.short && toUnit === TEMPERATURE_UNITS.FAHRENHEIT.short) { + return TemperatureConverter.celsiusToFahrenheit(value); + } else if (fromUnit === TEMPERATURE_UNITS.FAHRENHEIT.short && toUnit === TEMPERATURE_UNITS.CELSIUS.short) { + return TemperatureConverter.fahrenheitToCelsius(value); + } else if (fromUnit === TEMPERATURE_UNITS.CELSIUS_FAN_OVEN.short && toUnit === TEMPERATURE_UNITS.FAHRENHEIT.short) { + return TemperatureConverter.celsiusFanOvenToFahrenheit(value); + } else if (fromUnit === TEMPERATURE_UNITS.FAHRENHEIT.short && toUnit === TEMPERATURE_UNITS.CELSIUS_FAN_OVEN.short) { + return TemperatureConverter.fahrenheitToCelsiusFanOven(value); + } else if (fromUnit === TEMPERATURE_UNITS.CELSIUS.short && toUnit === TEMPERATURE_UNITS.CELSIUS_FAN_OVEN.short) { + return TemperatureConverter.celsiusToCelsiusFanOven(value); + } else if (fromUnit === TEMPERATURE_UNITS.CELSIUS_FAN_OVEN.short && toUnit === TEMPERATURE_UNITS.CELSIUS.short) { + return TemperatureConverter.celsiusFanOvenToCelsius(value); + } + + return conversionErrorString(fromUnit, toUnit); +} + +const convertVolume = (value: number, fromUnit: UnitShort, toUnit: UnitShort): number | string => { + if (!isVolumeUnit(toUnit) || !isVolumeUnit(fromUnit)) { + return conversionErrorString(fromUnit, toUnit); + } + + if (fromUnit === VOLUME_UNITS.LITRE.short && toUnit === VOLUME_UNITS.PINT.short) { + return VolumeConverter.litresToPints(value); + } else if (fromUnit === VOLUME_UNITS.PINT.short && toUnit === VOLUME_UNITS.LITRE.short) { + return VolumeConverter.pintsToLitres(value); + } else if (fromUnit === VOLUME_UNITS.LITRE.short && toUnit === VOLUME_UNITS.FLUID_OUNCE.short) { + return VolumeConverter.litresToFluidOunces(value); + } else if (fromUnit === VOLUME_UNITS.FLUID_OUNCE.short && toUnit === VOLUME_UNITS.LITRE.short) { + return VolumeConverter.fluidOuncesToLitres(value); + } else if (fromUnit === VOLUME_UNITS.GALLON.short && toUnit === VOLUME_UNITS.PINT.short) { + return VolumeConverter.gallonsToPints(value); + } else if (fromUnit === VOLUME_UNITS.PINT.short && toUnit === VOLUME_UNITS.GALLON.short) { + return VolumeConverter.pintsToGallons(value); + } else if (fromUnit === VOLUME_UNITS.GALLON.short && toUnit === VOLUME_UNITS.MILLILITRE.short) { + return VolumeConverter.gallonsToMillilitres(value); + } else if (fromUnit === VOLUME_UNITS.MILLILITRE.short && toUnit === VOLUME_UNITS.GALLON.short) { + return VolumeConverter.millilitresToGallons(value); + } else if (fromUnit === VOLUME_UNITS.PINT.short && toUnit === VOLUME_UNITS.FLUID_OUNCE.short) { + return VolumeConverter.pintsToFluidOunces(value); + } else if (fromUnit === VOLUME_UNITS.FLUID_OUNCE.short && toUnit === VOLUME_UNITS.PINT.short) { + return VolumeConverter.fluidOuncesToPints(value); + } else if (fromUnit === VOLUME_UNITS.MILLILITRE.short && toUnit === VOLUME_UNITS.FLUID_OUNCE.short) { + return VolumeConverter.millilitresToFluidOunces(value); + } else if (fromUnit === VOLUME_UNITS.FLUID_OUNCE.short && toUnit === VOLUME_UNITS.MILLILITRE.short) { + return VolumeConverter.fluidOuncesToMillilitres(value); + } else if (fromUnit === VOLUME_UNITS.LITRE.short && toUnit === VOLUME_UNITS.GALLON.short) { + return VolumeConverter.litresToGallons(value); + } else if (fromUnit === VOLUME_UNITS.LITRE.short && toUnit === VOLUME_UNITS.MILLILITRE.short) { + return VolumeConverter.litresToMillilitres(value); + } else if (fromUnit === VOLUME_UNITS.MILLILITRE.short && toUnit === VOLUME_UNITS.LITRE.short) { + return VolumeConverter.millilitresToLitres(value); + } else if (fromUnit === VOLUME_UNITS.GALLON.short && toUnit === VOLUME_UNITS.LITRE.short) { + return VolumeConverter.gallonsToLitres(value); + } else if (fromUnit === VOLUME_UNITS.MILLILITRE.short && toUnit === VOLUME_UNITS.PINT.short) { + return VolumeConverter.millilitresToPints(value); + } else if (fromUnit === VOLUME_UNITS.PINT.short && toUnit === VOLUME_UNITS.MILLILITRE.short) { + return VolumeConverter.pintsToMillilitres(value); + } else if (fromUnit === VOLUME_UNITS.GALLON.short && toUnit === VOLUME_UNITS.FLUID_OUNCE.short) { + return VolumeConverter.gallonsToFluidOunces(value); + } else if (fromUnit === VOLUME_UNITS.FLUID_OUNCE.short && toUnit === VOLUME_UNITS.GALLON.short) { + return VolumeConverter.fluidOuncesToGallons(value); + } + + return conversionErrorString(fromUnit, toUnit); +} + +const convertDistance = (value: number, fromUnit: UnitShort, toUnit: UnitShort): number | string => { + if (!isDistanceUnit(toUnit) || !isDistanceUnit(fromUnit)) { + return conversionErrorString(fromUnit, toUnit); + } + + if (fromUnit === DISTANCE_UNITS.METRE.short && toUnit === DISTANCE_UNITS.YARD.short) { + return DistanceConverter.metresToYards(value); + } else if (fromUnit === DISTANCE_UNITS.METRE.short && toUnit === DISTANCE_UNITS.FOOT.short) { + return DistanceConverter.metresToFeet(value); + } else if (fromUnit === DISTANCE_UNITS.FOOT.short && toUnit === DISTANCE_UNITS.METRE.short) { + return DistanceConverter.feetToMetres(value); + } else if (fromUnit === DISTANCE_UNITS.YARD.short && toUnit === DISTANCE_UNITS.METRE.short) { + return DistanceConverter.yardsToMetres(value); + } else if (fromUnit === DISTANCE_UNITS.KILOMETRE.short && toUnit === DISTANCE_UNITS.MILE.short) { + return DistanceConverter.kilometresToMiles(value); + } else if (fromUnit === DISTANCE_UNITS.MILE.short && toUnit === DISTANCE_UNITS.KILOMETRE.short) { + return DistanceConverter.milesToKilometres(value); + } else if (fromUnit === DISTANCE_UNITS.CENTIMETRE.short && toUnit === DISTANCE_UNITS.INCH.short) { + return DistanceConverter.centimetresToInches(value); + } else if (fromUnit === DISTANCE_UNITS.CENTIMETRE.short && toUnit === DISTANCE_UNITS.INCH.short) { + return DistanceConverter.centimetresToFeet(value); + } else if (fromUnit === DISTANCE_UNITS.FOOT.short && toUnit === DISTANCE_UNITS.CENTIMETRE.short) { + return DistanceConverter.feetToCentimetres(value); + } else if (fromUnit === DISTANCE_UNITS.INCH.short && toUnit === DISTANCE_UNITS.CENTIMETRE.short) { + return DistanceConverter.inchesToCentimetres(value); + } else if (fromUnit === DISTANCE_UNITS.FOOT.short && toUnit === DISTANCE_UNITS.INCH.short) { + return DistanceConverter.feetToInches(value); + } else if (fromUnit === DISTANCE_UNITS.INCH.short && toUnit === DISTANCE_UNITS.FOOT.short) { + return DistanceConverter.inchesToFeet(value); + } else if (fromUnit === DISTANCE_UNITS.CENTIMETRE.short && toUnit == DISTANCE_UNITS.METRE.short) { + return DistanceConverter.centimetresToMetres(value); + } else if (fromUnit === DISTANCE_UNITS.METRE.short && toUnit === DISTANCE_UNITS.CENTIMETRE.short) { + return DistanceConverter.metresToCentimetres(value); + } else if (fromUnit === DISTANCE_UNITS.KILOMETRE.short && toUnit === DISTANCE_UNITS.METRE.short) { + return DistanceConverter.kilometresToMetres(value); + } else if (fromUnit === DISTANCE_UNITS.METRE.short && toUnit === DISTANCE_UNITS.KILOMETRE.short) { + return DistanceConverter.metresToKilometres(value); + } else if (fromUnit === DISTANCE_UNITS.KILOMETRE.short && toUnit === DISTANCE_UNITS.CENTIMETRE.short) { + return DistanceConverter.kilometresToCentimetres(value); + } else if (fromUnit === DISTANCE_UNITS.CENTIMETRE.short && toUnit === DISTANCE_UNITS.KILOMETRE.short) { + return DistanceConverter.centimetresToKilometres(value); + } else if (fromUnit === DISTANCE_UNITS.METRE.short && toUnit === DISTANCE_UNITS.INCH.short) { + return DistanceConverter.metresToInches(value); + } else if (fromUnit === DISTANCE_UNITS.INCH.short && toUnit === DISTANCE_UNITS.METRE.short) { + return DistanceConverter.inchesToMetres(value); + } else if (fromUnit === DISTANCE_UNITS.KILOMETRE.short && toUnit === DISTANCE_UNITS.YARD.short) { + return DistanceConverter.kilometresToYards(value); + } else if (fromUnit === DISTANCE_UNITS.YARD.short && toUnit === DISTANCE_UNITS.KILOMETRE.short) { + return DistanceConverter.yardsToKilometres(value); + } else if (fromUnit === DISTANCE_UNITS.MILE.short && toUnit === DISTANCE_UNITS.YARD.short) { + return DistanceConverter.milesToYards(value); + } else if (fromUnit === DISTANCE_UNITS.YARD.short && toUnit === DISTANCE_UNITS.MILE.short) { + return DistanceConverter.yardsToMiles(value); + } else if (fromUnit === DISTANCE_UNITS.MILE.short && toUnit === DISTANCE_UNITS.FOOT.short) { + return DistanceConverter.milesToFeet(value); + } else if (fromUnit === DISTANCE_UNITS.FOOT.short && toUnit === DISTANCE_UNITS.MILE.short) { + return DistanceConverter.feetToMiles(value); + } else if (fromUnit === DISTANCE_UNITS.MILE.short && toUnit === DISTANCE_UNITS.INCH.short) { + return DistanceConverter.milesToInches(value); + } else if (fromUnit === DISTANCE_UNITS.INCH.short && toUnit === DISTANCE_UNITS.MILE.short) { + return DistanceConverter.inchesToMiles(value); + } else if (fromUnit === DISTANCE_UNITS.YARD.short && toUnit === DISTANCE_UNITS.FOOT.short) { + return DistanceConverter.yardsToFeet(value); + } else if (fromUnit === DISTANCE_UNITS.FOOT.short && toUnit === DISTANCE_UNITS.YARD.short) { + return DistanceConverter.feetToYards(value); + } else if (fromUnit === DISTANCE_UNITS.MILE.short && toUnit === DISTANCE_UNITS.METRE.short) { + return DistanceConverter.milesToMetres(value); + } else if (fromUnit === DISTANCE_UNITS.METRE.short && toUnit === DISTANCE_UNITS.MILE.short) { + return DistanceConverter.metresToMiles(value); + } + + return conversionErrorString(fromUnit, toUnit); +} + +export const convertUnit = (value: number, fromUnit: UnitShort, toUnit: UnitShort): number | string => { + let result: number | string; + if (fromUnit === toUnit) { + return conversionErrorString(fromUnit, toUnit); + } + if (isMassUnit(fromUnit) && isMassUnit(toUnit)) { + result = convertMass(value, fromUnit, toUnit); + } else if (isTemperatureUnit(fromUnit) && isTemperatureUnit(toUnit)) { + result = convertTemperature(value, fromUnit, toUnit); + } else if (isVolumeUnit(fromUnit) && isVolumeUnit(toUnit)) { + result = convertVolume(value, fromUnit, toUnit); + } else if (isDistanceUnit(fromUnit) && isDistanceUnit(toUnit)) { + result = convertDistance(value, fromUnit, toUnit); + } else { + return conversionErrorString(fromUnit, toUnit); + } + + const rounded = new Decimal(result).toDecimalPlaces(5); + return rounded.isInteger() ? rounded.toNumber() : Number(rounded); +} diff --git a/src/units/distance-converter.test.ts b/src/units/distance-converter.test.ts new file mode 100644 index 0000000..12acadf --- /dev/null +++ b/src/units/distance-converter.test.ts @@ -0,0 +1,580 @@ +import { describe, expect, test } from 'bun:test'; +import { DistanceConverter } from './distance-converter'; + +describe('DistanceConverter', () => { + describe('metresToYards', () => { + const testCases = [ + [1, 1.09361], + [10, 10.9361], + [20, 21.8722], + [30, 32.8083] + ]; + testCases.forEach(([metres, yards]) => { + test(`should convert ${metres} metres to ${yards} yards`, () => { + // Arrange & Act + const result = DistanceConverter.metresToYards(metres); + + // Assert + expect(result).toEqual(yards); + }); + }); + }); + + describe('metresToFeet', () => { + const testCases = [ + [1, 3.28084], + [10, 32.8084], + [20, 65.6168], + [30, 98.4252] + ]; + testCases.forEach(([metres, feet]) => { + test(`should convert ${metres} metres to ${feet} feet`, () => { + // Arrange & Act + const result = DistanceConverter.metresToFeet(metres); + + // Assert + expect(result).toEqual(feet); + }); + }); + }); + + describe('feetToMetres', () => { + const testCases = [ + [1, 0.3048], + [10, 3.048], + [20, 6.096], + [30, 9.144] + ]; + testCases.forEach(([feet, metres]) => { + test(`should convert ${feet} feet to ${metres} metres`, () => { + // Arrange & Act + const result = DistanceConverter.feetToMetres(feet); + + // Assert + expect(result).toEqual(metres); + }); + }); + }); + + describe('yardsToMetres', () => { + const testCases = [ + [1, 0.9144], + [10, 9.14403], + [20, 18.28806], + [30, 27.43208] + ]; + testCases.forEach(([yards, metres]) => { + test(`should convert ${yards} yards to ${metres} metres`, () => { + // Arrange & Act + const result = DistanceConverter.yardsToMetres(yards); + + // Assert + expect(result).toEqual(metres); + }); + }); + }); + + describe('kilometresToMiles', () => { + const testCases = [ + [1, 0.62137], + [10, 6.21371], + [20, 12.42742], + [30, 18.64113] + ]; + testCases.forEach(([km, miles]) => { + test(`should convert ${km} km to ${miles} miles`, () => { + // Arrange & Act + const result = DistanceConverter.kilometresToMiles(km); + + // Assert + expect(result).toEqual(miles); + }); + }); + }); + + describe('milesToKilometres', () => { + const testCases = [ + [1, 1.60934], + [10, 16.09344], + [20, 32.18689], + [30, 48.28033] + ]; + testCases.forEach(([miles, km]) => { + test(`should convert ${miles} miles to ${km} km`, () => { + // Arrange & Act + const result = DistanceConverter.milesToKilometres(miles); + + // Assert + expect(result).toEqual(km); + }); + }); + }); + + describe('centimetresToInches', () => { + const testCases = [ + [1, 0.3937], + [10, 3.93701], + [20, 7.87402], + [30, 11.81103] + ]; + testCases.forEach(([cm, inches]) => { + test(`should convert ${cm} cm to ${inches} inches`, () => { + // Arrange & Act + const result = DistanceConverter.centimetresToInches(cm); + + // Assert + expect(result).toEqual(inches); + }); + }); + }); + + describe('centimetresToFeet', () => { + const testCases = [ + [1, 0.03281], + [10, 0.32808], + [20, 0.65617], + [30, 0.98425] + ]; + testCases.forEach(([cm, feet]) => { + test(`should convert ${cm} cm to ${feet} feet`, () => { + // Arrange & Act + const result = DistanceConverter.centimetresToFeet(cm); + + // Assert + expect(result).toEqual(feet); + }); + }); + }); + + describe('feetToCentimetres', () => { + const testCases = [ + [1, 30.48], + [10, 304.79999], + [20, 609.59998], + [30, 914.39997] + ]; + testCases.forEach(([feet, cm]) => { + test(`should convert ${feet} feet to ${cm} cm`, () => { + // Arrange & Act + const result = DistanceConverter.feetToCentimetres(feet); + + // Assert + expect(result).toEqual(cm); + }); + }); + }); + + describe('inchesToCentimetres', () => { + const testCases = [ + [1, 2.54], + [10, 25.39999], + [20, 50.79997], + [30, 76.19996] + ]; + testCases.forEach(([inches, cm]) => { + test(`should convert ${inches} inches to ${cm} cm`, () => { + // Arrange & Act + const result = DistanceConverter.inchesToCentimetres(inches); + + // Assert + expect(result).toEqual(cm); + }); + }); + }); + + describe('feetToInches', () => { + const testCases = [ + [1, 12], + [10, 120], + [20, 240], + [30, 360] + ]; + testCases.forEach(([feet, inches]) => { + test(`should convert ${feet} feet to ${inches} inches`, () => { + // Arrange & Act + const result = DistanceConverter.feetToInches(feet); + + // Assert + expect(result).toEqual(inches); + }); + }); + }); + + describe('inchesToFeet', () => { + const testCases = [ + [1, 0.08333], + [10, 0.83333], + [20, 1.66667], + [30, 2.5] + ]; + testCases.forEach(([inches, feet]) => { + test(`should convert ${inches} inches to ${feet} feet`, () => { + // Arrange & Act + const result = DistanceConverter.inchesToFeet(inches); + + // Assert + expect(result).toEqual(feet); + }); + }); + }); + + describe('centimetresToMetres', () => { + const testCases = [ + [1, 0.01], + [10, 0.1], + [20, 0.2], + [30, 0.3] + ]; + testCases.forEach(([cm, metres]) => { + test(`should convert ${cm} cm to ${metres} metres`, () => { + // Arrange & Act + const result = DistanceConverter.centimetresToMetres(cm); + + // Assert + expect(result).toEqual(metres); + }); + }); + }); + + describe('metresToCentimetres', () => { + const testCases = [ + [1, 100], + [10, 1000], + [20, 2000], + [30, 3000] + ]; + testCases.forEach(([metres, cm]) => { + test(`should convert ${metres} metres to ${cm} cm`, () => { + // Arrange & Act + const result = DistanceConverter.metresToCentimetres(metres); + + // Assert + expect(result).toEqual(cm); + }); + }); + }); + + describe('kilometresToMetres', () => { + const testCases = [ + [1, 1000], + [10, 10000], + [20, 20000], + [30, 30000] + ]; + testCases.forEach(([km, metres]) => { + test(`should convert ${km} km to ${metres} metres`, () => { + // Arrange & Act + const result = DistanceConverter.kilometresToMetres(km); + + // Assert + expect(result).toEqual(metres); + }); + }); + }); + + describe('metresToKilometres', () => { + const testCases = [ + [1, 0.001], + [10, 0.01], + [20, 0.02], + [30, 0.03] + ]; + testCases.forEach(([metres, km]) => { + test(`should convert ${metres} metres to ${km} km`, () => { + // Arrange & Act + const result = DistanceConverter.metresToKilometres(metres); + + // Assert + expect(result).toEqual(km); + }); + }); + }); + + describe('kilometresToCentimetres', () => { + const testCases = [ + [1, 100000], + [10, 1000000], + [20, 2000000], + [30, 3000000] + ]; + testCases.forEach(([km, cm]) => { + test(`should convert ${km} km to ${cm} cm`, () => { + // Arrange & Act + const result = DistanceConverter.kilometresToCentimetres(km); + + // Assert + expect(result).toEqual(cm); + }); + }); + }); + + describe('centimetresToKilometres', () => { + const testCases = [ + [1, 0.00001], + [10, 0.0001], + [20, 0.0002], + [30, 0.0003] + ]; + testCases.forEach(([cm, km]) => { + test(`should convert ${cm} cm to ${km} km`, () => { + // Arrange & Act + const result = DistanceConverter.centimetresToKilometres(cm); + + // Assert + expect(result).toEqual(km); + }); + }); + }); + + describe('metresToInches', () => { + const testCases = [ + [1, 39.3701], + [10, 393.701], + [20, 787.402], + [30, 1181.103] + ]; + testCases.forEach(([metres, inches]) => { + test(`should convert ${metres} metres to ${inches} inches`, () => { + // Arrange & Act + const result = DistanceConverter.metresToInches(metres); + + // Assert + expect(result).toEqual(inches); + }); + }); + }); + + describe('inchesToMetres', () => { + const testCases = [ + [1, 0.0254], + [10, 0.254], + [20, 0.508], + [30, 0.762] + ]; + testCases.forEach(([inches, metres]) => { + test(`should convert ${inches} inches to ${metres} metres`, () => { + // Arrange & Act + const result = DistanceConverter.inchesToMetres(inches); + + // Assert + expect(result).toEqual(metres); + }); + }); + }); + + describe('kilometresToYards', () => { + const testCases = [ + [1, 1093.61], + [10, 10936.1], + [20, 21872.2], + [30, 32808.3] + ]; + testCases.forEach(([km, yards]) => { + test(`should convert ${km} km to ${yards} yards`, () => { + // Arrange & Act + const result = DistanceConverter.kilometresToYards(km); + + // Assert + expect(result).toEqual(yards); + }); + }); + }); + + describe('yardsToKilometres', () => { + const testCases = [ + [1, 0.00091], + [10, 0.00914], + [20, 0.01829], + [30, 0.02743] + ]; + testCases.forEach(([yards, km]) => { + test(`should convert ${yards} yards to ${km} km`, () => { + // Arrange & Act + const result = DistanceConverter.yardsToKilometres(yards); + + // Assert + expect(result).toEqual(km); + }); + }); + }); + + describe('milesToYards', () => { + const testCases = [ + [1, 1760], + [10, 17600], + [20, 35200], + [30, 52800] + ]; + testCases.forEach(([miles, yards]) => { + test(`should convert ${miles} miles to ${yards} yards`, () => { + // Arrange & Act + const result = DistanceConverter.milesToYards(miles); + + // Assert + expect(result).toEqual(yards); + }); + }); + }); + + describe('yardsToMiles', () => { + const testCases = [ + [1, 0.00057], + [10, 0.00568], + [20, 0.01136], + [30, 0.01705] + ]; + testCases.forEach(([yards, miles]) => { + test(`should convert ${yards} yards to ${miles} miles`, () => { + // Arrange & Act + const result = DistanceConverter.yardsToMiles(yards); + + // Assert + expect(result).toEqual(miles); + }); + }); + }); + + describe('milesToFeet', () => { + const testCases = [ + [1, 5280], + [10, 52800], + [20, 105600], + [30, 158400] + ]; + testCases.forEach(([miles, feet]) => { + test(`should convert ${miles} miles to ${feet} feet`, () => { + // Arrange & Act + const result = DistanceConverter.milesToFeet(miles); + + // Assert + expect(result).toEqual(feet); + }); + }); + }); + + describe('feetToMiles', () => { + const testCases = [ + [1, 0.00019], + [10, 0.00189], + [20, 0.00379], + [30, 0.00568] + ]; + testCases.forEach(([feet, miles]) => { + test(`should convert ${feet} feet to ${miles} miles`, () => { + // Arrange & Act + const result = DistanceConverter.feetToMiles(feet); + + // Assert + expect(result).toEqual(miles); + }); + }); + }); + + describe('milesToInches', () => { + const testCases = [ + [1, 63360], + [10, 633600], + [20, 1267200], + [30, 1900800] + ]; + testCases.forEach(([miles, inches]) => { + test(`should convert ${miles} miles to ${inches} inches`, () => { + // Arrange & Act + const result = DistanceConverter.milesToInches(miles); + + // Assert + expect(result).toEqual(inches); + }); + }); + }); + + describe('inchesToMiles', () => { + const testCases = [ + [1, 0.00002], + [10, 0.00016], + [20, 0.00032], + [30, 0.00047] + ]; + testCases.forEach(([inches, miles]) => { + test(`should convert ${inches} inches to ${miles} miles`, () => { + // Arrange & Act + const result = DistanceConverter.inchesToMiles(inches); + + // Assert + expect(result).toEqual(miles); + }); + }); + }); + + describe('yardsToFeet', () => { + const testCases = [ + [1, 3], + [10, 30], + [20, 60], + [30, 90] + ]; + testCases.forEach(([yards, feet]) => { + test(`should convert ${yards} yards to ${feet} feet`, () => { + // Arrange & Act + const result = DistanceConverter.yardsToFeet(yards); + + // Assert + expect(result).toEqual(feet); + }); + }); + }); + + describe('feetToYards', () => { + const testCases = [ + [1, 0.33333], + [10, 3.33333], + [20, 6.66666], + [30, 9.99999] + ]; + testCases.forEach(([feet, yards]) => { + test(`should convert ${feet} feet to ${yards} yards`, () => { + // Arrange & Act + const result = DistanceConverter.feetToYards(feet); + + // Assert + expect(result).toEqual(yards); + }); + }); + }); + + describe('milesToMetres', () => { + const testCases = [ + [1, 1609.34], + [10, 16093.44], + [20, 32186.89], + [30, 48280.33] + ]; + testCases.forEach(([miles, metres]) => { + test(`should convert ${miles} miles to ${metres} metres`, () => { + // Arrange & Act + const result = DistanceConverter.milesToMetres(miles); + + // Assert + expect(result).toEqual(metres); + }); + }); + }); + + describe('metresToMiles', () => { + const testCases = [ + [1, 0.00062], + [10, 0.00621], + [20, 0.01243], + [30, 0.01864] + ]; + testCases.forEach(([metres, miles]) => { + test(`should convert ${metres} metres to ${miles} miles`, () => { + // Arrange & Act + const result = DistanceConverter.metresToMiles(metres); + + // Assert + expect(result).toEqual(miles); + }); + }); + }); +}); diff --git a/src/units/distance-converter.ts b/src/units/distance-converter.ts new file mode 100644 index 0000000..c97ba25 --- /dev/null +++ b/src/units/distance-converter.ts @@ -0,0 +1,182 @@ +import { Decimal } from 'decimal.js'; + +export class DistanceConverter { + private static readonly METRES_TO_YARDS = new Decimal(1.09361); + private static readonly KILOMETRES_TO_MILES = new Decimal(0.621371); + private static readonly CENTIMETRES_TO_INCHES = new Decimal(0.393701); + private static readonly METRES_TO_FEET = new Decimal(3.28084); + private static readonly CENTIMETRES_TO_FEET = new Decimal(0.0328084); + private static readonly FEET_TO_INCHES = new Decimal(12); + private static readonly METRES_TO_INCHES = new Decimal(39.3701); + private static readonly INCHES_TO_METRES = new Decimal(0.0254); + private static readonly KILOMETRES_TO_YARDS = new Decimal(1093.61); + private static readonly YARDS_TO_KILOMETRES = new Decimal(0.0009144); + private static readonly MILES_TO_YARDS = new Decimal(1760); + private static readonly YARDS_TO_MILES = new Decimal(0.000568182); + private static readonly MILES_TO_FEET = new Decimal(5280); + private static readonly FEET_TO_MILES = new Decimal(0.000189394); + private static readonly MILES_TO_INCHES = new Decimal(63360); + private static readonly INCHES_TO_MILES = new Decimal(0.0000157828); + private static readonly YARDS_TO_FEET = new Decimal(3); + private static readonly FEET_TO_YARDS = new Decimal(0.333333); + + static metresToYards(metres: number): number { + const result = new Decimal(metres).mul(this.METRES_TO_YARDS); + return Number(result.toFixed(5)); + } + + static metresToFeet(metres: number): number { + const result = new Decimal(metres).mul(this.METRES_TO_FEET); + return Number(result.toFixed(5)); + } + + static feetToMetres(feet: number): number { + const result = new Decimal(feet).div(this.METRES_TO_FEET); + return Number(result.toFixed(5)); + } + + static yardsToMetres(yards: number): number { + const result = new Decimal(yards).div(this.METRES_TO_YARDS); + return Number(result.toFixed(5)); + } + + static kilometresToMiles(kilometres: number): number { + const result = new Decimal(kilometres).mul(this.KILOMETRES_TO_MILES); + return Number(result.toFixed(5)); + } + + static milesToKilometres(miles: number): number { + const result = new Decimal(miles).div(this.KILOMETRES_TO_MILES); + return Number(result.toFixed(5)); + } + + static centimetresToInches(centimetres: number): number { + const result = new Decimal(centimetres).mul(this.CENTIMETRES_TO_INCHES); + return Number(result.toFixed(5)); + } + + static centimetresToFeet(centimetres: number): number { + const result = new Decimal(centimetres).mul(this.CENTIMETRES_TO_FEET); + return Number(result.toFixed(5)); + } + + static feetToCentimetres(feet: number): number { + const result = new Decimal(feet).div(this.CENTIMETRES_TO_FEET); + return Number(result.toFixed(5)); + } + + static inchesToCentimetres(inches: number): number { + const result = new Decimal(inches).div(this.CENTIMETRES_TO_INCHES); + return Number(result.toFixed(5)); + } + + static feetToInches(feet: number): number { + const result = new Decimal(feet).mul(this.FEET_TO_INCHES); + return Number(result.toFixed(5)); + } + + static inchesToFeet(inches: number): number { + const result = new Decimal(inches).div(this.FEET_TO_INCHES); + return Number(result.toFixed(5)); + } + + static centimetresToMetres(centimetres: number): number { + const result = new Decimal(centimetres).div(100); + return Number(result.toFixed(5)); + } + + static metresToCentimetres(metres: number): number { + const result = new Decimal(metres).mul(100); + return Number(result.toFixed(5)); + } + + static kilometresToMetres(kilometres: number): number { + const result = new Decimal(kilometres).mul(1000); + return Number(result.toFixed(5)); + } + + static metresToKilometres(metres: number): number { + const result = new Decimal(metres).div(1000); + return Number(result.toFixed(5)); + } + + static kilometresToCentimetres(kilometres: number): number { + const result = new Decimal(kilometres).mul(100000); + return Number(result.toFixed(5)); + } + + static centimetresToKilometres(centimetres: number): number { + const result = new Decimal(centimetres).div(100000); + return Number(result.toFixed(5)); + } + + static metresToInches(metres: number): number { + const result = new Decimal(metres).mul(this.METRES_TO_INCHES); + return Number(result.toFixed(5)); + } + + static inchesToMetres(inches: number): number { + const result = new Decimal(inches).mul(this.INCHES_TO_METRES); + return Number(result.toFixed(5)); + } + + static kilometresToYards(kilometres: number): number { + const result = new Decimal(kilometres).mul(this.KILOMETRES_TO_YARDS); + return Number(result.toFixed(5)); + } + + static yardsToKilometres(yards: number): number { + const result = new Decimal(yards).mul(this.YARDS_TO_KILOMETRES); + return Number(result.toFixed(5)); + } + + static milesToYards(miles: number): number { + const result = new Decimal(miles).mul(this.MILES_TO_YARDS); + return Number(result.toFixed(5)); + } + + static yardsToMiles(yards: number): number { + const result = new Decimal(yards).mul(this.YARDS_TO_MILES); + return Number(result.toFixed(5)); + } + + static milesToFeet(miles: number): number { + const result = new Decimal(miles).mul(this.MILES_TO_FEET); + return Number(result.toFixed(5)); + } + + static feetToMiles(feet: number): number { + const result = new Decimal(feet).mul(this.FEET_TO_MILES); + return Number(result.toFixed(5)); + } + + static milesToInches(miles: number): number { + const result = new Decimal(miles).mul(this.MILES_TO_INCHES); + return Number(result.toFixed(5)); + } + + static inchesToMiles(inches: number): number { + const result = new Decimal(inches).mul(this.INCHES_TO_MILES); + return Number(result.toFixed(5)); + } + + static yardsToFeet(yards: number): number { + const result = new Decimal(yards).mul(this.YARDS_TO_FEET); + return Number(result.toFixed(5)); + } + + static feetToYards(feet: number): number { + const result = new Decimal(feet).mul(this.FEET_TO_YARDS); + return Number(result.toFixed(5)); + } + + static milesToMetres(miles: number): number { + const kilometres = this.milesToKilometres(miles); + return this.kilometresToMetres(kilometres); + } + + static metresToMiles(metres: number): number { + const kilometres = this.metresToKilometres(metres); + return this.kilometresToMiles(kilometres); + } +} diff --git a/src/units/mass-converter.test.ts b/src/units/mass-converter.test.ts new file mode 100644 index 0000000..20d8fa6 --- /dev/null +++ b/src/units/mass-converter.test.ts @@ -0,0 +1,220 @@ +import { MassConverter } from './mass-converter'; +import { describe, expect, test } from 'bun:test'; + +describe('MassConverter', () => { + describe('kilogramsToPounds', () => { + const testCases = [ + [1, 2.20462], + [10, 22.0462], + [20, 44.0924], + [30, 66.1386] + ]; + testCases.forEach(([kilos, pounds]) => { + test(`should convert ${kilos} kg to ${pounds} lbs`, () => { + // Arrange & Act + const result = MassConverter.kilogramsToPounds(kilos); + + // Assert + expect(result).toEqual(pounds); + }); + }); + }); + + describe('poundsToKilograms', () => { + const testCases = [ + [1, 0.45359], + [10, 4.53593], + [20, 9.07186], + [30, 13.60779] + ]; + testCases.forEach(([lbs, kg]) => { + test(`should convert ${lbs} lbs to ${kg} kg`, () => { + // Arrange & Act + const result = MassConverter.poundsToKilograms(lbs); + + // Assert + expect(result).toEqual(kg); + }); + }); + }); + + describe('gramsToKilograms', () => { + const testCases = [ + [1, 0.001], + [10, 0.01], + [20, 0.02], + [30, 0.03] + ]; + testCases.forEach(([g, kg]) => { + test(`should convert ${g} g to ${kg} kg`, () => { + // Arrange & Act + const result = MassConverter.gramsToKilograms(g); + + // Assert + expect(result).toEqual(kg); + }); + }); + }); + + describe('kilogramsToGrams', () => { + const testCases = [ + [1, 1000], + [10, 10000], + [20, 20000], + [30, 30000] + ]; + testCases.forEach(([kilos, g]) => { + test(`should convert ${kilos} kg to ${g} g`, () => { + // Arrange & Act + const result = MassConverter.kilogramsToGrams(kilos); + + // Assert + expect(result).toEqual(g); + }); + }); + }); + + describe('gramsToOunces', () => { + const testCases = [ + [1, 0.03527], + [10, 0.35274], + [20, 0.70548], + [30, 1.05822] + ]; + testCases.forEach(([g, oz]) => { + test(`should convert ${g} g to ${oz} oz`, () => { + // Arrange & Act + const result = MassConverter.gramsToOunces(g); + + // Assert + expect(result).toEqual(oz); + }); + }); + }); + + describe('ouncesToGrams', () => { + const testCases = [ + [1, 28.34949], + [10, 283.49493], + [20, 566.98985], + [30, 850.48478] + ]; + testCases.forEach(([oz, g]) => { + test(`should convert ${oz} oz to ${g} g`, () => { + // Arrange & Act + const result = MassConverter.ouncesToGrams(oz); + + // Assert + expect(result).toEqual(g); + }); + }); + }); + + describe('ouncesToPounds', () => { + const testCases = [ + [1, 0.0625], + [10, 0.625], + [20, 1.25], + [30, 1.875] + ]; + testCases.forEach(([oz, lbs]) => { + test(`should convert ${oz} oz to ${lbs} lbs`, () => { + // Arrange & Act + const result = MassConverter.ouncesToPounds(oz); + + // Assert + expect(result).toEqual(lbs); + }); + }); + }); + + describe('poundsToOunces', () => { + const testCases = [ + [1, 16], + [10, 160], + [20, 320], + [30, 480] + ]; + testCases.forEach(([lbs, oz]) => { + test(`should convert ${lbs} lbs to ${oz} oz`, () => { + // Arrange & Act + const result = MassConverter.poundsToOunces(lbs); + + // Assert + expect(result).toEqual(oz); + }); + }); + }); + + describe('gramsToPounds', () => { + const testCases = [ + [1, 0.0022], + [10, 0.02205], + [20, 0.04409], + [30, 0.06614] + ]; + testCases.forEach(([g, lbs]) => { + test(`should convert ${g} g to ${lbs} lbs`, () => { + // Arrange & Act + const result = MassConverter.gramsToPounds(g); + + // Assert + expect(result).toEqual(lbs); + }); + }); + }); + + describe('poundsToGrams', () => { + const testCases = [ + [1, 453.59291], + [10, 4535.92909], + [20, 9071.85819], + [30, 13607.78728] + ]; + testCases.forEach(([lbs, g]) => { + test(`should convert ${lbs} lbs to ${g} g`, () => { + // Arrange & Act + const result = MassConverter.poundsToGrams(lbs); + + // Assert + expect(result).toEqual(g); + }); + }); + }); + + describe('kilogramsToOunces', () => { + const testCases = [ + [1, 35.274], + [10, 352.74], + [20, 705.48], + [30, 1058.22] + ]; + testCases.forEach(([kilos, oz]) => { + test(`should convert ${kilos} kg to ${oz} oz`, () => { + // Arrange & Act + const result = MassConverter.kilogramsToOunces(kilos); + + // Assert + expect(result).toEqual(oz); + }); + }); + }); + + describe('ouncesToKilograms', () => { + const testCases = [ + [1, 0.02835], + [10, 0.28349], + [20, 0.56699], + [30, 0.85048] + ]; + testCases.forEach(([oz, kg]) => { + test(`should convert ${oz} oz to ${kg} kg`, () => { + // Arrange & Act + const result = MassConverter.ouncesToKilograms(oz); + + // Assert + expect(result).toEqual(kg); + }); + }); + }); +}); diff --git a/src/units/mass-converter.ts b/src/units/mass-converter.ts new file mode 100644 index 0000000..160dc4b --- /dev/null +++ b/src/units/mass-converter.ts @@ -0,0 +1,68 @@ + +import { Decimal } from 'decimal.js'; + +export class MassConverter { + private static readonly GRAMS_TO_OUNCES = new Decimal(0.035274); + private static readonly KILOGRAMS_TO_POUNDS = new Decimal(2.20462); + private static readonly KILOGRAMS_TO_OUNCES = new Decimal(35.274); + + static kilogramsToPounds(kilos: number): number { + const result = new Decimal(kilos).mul(this.KILOGRAMS_TO_POUNDS); + return Number(result.toFixed(5)); + } + + static poundsToKilograms(pounds: number): number { + const result = new Decimal(pounds).div(this.KILOGRAMS_TO_POUNDS); + return Number(result.toFixed(5)); + } + + static gramsToKilograms(grams: number): number { + const result = new Decimal(grams).div(1000); + return Number(result.toFixed(5)); + } + + static kilogramsToGrams(kilos: number): number { + const result = new Decimal(kilos).mul(1000); + return Number(result.toFixed(5)); + } + + static gramsToOunces(grams: number): number { + const result = new Decimal(grams).mul(this.GRAMS_TO_OUNCES); + return Number(result.toFixed(5)); + } + + static ouncesToGrams(ounces: number): number { + const result = new Decimal(ounces).div(this.GRAMS_TO_OUNCES); + return Number(result.toFixed(5)); + } + + static ouncesToPounds(ounces: number): number { + const result = new Decimal(ounces).div(16); + return Number(result.toFixed(5)); + } + + static poundsToOunces(pounds: number): number { + const result = new Decimal(pounds).mul(16); + return Number(result.toFixed(5)); + } + + static gramsToPounds(grams: number): number { + const result = new Decimal(grams).mul(this.KILOGRAMS_TO_POUNDS).div(1000); + return Number(result.toFixed(5)); + } + + static poundsToGrams(pounds: number): number { + const result = new Decimal(pounds).div(this.KILOGRAMS_TO_POUNDS).mul(1000); + return Number(result.toFixed(5)); + } + + static kilogramsToOunces(kilos: number): number { + const result = new Decimal(kilos).mul(this.KILOGRAMS_TO_OUNCES); + return Number(result.toFixed(5)); + } + + static ouncesToKilograms(ounces: number): number { + const result = new Decimal(ounces).div(this.KILOGRAMS_TO_OUNCES); + return Number(result.toFixed(5)); + } +} diff --git a/src/units/model.ts b/src/units/model.ts new file mode 100644 index 0000000..60aebdb --- /dev/null +++ b/src/units/model.ts @@ -0,0 +1,60 @@ +export const MASS_UNITS = { + GRAM: { long: 'gram', short: 'g' }, + KILOGRAM: { long: 'kilogram', short: 'kg' }, + OUNCE: { long: 'ounce', short: 'oz' }, + POUND: { long: 'pound', short: 'lb' }, +} as const; + +export const TEMPERATURE_UNITS = { + CELSIUS: { long: 'celsius', short: 'c' }, + CELSIUS_FAN_OVEN: { long: 'celsius (fan oven)', short: 'c (fan)' }, + FAHRENHEIT: { long: 'fahrenheit', short: 'f' } +}; + +export const VOLUME_UNITS = { + MILLILITRE: { long: 'millilitre', short: 'ml' }, + FLUID_OUNCE: { long: 'fluid ounce', short: 'fl oz' }, + LITRE: { long: 'litre', short: 'l' }, + GALLON: { long: 'gallon', short: 'gal' }, + PINT: { long: 'pint', short: 'pt' }, +} as const; + +export const DISTANCE_UNITS = { + CENTIMETRE: { long: 'centimetre', short: 'cm' }, + METRE: { long: 'metre', short: 'm' }, + KILOMETRE: { long: 'kilometre', short: 'km' }, + INCH: { long: 'inch', short: 'in' }, + FOOT: { long: 'foot', short: 'ft' }, + YARD: { long: 'yard', short: 'yd' }, + MILE: { long: 'mile', short: 'mi' }, +} + +type MassUnitShort = typeof MASS_UNITS[keyof typeof MASS_UNITS]['short']; +type TemperatureUnitShort = typeof TEMPERATURE_UNITS[keyof typeof TEMPERATURE_UNITS]['short']; +type VolumeUnitShort = typeof VOLUME_UNITS[keyof typeof VOLUME_UNITS]['short']; +type DistanceUnitShort = typeof DISTANCE_UNITS[keyof typeof DISTANCE_UNITS]['short']; + +export type UnitShort = MassUnitShort | TemperatureUnitShort | VolumeUnitShort | DistanceUnitShort; + +export function isMassUnit(unit: UnitShort): boolean { + return Object.values(MASS_UNITS).some((u) => u.short === unit); +} + +export function isTemperatureUnit(unit: UnitShort): boolean { + return Object.values(TEMPERATURE_UNITS).some((u) => u.short === unit); +} + +export function isVolumeUnit(unit: UnitShort): boolean { + return Object.values(VOLUME_UNITS).some((u) => u.short === unit); +} + +export function isDistanceUnit(unit: UnitShort): boolean { + return Object.values(DISTANCE_UNITS).some((u) => u.short === unit); +} + +export const unitMap = { + ...MASS_UNITS, + ...TEMPERATURE_UNITS, + ...VOLUME_UNITS, + ...DISTANCE_UNITS +}; diff --git a/src/units/temperatre-converter.ts b/src/units/temperatre-converter.ts new file mode 100644 index 0000000..fa2dba4 --- /dev/null +++ b/src/units/temperatre-converter.ts @@ -0,0 +1,46 @@ +import { Decimal } from 'decimal.js'; + +export class TemperatureConverter { + private static readonly CELSIUS_TO_FAHRENHEIT = 1.8; + private static readonly FAHRENHEIT_TO_CELSIUS = 0.5555555555555556; + + static celsiusToFahrenheit(celsius: number): number { + return (celsius * this.CELSIUS_TO_FAHRENHEIT) + 32; + } + + static fahrenheitToCelsius(fahrenheit: number): number { + return (fahrenheit - 32) * this.FAHRENHEIT_TO_CELSIUS; + } + + /** + * American ovens don't tend to be fanned, whereas british ones are, + * requiring the temperature be adjusted by 10%. + */ + static celsiusFanOvenToFahrenheit(celsiusFanOven: number): number { + const celsius = new Decimal(celsiusFanOven).div(0.9); + + return this.celsiusToFahrenheit(celsius.toNumber()); + } + + /** + * American ovens don't tend to be fanned, whereas british ones are, + * requiring the temperature be adjusted by 10%. + */ + static fahrenheitToCelsiusFanOven(fahrenheit: number): number { + const celsius = new Decimal(this.fahrenheitToCelsius(fahrenheit)); + + return celsius.mul(0.9).toNumber(); + } + + static celsiusToCelsiusFanOven(celsius: number): number { + const c = new Decimal(celsius); + + return c.mul(0.9).toNumber(); + } + + static celsiusFanOvenToCelsius(celsiusFanOven: number): number { + const c = new Decimal(celsiusFanOven); + + return c.div(0.9).toNumber(); + } +} diff --git a/src/units/temperature-converter.test.ts b/src/units/temperature-converter.test.ts new file mode 100644 index 0000000..dd49262 --- /dev/null +++ b/src/units/temperature-converter.test.ts @@ -0,0 +1,118 @@ +import { describe, expect, test } from 'bun:test'; +import { TemperatureConverter } from "./temperatre-converter"; + +describe('TemperatureConverter', () => { + describe('celsiusToFahrenheit', () => { + const testCases = [ + [0, 32], + [10, 50], + [15, 59], + [20, 68], + [30, 86] + ]; + testCases.forEach(([c, f]) => { + test(`should convert ${c} C to ${f} F`, () => { + // Arrange & Act + const result = TemperatureConverter.celsiusToFahrenheit(c); + + // Assert + expect(result).toEqual(f); + }); + }); + }); + + describe('fahrenheitToCelsius', () => { + const testCases = [ + [32, 0], + [50, 10], + [59, 15], + [68, 20], + [86, 30] + ]; + testCases.forEach(([f, c]) => { + test(`should convert ${f} F to ${c} C`, () => { + // Arrange & Act + const result = TemperatureConverter.fahrenheitToCelsius(f); + + // Assert + expect(result).toEqual(c); + }); + }); + }); + + describe('celsiusFanOvenToFahrenheit', () => { + const testCases = [ + [0, 32], + [10, 52], + [15, 62], + [20, 72], + [30, 92] + ]; + testCases.forEach(([c, f]) => { + test(`should convert ${c} C (fan oven) to ${f} F`, () => { + // Arrange & Act + const result = TemperatureConverter.celsiusFanOvenToFahrenheit(c); + + // Assert + expect(result).toEqual(f); + }); + }); + }); + + describe('fahrenheitToCelsiusFanOven', () => { + const testCases = [ + [32, 0], + [50, 9], + [59, 13.5], + [68, 18], + [86, 27] + ]; + testCases.forEach(([f, c]) => { + test(`should convert ${f} F to ${c} C (fan oven)`, () => { + // Arrange & Act + const result = TemperatureConverter.fahrenheitToCelsiusFanOven(f); + + // Assert + expect(result).toEqual(c); + }); + }); + }); + + describe('celsiusToCelsiusFanOven', () => { + const testCases = [ + [0, 0], + [10, 9], + [15, 13.5], + [20, 18], + [30, 27] + ]; + testCases.forEach(([c, cf]) => { + test(`should convert ${c} C to ${cf} C (fan oven)`, () => { + // Arrange & Act + const result = TemperatureConverter.celsiusToCelsiusFanOven(c); + + // Assert + expect(result).toEqual(cf); + }); + }); + }); + + describe('celsiusFanOvenToCelsius', () => { + const testCases = [ + [0, 0], + [9, 10], + [13.5, 15], + [18, 20], + [27, 30] + ]; + testCases.forEach(([cf, c]) => { + test(`should convert ${cf} C (fan oven) to ${c} C`, () => { + // Arrange & Act + const result = TemperatureConverter.celsiusFanOvenToCelsius(cf); + + // Assert + expect(result).toEqual(c); + }); + }); + }); +}); diff --git a/src/units/volume-converter.test.ts b/src/units/volume-converter.test.ts new file mode 100644 index 0000000..0068ad4 --- /dev/null +++ b/src/units/volume-converter.test.ts @@ -0,0 +1,293 @@ +import { describe, expect, test } from 'bun:test'; +import { VolumeConverter } from './volume-converter'; + +describe('VolumeConverter', () => { + describe('litresToPints', () => { + const testCases = [ + [1, 1.75975], + [10, 17.5975], + [20, 35.195], + [30, 52.7925] + ]; + testCases.forEach(([litres, pints]) => { + test(`should convert ${litres} l to ${pints} pt`, () => { + // Arrange & Act + const result = VolumeConverter.litresToPints(litres); + + // Assert + expect(result).toEqual(pints); + }); + }); + }); + + describe('pintsToLitres', () => { + const testCases = [ + [1, 0.56826], + [10, 5.68261], + [20, 11.36522], + [30, 17.04783] + ]; + testCases.forEach(([pints, litres]) => { + test(`should convert ${pints} pt to ${litres} l`, () => { + // Arrange & Act + const result = VolumeConverter.pintsToLitres(pints); + + // Assert + expect(result).toEqual(litres); + }); + }); + }); + + describe('litresToFluidOunces', () => { + const testCases = [ + [1, 33.814], + [10, 338.14], + [20, 676.28], + [30, 1014.42] + ]; + testCases.forEach(([litres, fluidOunces]) => { + test(`should convert ${litres} l to ${fluidOunces} fl oz`, () => { + // Arrange & Act + const result = VolumeConverter.litresToFluidOunces(litres); + + // Assert + expect(result).toEqual(fluidOunces); + }); + }); + }); + + describe('fluidOuncesToLitres', () => { + const testCases = [ + [1, 0.02957], + [10, 0.29574], + [20, 0.59147], + [30, 0.88721] + ]; + testCases.forEach(([fluidOunces, litres]) => { + test(`should convert ${fluidOunces} fl oz to ${litres} l`, () => { + // Arrange & Act + const result = VolumeConverter.fluidOuncesToLitres(fluidOunces); + + // Assert + expect(result).toEqual(litres); + }); + }); + }); + + describe('gallonsToPints', () => { + const testCases = [ + [1, 8], + [2, 16], + [3, 24], + [4, 32] + ]; + testCases.forEach(([gallons, pints]) => { + test(`should convert ${gallons} gal to ${pints} pt`, () => { + // Arrange & Act + const result = VolumeConverter.gallonsToPints(gallons); + + // Assert + expect(result).toEqual(pints); + }); + }); + }); + + describe('pintsToGallons', () => { + const testCases = [ + [1, 0.125], + [2, 0.25], + [3, 0.375], + [4, 0.5] + ]; + testCases.forEach(([pints, gallons]) => { + test(`should convert ${pints} pt to ${gallons} gal`, () => { + // Arrange & Act + const result = VolumeConverter.pintsToGallons(pints); + + // Assert + expect(result).toEqual(gallons); + }); + }); + }); + + describe('gallonsToMillilitres', () => { + const testCases = [ + [1, 3785.41], + [2, 7570.82], + [3, 11356.23], + [4, 15141.64] + ]; + testCases.forEach(([gallons, millilitres]) => { + test(`should convert ${gallons} gal to ${millilitres} ml`, () => { + // Arrange & Act + const result = VolumeConverter.gallonsToMillilitres(gallons); + + // Assert + expect(result).toEqual(millilitres); + }); + }); + }); + + describe('millilitresToGallons', () => { + const testCases = [ + [1000, 0.26417], + [2000, 0.52834], + [3000, 0.79252], + [4000, 1.05669] + ]; + testCases.forEach(([millilitres, gallons]) => { + test(`should convert ${millilitres} ml to ${gallons} gal`, () => { + // Arrange & Act + const result = VolumeConverter.millilitresToGallons(millilitres); + + // Assert + expect(result).toEqual(gallons); + }); + }); + }); + + describe('pintsToFluidOunces', () => { + const testCases = [ + [1, 20], + [2, 40], + [3, 60], + [4, 80] + ]; + testCases.forEach(([pints, fluidOunces]) => { + test(`should convert ${pints} pt to ${fluidOunces} fl oz`, () => { + // Arrange & Act + const result = VolumeConverter.pintsToFluidOunces(pints); + + // Assert + expect(result).toEqual(fluidOunces); + }); + }); + }); + + describe('fluidOuncesToPints', () => { + const testCases = [ + [1, 0.05], + [2, 0.1], + [3, 0.15], + [4, 0.2] + ]; + testCases.forEach(([fluidOunces, pints]) => { + test(`should convert ${fluidOunces} fl oz to ${pints} pt`, () => { + // Arrange & Act + const result = VolumeConverter.fluidOuncesToPints(fluidOunces); + + // Assert + expect(result).toEqual(pints); + }); + }); + }); + + describe('millilitresToFluidOunces', () => { + const testCases = [ + [1, .03381], + [10, .33814], + [20, .67628], + [30, 1.01442] + ]; + testCases.forEach(([millilitres, fluidOunces]) => { + test(`should convert ${millilitres} ml to ${fluidOunces} fl oz`, () => { + // Arrange & Act + const result = VolumeConverter.millilitresToFluidOunces(millilitres); + + // Assert + expect(result).toEqual(fluidOunces); + }); + }); + }); + + describe('fluidOuncesToMillilitres', () => { + const testCases = [ + [1, 29.57355], + [10, 295.73549], + [20, 591.47099], + [30, 887.20648] + ]; + testCases.forEach(([fluidOunces, millilitres]) => { + test(`should convert ${fluidOunces} fl oz to ${millilitres} ml`, () => { + // Arrange & Act + const result = VolumeConverter.fluidOuncesToMillilitres(fluidOunces); + + // Assert + expect(result).toEqual(millilitres); + }); + }); + }); + + describe('litresToGallons', () => { + const testCases = [ + [1, .26417], + [10, 2.64172], + [20, 5.28344], + [30, 7.92516] + ]; + testCases.forEach(([litres, gallons]) => { + test(`should convert ${litres} l to ${gallons} gal`, () => { + // Arrange & Act + const result = VolumeConverter.litresToGallons(litres); + + // Assert + expect(result).toEqual(gallons); + }); + }); + }); + + describe('gallonsToLitres', () => { + const testCases = [ + [1, 3.78541], + [10, 37.85413], + [20, 75.70825], + [30, 113.56238] + ]; + testCases.forEach(([gallons, litres]) => { + test(`should convert ${gallons} gal to ${litres} l`, () => { + // Arrange & Act + const result = VolumeConverter.gallonsToLitres(gallons); + + // Assert + expect(result).toEqual(litres); + }); + }); + }); + + describe('millilitresToPints', () => { + const testCases = [ + [10, .0176], + [20, .0352], + [30, .0528], + [100, .176], + [10000, 17.6], + ]; + testCases.forEach(([millilitres, pints]) => { + test(`should convert ${millilitres} ml to ${pints} pt`, () => { + // Arrange & Act + const result = VolumeConverter.millilitresToPints(millilitres); + + // Assert + expect(result).toEqual(pints); + }); + }); + }); + + describe('pintsToMillilitres', () => { + const testCases = [ + [1, 568.18182], + [2.5, 1420.45455], + [10, 5681.81818], + [20, 11363.63636], + ]; + testCases.forEach(([pints, millilitres]) => { + test(`should convert ${pints} pt to ${millilitres} ml`, () => { + // Arrange & Act + const result = VolumeConverter.pintsToMillilitres(pints); + + // Assert + expect(result).toEqual(millilitres); + }); + }); + }); +}); diff --git a/src/units/volume-converter.ts b/src/units/volume-converter.ts new file mode 100644 index 0000000..d2039b1 --- /dev/null +++ b/src/units/volume-converter.ts @@ -0,0 +1,130 @@ + +import { Decimal } from 'decimal.js'; + +export class VolumeConverter { + private static readonly LITRES_TO_PINTS = new Decimal(1.75975); + private static readonly PINTS_TO_LITRES = new Decimal(0.568261); + private static readonly LITRES_TO_FLUID_OUNCES = new Decimal(33.814); + private static readonly FLUID_OUNCES_TO_LITRES = new Decimal(0.0295735); + private static readonly GALLONS_TO_PINTS = new Decimal(8); + private static readonly PINTS_TO_GALLONS = new Decimal(0.125); + private static readonly GALLONS_TO_MILLILITRES = new Decimal(3785.41); + private static readonly MILLILITRES_TO_GALLONS = new Decimal(0.000264172); + private static readonly PINTS_TO_FLUID_OUNCES = new Decimal(20); + private static readonly FLUID_OUNCES_TO_PINTS = new Decimal(0.05); + private static readonly MILLILITRE_TO_FLUID_OUNCE = new Decimal(0.033814); + private static readonly LITRE_TO_GALLON = new Decimal(0.264172); + private static readonly MILLILITRE_TO_PINT = new Decimal(0.00176); + + static litresToPints(litres: number): number { + const result = new Decimal(litres).mul(this.LITRES_TO_PINTS); + return Number(result.toFixed(5)); + } + + static pintsToLitres(pints: number): number { + const result = new Decimal(pints).mul(this.PINTS_TO_LITRES); + return Number(result.toFixed(5)); + } + + static litresToFluidOunces(litres: number): number { + const result = new Decimal(litres).mul(this.LITRES_TO_FLUID_OUNCES); + return Number(result.toFixed(5)); + } + + static fluidOuncesToLitres(fluidOunces: number): number { + const result = new Decimal(fluidOunces).mul(this.FLUID_OUNCES_TO_LITRES); + return Number(result.toFixed(5)); + } + + static gallonsToPints(gallons: number): number { + const result = new Decimal(gallons).mul(this.GALLONS_TO_PINTS); + return Number(result.toFixed(5)); + } + + static pintsToGallons(pints: number): number { + const result = new Decimal(pints).mul(this.PINTS_TO_GALLONS); + return Number(result.toFixed(5)); + } + + static gallonsToMillilitres(gallons: number): number { + const result = new Decimal(gallons).mul(this.GALLONS_TO_MILLILITRES); + return Number(result.toFixed(5)); + } + + static millilitresToGallons(millilitres: number): number { + const result = new Decimal(millilitres).mul(this.MILLILITRES_TO_GALLONS); + return Number(result.toFixed(5)); + } + + static pintsToFluidOunces(pints: number): number { + const result = new Decimal(pints).mul(this.PINTS_TO_FLUID_OUNCES); + return Number(result.toFixed(5)); + } + + static fluidOuncesToPints(fluidOunces: number): number { + const result = new Decimal(fluidOunces).mul(this.FLUID_OUNCES_TO_PINTS); + return Number(result.toFixed(5)); + } + + static millilitresToFluidOunces(millilitres: number): number { + const result = new Decimal(millilitres).mul(this.MILLILITRE_TO_FLUID_OUNCE); + + return Number(result.toFixed(5)); + } + + static fluidOuncesToMillilitres(fluidOunces: number): number { + const result = new Decimal(fluidOunces).div(this.MILLILITRE_TO_FLUID_OUNCE); + + return Number(result.toFixed(5)); + } + + static litresToGallons(litres: number): number { + const result = new Decimal(litres).mul(this.LITRE_TO_GALLON); + + return Number(result.toFixed(5)); + } + + static litresToMillilitres(litres: number): number { + const result = new Decimal(litres).mul(1000); + + return Number(result.toFixed(5)); + } + + static millilitresToLitres(millilitres: number): number { + const result = new Decimal(millilitres).div(1000); + + return Number(result.toFixed(5)); + } + + static gallonsToLitres(gallons: number): number { + const result = new Decimal(gallons).div(this.LITRE_TO_GALLON); + + return Number(result.toFixed(5)); + } + + static millilitresToPints(millilitres: number): number { + const result = new Decimal(millilitres).mul(this.MILLILITRE_TO_PINT); + + return Number(result.toFixed(5)); + } + + static pintsToMillilitres(pints: number): number { + const result = new Decimal(pints).div(this.MILLILITRE_TO_PINT); + + return Number(result.toFixed(5)); + } + + static gallonsToFluidOunces(gallons: number): number { + const litres = this.gallonsToLitres(gallons); + const millilitres = this.litresToMillilitres(litres); + + return this.millilitresToFluidOunces(millilitres); + } + + static fluidOuncesToGallons(fluidOunces: number): number { + const millilitres = this.fluidOuncesToMillilitres(fluidOunces); + const litres = this.millilitresToLitres(millilitres); + + return this.litresToGallons(litres); + } +} diff --git a/static/assets/home-icon.png b/static/assets/home-icon.png deleted file mode 100644 index c4616ca..0000000 Binary files a/static/assets/home-icon.png and /dev/null differ diff --git a/static/assets/menu-icon.png b/static/assets/menu-icon.png deleted file mode 100644 index 448a82f..0000000 Binary files a/static/assets/menu-icon.png and /dev/null differ diff --git a/static/script.js b/static/script.js deleted file mode 100644 index 3055e8d..0000000 --- a/static/script.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Adds a global event listener to the specified parent element. - * This is lifted from the Web Dev Simplified YouTube channel. - * - * @link https://www.youtube.com/watch?v=cOoP8-NPLSo - * - * @param {string} type - The type of event to listen for. - * @param {string} selector - The CSS selector for the target element(s). - * @param {function} callback - The callback function to be executed when the event is triggered. - * @param {HTMLElement} [parent=document] - The parent element to which the event listener is attached. - */ -function addGlobalEventListener(type, selector, callback, parent = document) { - parent.addEventListener(type, (event) => { - if (event.target.matches(selector)) { - callback(event); - } - }); -} - -/** - * Loads the preferred colour scheme from local storage or the user's system settings and sets the theme accordingly. - * @param themeToggleCheckbox {HTMLInputElement} - An of type checkbox that toggles the theme. - */ -function loadPreferredColourScheme(themeToggleCheckbox) { - const currentTheme = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); - document.documentElement.setAttribute('data-theme', currentTheme); - themeToggleCheckbox.checked = currentTheme === 'dark'; -} - -const switchTheme = { - type: 'change', - selector: '#theme-switch', - callback: (event) => { - const newTheme = event.target.checked ? 'dark' : 'light'; - document.documentElement.setAttribute('data-theme', newTheme); - localStorage.setItem('theme', newTheme); - }, - add: function(){ - addGlobalEventListener(this.type, this.selector, this.callback) - } -}; - -const toggleNavPopover = { - type: 'change', - selector: 'select', - callback: (event) => { - const selectedClass = event.target.className; - if (selectedClass.match('page-select')) { - const navMenuPopover = document.getElementById('nav-menu-popover'); - navMenuPopover.togglePopover(); - } - }, - add: function(){ - addGlobalEventListener(this.type, this.selector, this.callback) - } -} - -document.addEventListener('DOMContentLoaded', () => { - const themeSwitch = document.getElementById('theme-switch'); - loadPreferredColourScheme(themeSwitch); - switchTheme.add(); - toggleNavPopover.add(); -}); \ No newline at end of file diff --git a/static/style.css b/static/style.css deleted file mode 100644 index 654224d..0000000 --- a/static/style.css +++ /dev/null @@ -1,133 +0,0 @@ -@import url('./styles/variables.css'); -@import url('./styles/theme.css'); -@import url('./styles/utility.css'); -@import url('./styles/button.css'); -@import url('./styles/chip.css'); -@import url('./styles/input.css'); -@import url('./styles/globals.css'); -@import url('./styles/modal.css'); -@import url('./styles/nav.css'); -@import url('./styles/switch.css'); - -/* Responsive design */ -@media (min-width: 1024px) { - body { - font-size: 1.125rem; - } - - main { - max-width: 1200px; - padding: 2rem; - } - - header { - text-align: center; - } - - section { - padding: 2rem; - } - - h1 { - font-size: 2.5rem; - } - - h2 { - font-size: 2rem; - } - - h3 { - font-size: 1.75rem; - } - - button { - font-size: 1.25rem; - padding: 1.5rem; - } - - input[type="text"], - textarea { - font-size: 1rem; - } -} - -@media (max-width: 660px) { - #nav-menu-popover:popover-open { - width: 100%; - height: fit-content; - top: 0; - left: 0; - flex-direction: column; - justify-content: space-evenly; - align-items: center; - } - -} - -@media (max-width: 600px) { - body { - font-size: var(--font-size-small); - } - - h1 { - font-size: var(--font-size-large); - } - - main { - padding: 0.5rem; - } - - section { - padding: 0.5rem; - } - - button { - padding: 1rem; - } - - input[type="text"], - textarea { - font-size: var(--font-size-small); - } -} - -/* Chrome Mobile Breakpoint */ -@media (max-width: 418px) { - h1 { - font-size: 1.25rem; - } - - h2 { - font-size: 1rem; - } - - h3 { - font-size: 0.875rem; - } - - h3.nav-title { - display: none; - } - - button { - font-size: 0.875rem; - } - - input[type="text"], - textarea { - font-size: 0.75rem; - } - - nav { - flex-direction: column; - justify-content: center; - align-items: center; - } -} - -/* If the user prefers a smooth scroll make it smooth */ -@media(prefers-reduced-motion: no-preference) { - html { - scroll-behavior: smooth; - } -} \ No newline at end of file diff --git a/static/styles/button.css b/static/styles/button.css deleted file mode 100644 index fee9579..0000000 --- a/static/styles/button.css +++ /dev/null @@ -1,14 +0,0 @@ -button { - background-color: var(--button-background); - color: var(--button-color); - border: none; - border-radius: var(--button-border-radius); - padding: var(--button-padding); - font-size: var(--button-font-size); - cursor: pointer; - transition: background-color 0.3s ease; -} - -button:hover { - background-color: var(--button-hover-background); -} \ No newline at end of file diff --git a/static/styles/chip.css b/static/styles/chip.css deleted file mode 100644 index cdeee43..0000000 --- a/static/styles/chip.css +++ /dev/null @@ -1,79 +0,0 @@ -.chip { - display: inline-flex; - align-items: center; - padding: 0 8px; - height: 24px; - font-size: var(--font-size-small); - line-height: 24px; - border-radius: 12px; - background-color: var(--secondary-color); - color: var(--text-color); - margin: 4px; - font-family: var(--font-family), serif; - border: 1px solid var(--input-border); - transition: background-color 0.3s ease; -} - -.chip:hover { - background-color: var(--input-background); -} - -.chip-label { - padding-right: 6px; - padding-left: 2px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.chip-delete { - display: flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; - border: none; - background-color: var(--button-background); - color: var(--button-color); - font-size: 12px; - cursor: pointer; - padding: 0; - border-radius: 50%; - margin-left: 4px; - transition: background-color 0.3s ease; -} - -.chip-delete:hover { - cursor: pointer; - background-color: var(--button-hover-background); -} - -.chip-delete:focus { - outline: none; - box-shadow: 0 0 0 2px var(--card-shadow); -} - -button.chip-delete { - display: flex; - align-items: center; - justify-content: center; - width: 22px; - height: 22px; - border: none; - background-color: #9e9e9e; - color: #e0e0e0; - font-size: 14px; - cursor: pointer; - padding: 0; - border-radius: 50%; - margin-left: 4px; -} - -button.chip-delete:hover { - background-color: #757575; -} - -button.chip-delete:focus { - outline: none; - box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1); -} diff --git a/static/styles/globals.css b/static/styles/globals.css deleted file mode 100644 index 2eec7e0..0000000 --- a/static/styles/globals.css +++ /dev/null @@ -1,67 +0,0 @@ -body { - font-family: var(--font-family), serif; - font-size: var(--font-size-base); - line-height: 1.6; - color: var(--text-color); - background-color: var(--secondary-color); - margin: 0; - padding: 0; -} - -main { - max-width: 800px; - margin: 0 auto; - padding: 1rem; -} - -header { - text-align: center; - margin-bottom: 2rem; - position: sticky; - top: 0; - background-color: var(--secondary-color); - z-index: 1000; -} - -footer { - background-color: var(--secondary-color); - color: var(--text-color); - text-align: center; - padding: 1rem; - position: sticky; - bottom: 0; - z-index: 0; -} - -h1, -h2, -h3 { - color: var(--primary-color); - text-align: center; -} - -p { - margin-bottom: 1rem; -} - -label { - display: block; - margin-bottom: 0.5rem; -} - -a { - color: var(--link-color); - text-decoration: none; - transition: color 0.3s ease; -} - -a:hover { - color: var(--link-hover-color); -} - -code { - background-color: var(--secondary-color); - color: var(--text-color); - padding: 5px; - border-radius: 4px; -} \ No newline at end of file diff --git a/static/styles/input.css b/static/styles/input.css deleted file mode 100644 index b58b916..0000000 --- a/static/styles/input.css +++ /dev/null @@ -1,24 +0,0 @@ -input[type="text"], -input[type="colour"], -select, -textarea { - min-width: 8rem; - width: fit-content; - padding: 10px; - margin: 8px 0; - box-sizing: border-box; - border: 1px solid var(--input-border); - border-radius: 4px; - font-size: 1rem; - background: var(--input-background); - color: var(--input-text-color); - transition: border-color 0.3s ease; -} - -input[type="text"]:focus, -select:focus, -input[type="colour"]:focus, -textarea:focus { - border-color: var(--primary-color); - outline: transparent; -} \ No newline at end of file diff --git a/static/styles/modal.css b/static/styles/modal.css deleted file mode 100644 index 8dc8323..0000000 --- a/static/styles/modal.css +++ /dev/null @@ -1,22 +0,0 @@ -#modal-dialog { - background: var(--secondary-color); - color: var(--text-color); -} - -/* Distinguish between the top and bottom layers when opening a modal dialog */ -#modal-dialog::backdrop { - background: #fff5; - backdrop-filter: blur(4px); -} - -/* Transition the dialog (currently only in firefox & chromium) */ -#modal-dialog { - opacity: 1; - transform: scale(1); - transition: all 0.5s ease-in-out; - - @starting-style { - opacity: 0; - transform: scale(0.8); - } -} \ No newline at end of file diff --git a/static/styles/nav.css b/static/styles/nav.css deleted file mode 100644 index f96e8d1..0000000 --- a/static/styles/nav.css +++ /dev/null @@ -1,78 +0,0 @@ -#nav-menu-popover:popover-open { - width: 50%; - position: absolute; - inset: unset; - top: 0; - left: 2.65rem; - margin: 0; - display: flex; - border: none; - flex-direction: row; - justify-content: space-between; - align-items: center; - background-color: var(--card-background); -} - -nav { - background-color: var(--card-background); - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - height: 6rem; - - img.icon { - height: 1.65rem; - display: block; - border-radius: 50%; - background-color: var(--button-background); - } - - button.round-icon { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - margin: 1rem; - border-radius: 50%; - background-color: var(--button-background); - color: var(--button-color); - border: none; - height: 1.65rem; /* Adjust size as needed */ - } - - button.round-icon img { - /*width: 1.65rem; !* Adjust size as needed *!*/ - /*border-radius: 50%;*/ - object-fit: cover; - } - - .active { - float: left; - display: block; - border-radius: 50%; - text-align: center; - padding: 0 10px; - height: 1.65rem; - text-decoration: none; - background-color: var(--button-background); - color: var(--button-color); - } - - .active:hover { - background-color: var(--button-hover-background); - color: var(--button-color) - } - - label { - display: flex; - flex-direction: column-reverse; - } - - label>span { - text-align: center; - font-size: small; - transform-origin: top center; - color: var(--button-color) - } -} \ No newline at end of file diff --git a/static/styles/switch.css b/static/styles/switch.css deleted file mode 100644 index 71afce3..0000000 --- a/static/styles/switch.css +++ /dev/null @@ -1,46 +0,0 @@ -.switch { - margin: 1rem; - appearance: none; - position: relative; - display: inline-block; - background: lightgrey; - height: 1.65rem; - width: 2.75rem; - vertical-align: middle; - border-radius: 2rem; - box-shadow: 0 1px 3px var(--card-shadow) inset; - transition: 0.25s linear background; -} - -.switch::before { - content: ''; - display: block; - width: 1.25rem; - height: 1.25rem; - background: #fff; - border-radius: 1.2rem; - position: absolute; - top: 0.2rem; - left: 0.2rem; - box-shadow: 0 1px 3px #0003; - transition: 0.25s linear transform; - transform: translateX(0rem); -} - -.switch:checked { - background: var(--primary-color); -} - -.switch:not(:checked), -.switch:not(:checked):focus-visible { - background-color: var(--primary-color); -} - -.switch:checked::before { - transform: translateX(1rem); -} - -.switch:focus-visible { - outline: 2px solid var(--primary-color); - outline-offset: 2px; -} \ No newline at end of file diff --git a/static/styles/theme.css b/static/styles/theme.css deleted file mode 100644 index 9b6f653..0000000 --- a/static/styles/theme.css +++ /dev/null @@ -1,33 +0,0 @@ -[data-theme="light"] { - --text-color: #1a1a1a; - --primary-color: #6200ea; - --secondary-color: #e0e0e0; - --card-background: #ffffff; - --button-background: #6200ea; - --card-border: #b0b0b0; - --card-shadow: rgba(0, 0, 0, 0.1); - --button-color: #ffffff; - --button-hover-background: #3700b3; - --link-color: #6200ea; - --link-hover-color: #3700b3; - --input-background: #ffffff; - --input-border: #8a8a8a; - --input-text-color: #1a1a1a; -} - -[data-theme="dark"] { - --text-color: #e0e0e0; - --primary-color: #1e88e5; - --secondary-color: #444; - --card-background: #333; - --card-border: #222; - --card-shadow: rgba(0, 0, 0, 0.6); - --button-background: #1e88e5; - --button-hover-background: #1565c0; - --button-color: #ffffff; - --link-color: #1e88e5; - --link-hover-color: #1565c0; - --input-background: #444; - --input-border: #333; - --input-text-color: #e0e0e0; -} \ No newline at end of file diff --git a/static/styles/utility.css b/static/styles/utility.css deleted file mode 100644 index 1c8237a..0000000 --- a/static/styles/utility.css +++ /dev/null @@ -1,87 +0,0 @@ -.card { - background-color: var(--card-background); - border: 1px solid var(--card-border); - border-radius: var(--card-radius); - box-shadow: 0 4px 8px var(--card-shadow); - padding: 1rem; - margin-bottom: 2rem; -} - -.centred-row { - display: flex; - flex-direction: row; - gap: 1rem; - justify-content: center; -} - -.centred-column { - display: flex; - flex-direction: column; - gap: 1rem; - align-items: center; -} - -.box { - width: 6rem; - height: 6rem; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - background: var(--button-background); - overflow: auto; -} - -summary:hover { - cursor: pointer; -} - -details > summary { - list-style: none; -} - -.footnote:hover { - cursor: help; -} - -fieldset { - display: flex; - flex-direction: column; -} - -form #other-text { - display: none; -} - -form:has(#other:checked) #other-text { - display: block; -} - -.resize-vertical { - resize: vertical; -} - -.resize-horizontal { - resize: horizontal; -} - -.resize-both { - resize: both; -} - -.morph { - background: var(--secondary-color); - box-shadow: 0 0 0 transparent, 0 0 0 transparent; - transition: box-shadow 0.2s ease-in-out; -} - -.morph:hover { - box-shadow: 12px 12px 12px var(--text-color), -10px -10px 10px var(--secondary-color); - transition: .2s ease-in-out box-shadow; -} - -.gradient { - background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet); - background-clip: text; - color: transparent; -} \ No newline at end of file diff --git a/static/styles/variables.css b/static/styles/variables.css deleted file mode 100644 index 31e061c..0000000 --- a/static/styles/variables.css +++ /dev/null @@ -1,10 +0,0 @@ -:root { - --font-family: 'Roboto', sans-serif; - --font-size-base: 16px; - --font-size-large: 1.5rem; - --font-size-small: 0.875rem; - --card-radius: 8px; - --button-border-radius: 12px; - --button-padding: 10px 20px; - --button-font-size: 1rem; -} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}