From 825e129cb011b2c2fa6bf0e544feed05ac2fe11f Mon Sep 17 00:00:00 2001 From: dankbjss Date: Wed, 10 Sep 2025 14:14:36 +0100 Subject: [PATCH] new and improved --- .editorconfig | 8 + .github/workflows/deploy.yaml | 21 + .github/workflows/test.yaml | 16 + .gitignore | 175 ++ README.md | 36 + bun.lock | 34 + index.html | 183 -- index.js | 36 - index.ts | 24 + package.json | 18 + public/colour-picker.html | 29 + public/home.html | 40 + public/index.html | 117 + public/numbers.html | 44 + public/static/index.js | 2997 +++++++++++++++++++++++ public/static/style.css | 209 ++ public/timestamps.html | 108 + public/units.html | 139 ++ src/colour/hex-to-hsl.test.ts | 20 + src/colour/hex-to-hsl.ts | 38 + src/colour/hex-to-rgb.test.ts | 20 + src/colour/hex-to-rgb.ts | 7 + src/index.ts | 7 + src/numbers/scientific-notation.test.ts | 49 + src/numbers/scientific-notation.ts | 20 + src/timestamps/ecma.test.ts | 113 + src/timestamps/ecma.ts | 34 + src/timestamps/iso-string.test.ts | 83 + src/timestamps/iso-string.ts | 22 + src/timestamps/unix.test.ts | 114 + src/timestamps/unix.ts | 34 + src/units/convert-unit.test.ts | 109 + src/units/convert-unit.ts | 211 ++ src/units/distance-converter.test.ts | 580 +++++ src/units/distance-converter.ts | 182 ++ src/units/mass-converter.test.ts | 220 ++ src/units/mass-converter.ts | 68 + src/units/model.ts | 60 + src/units/temperatre-converter.ts | 46 + src/units/temperature-converter.test.ts | 118 + src/units/volume-converter.test.ts | 293 +++ src/units/volume-converter.ts | 130 + style.css | 519 ---- tsconfig.json | 27 + 44 files changed, 6620 insertions(+), 738 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/workflows/deploy.yaml create mode 100644 .github/workflows/test.yaml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bun.lock delete mode 100644 index.html delete mode 100644 index.js create mode 100644 index.ts create mode 100644 package.json create mode 100644 public/colour-picker.html create mode 100644 public/home.html create mode 100644 public/index.html create mode 100644 public/numbers.html create mode 100644 public/static/index.js create mode 100644 public/static/style.css create mode 100644 public/timestamps.html create mode 100644 public/units.html create mode 100644 src/colour/hex-to-hsl.test.ts create mode 100644 src/colour/hex-to-hsl.ts create mode 100644 src/colour/hex-to-rgb.test.ts create mode 100644 src/colour/hex-to-rgb.ts create mode 100644 src/index.ts create mode 100644 src/numbers/scientific-notation.test.ts create mode 100644 src/numbers/scientific-notation.ts create mode 100644 src/timestamps/ecma.test.ts create mode 100644 src/timestamps/ecma.ts create mode 100644 src/timestamps/iso-string.test.ts create mode 100644 src/timestamps/iso-string.ts create mode 100644 src/timestamps/unix.test.ts create mode 100644 src/timestamps/unix.ts create mode 100644 src/units/convert-unit.test.ts create mode 100644 src/units/convert-unit.ts create mode 100644 src/units/distance-converter.test.ts create mode 100644 src/units/distance-converter.ts create mode 100644 src/units/mass-converter.test.ts create mode 100644 src/units/mass-converter.ts create mode 100644 src/units/model.ts create mode 100644 src/units/temperatre-converter.ts create mode 100644 src/units/temperature-converter.test.ts create mode 100644 src/units/volume-converter.test.ts create mode 100644 src/units/volume-converter.ts delete mode 100644 style.css create mode 100644 tsconfig.json 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 new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +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 + +# 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/index.html b/index.html deleted file mode 100644 index 8ea29fe..0000000 --- a/index.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - Web Components - - - - - - -
-

Web Components

-

With Minimal JavaScript

-
-
-
-

JavaScript Out, CSS & HTML In

-

JavaScript frameworks are great. They allow you to create web apps with rich functionality, but at the - cost of having to learn the ins and outs of how that framework implements that functionality, all - the while bloating your project with tonnes of packages.

-

We go to frameworks because they allow us to build complex components. But many of these 'complex' - components can be built with just the Holy Trinity™ of web develeopment (HTML, CSS and - JavaScript). A lot of the time you don't even need 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 inspiriation* - from others. -

-

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

-
- -
-

Switch it Up

-

A modern approach to the switch with no JavaScript. This replicates the switch type that is currently - only availabe in safari, but hopefully will be added to other browsers soon.

-
- -
-

* 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 darkmode on or off with only css as per Coding2GO's video. - This implementation, while requiing some code, checks if the user has a preference for darkmode and - defaults the page accordingly while still allowing the user to flip between light and dark.

-
- -
-

HTML, you Autocomplete Me

-

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

-
- - - - - - - -
-
- -
-

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.

-
- -
-
- -
-

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.

-
-
- -
-

Dropdown with the Sickness

-

You can make a dropdown menu without JavaScript, and there are multiple ways to do this. IN this instance - I'm making use of an <input type="checkbox"> element. This can also be achieved with - an - <a> tag as you can see here (which is where I lifted the example from). -

-
- -
- -
- -
-

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>

-
- - -
-
-
-
- -
-

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

-
-
-
- -
-

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

-
-
-
- -
-

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

-
-
-
- - - - - diff --git a/index.js b/index.js deleted file mode 100644 index 17127e0..0000000 --- a/index.js +++ /dev/null @@ -1,36 +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 attatched. - */ -function addGlobalEventListner(type, selector, callback, parent = document) { - parent.addEventListener(type, (event) => { - if (event.target.matches(selector)) { - callback(event); - } - }); -}; - -addGlobalEventListner('click', '#open-dialog-button', (e) => { - const modalDialog = document.getElementById('modal-dialog'); - modalDialog.showModal(); -}); - -document.addEventListener('DOMContentLoaded', () => { - const themeToggleCheckbox = document.getElementById('switch'); - const currentTheme = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); - document.documentElement.setAttribute('data-theme', currentTheme); - themeToggleCheckbox.checked = currentTheme === 'dark'; - - addGlobalEventListner('change', '#switch', () => { - const newTheme = themeToggleCheckbox.checked ? 'dark' : 'light'; - document.documentElement.setAttribute('data-theme', newTheme); - localStorage.setItem('theme', newTheme); - }); -}); \ 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/style.css b/style.css deleted file mode 100644 index 9baac6d..0000000 --- a/style.css +++ /dev/null @@ -1,519 +0,0 @@ -/* Variables for colors and fonts */ -:root { - --text-color: #333; - --primary-color: #4CAF50; - --secondary-color: #f1f1f1; - --font-family: 'Roboto', sans-serif; - --font-size-base: 16px; - --font-size-large: 1.5rem; - --font-size-small: 0.875rem; - --card-background: #f9f9f9; - --card-border: #ddd; - --card-shadow: rgba(0, 0, 0, 0.1); - --card-radius: 8px; - --button-background: #4CAF50; - --button-color: #f9f9f9; - --button-hover-background: #45a049; - --button-border-radius: 12px; - --button-padding: 10px 20px; - --button-font-size: 1rem; - --link-color: #4CAF50; - --link-hover-color: #45a049; - --input-background: #f9f9f9; - --input-border: #ccc; - --input-text-color: #000; -} - -[data-theme="dark"] { - --text-color: #f1f1f1; - --primary-color: #2196F3; - --secondary-color: #333; - --card-background: #444; - --card-border: #333; - --card-shadow: rgba(0, 0, 0, 0.5); - --button-background: #2196F3; - --button-hover-background: #1e88e5; - --button-color: #fff; - --link-color: #2196F3; - --link-hover-color: #1e88e5; - --input-background: #555; - --input-border: #444; - --input-text-color: #fff; -} - -/* Global styles */ -body { - font-family: var(--font-family); - 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; -} - -/* Button styles */ -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); -} - -/* Input styles */ -input[type="text"], -input[type="email"], -input[type="password"], -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, -input[type="email"]:focus, -input[type="password"]:focus, -select:focus, -textarea:focus { - border-color: var(--primary-color); - outline: transparent; -} - -/* Utility Classes */ -.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; -} - -/* Styles for a switch checkbox */ -#switch { - appearance: none; - position: relative; - display: inline-block; - background: lightgrey; - height: 1.65rem; - width: 2.75rem; - vertical-align: middle; - border-radius: 2rem; - box-shadow: 0px 1px 3px #0003 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: 0px 1px 3px #0003; - transition: 0.25s linear transform; - transform: translateX(0rem); -} - -#switch:checked { - background: var(--primary-color); -} - -#switch:checked::before { - transform: translateX(1rem); -} - -#switch:focus-visible { - outline: 2px solid dodgerblue; - outline-offset: 2px; -} - -/* Make the summary tag explicitly interactble */ -summary:hover { - cursor: pointer; -} - -/* Dropdown stylings */ -.dropdown { - position: relative; - display: inline-block; - } - - .dropdown > input[type="checkbox"] { - position: absolute; - left: -100vw; - } - - .dropdown > label, - .dropdown > a[role="button"] { - display: inline-block; - padding: 6px 15px; - color: var(--input-text-color); - line-height: 1.5em; - text-decoration: none; - border: 1px solid var(--input-border); - cursor: pointer; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - } - - .dropdown > label:hover, - .dropdown > a[role="button"]:hover, - .dropdown > a[role="button"]:focus { - border-color: var(--input-border); - } - - .dropdown > label:after, - .dropdown > a[role="button"]:after { - content: "\25BC"; - display: inline-block; - margin-left: 6px; - } - - .dropdown > ul { - position: absolute; - z-index: 999; - display: block; - left: -100vw; - top: calc(1.5em + 14px); - border: 1px solid var(--input-border); - background: var(--card-background); - padding: 6px 0; - margin: 0; - list-style: none; - width: 100%; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - -webkit-box-shadow: 0 3px 8px var(--card-shadow); - -moz-box-shadow: 0 3px 8px var(--card-shadow); - box-shadow: 0 3px 8px var(--card-shadow); - } - - .dropdown > ul a { - display: block; - padding: 6px 15px; - text-decoration: none; - color: var(--input-text-color); - } - - .dropdown > ul a:hover, - .dropdown > ul a:focus { - background: var(--input-background); - } - - .dropdown > input[type="checkbox"]:checked ~ ul, - .dropdown > ul:target { - left: 0; - } - - .dropdown > [type="checkbox"]:checked + label:after, - .dropdown > ul:target ~ a:after { - content: "\25B2"; - } - - .dropdown a.close { - display: none; - } - - .dropdown > ul:target ~ a.close { - display: block; - position: absolute; - left: 0; - top: 0; - height: 100%; - width: 100%; - text-indent: -100vw; - z-index: 1000; - } - - -/* Dialog stylings */ -#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); - } -} - -/* Stylings for creating a dynamic radio entry */ -fieldset { - display: flex; - flex-direction: column; -} - -form #other-text { - display: none; -} - -form:has(#other:checked) #other-text { - display: block; -} - -/* Stylings for resizable elements */ -.resize-vertical { - resize: vertical; -} - -.resize-horizontal { - resize: horizontal; -} - -.resize-both { - resize: both; -} - -/* Stylings for new morphism */ -.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; -} - -/* Stylings for text gradients */ -.gradient { - background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet); - background-clip: text; - color: transparent; -} - -/* 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"], - input[type="email"], - input[type="password"], - textarea { - font-size: 1rem; - } -} - -@media (max-width: 600px) { - body { - font-size: var(--font-size-small); - } - - h1 { - font-size: var(--font-size-large); - } - - main { - padding: 0.5rem; - } - - header { - text-align: left; - } - - section { - padding: 0.5rem; - } - - button { - width: 100%; - padding: 1rem; - } - - input[type="text"], - input[type="email"], - input[type="password"], - textarea { - font-size: var(--font-size-small); - } -} - -@media (max-width: 400px) { - h1 { - font-size: 1.25rem; - } - - h2 { - font-size: 1rem; - } - - h3 { - font-size: 0.875rem; - } - - button { - font-size: 0.875rem; - width: auto; - padding: 0.5rem 1rem; - } - - input[type="text"], - input[type="email"], - input[type="password"], - textarea { - font-size: 0.75rem; - } -} - -/* If the user prefers a smooth scroll make it smooth */ -@media(prefers-reduced-motion: no-preference) { - html { - scroll-behavior: smooth; - } -} 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 + } +}