From 5ab3756962dc2d418952b01f1075947df5c9263c Mon Sep 17 00:00:00 2001 From: dankcgi Date: Fri, 14 Nov 2025 11:30:05 +0000 Subject: [PATCH 1/3] feat: added contrast checker --- bun.lock | 97 +++++++++++++++++++++++ index.ts | 24 ------ package.json | 8 +- public/colour-picker.html | 110 ++++++++++++++++++++------ public/index.html | 4 + public/static/style.css | 60 ++++++++++---- src/colour/contrast-ratio.test.ts | 31 ++++++++ src/colour/contrast-ratio.ts | 14 ++++ src/colour/relative-luminance.test.ts | 27 +++++++ src/colour/relative-luminance.ts | 21 +++++ src/colour/wcag-compliance.test.ts | 49 ++++++++++++ src/colour/wcag-compliance.ts | 17 ++++ src/index.ts | 5 ++ 13 files changed, 400 insertions(+), 67 deletions(-) delete mode 100644 index.ts create mode 100644 src/colour/contrast-ratio.test.ts create mode 100644 src/colour/contrast-ratio.ts create mode 100644 src/colour/relative-luminance.test.ts create mode 100644 src/colour/relative-luminance.ts create mode 100644 src/colour/wcag-compliance.test.ts create mode 100644 src/colour/wcag-compliance.ts diff --git a/bun.lock b/bun.lock index 614dd57..44144f6 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ }, "devDependencies": { "@types/bun": "latest", + "http-server": "^14.1.1", }, "peerDependencies": { "typescript": "^5.0.0", @@ -21,14 +22,110 @@ "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], + "ansi-styles": ["ansi-styles@4.3.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "async": ["async@3.2.6", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/async/-/async-3.2.6.tgz", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "basic-auth": ["basic-auth@2.0.1", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", { "dependencies": { "safe-buffer": "5.1.2" } }, "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg=="], + "bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/call-bound/-/call-bound-1.0.4.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "chalk": ["chalk@4.1.2", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/chalk/-/chalk-4.1.2.tgz", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "color-convert": ["color-convert@2.0.1", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/color-convert/-/color-convert-2.0.1.tgz", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/color-name/-/color-name-1.1.4.tgz", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "corser": ["corser@2.0.1", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/corser/-/corser-2.0.1.tgz", {}, "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ=="], + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "debug": ["debug@4.4.3", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + "dunder-proto": ["dunder-proto@1.0.1", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "es-define-property": ["es-define-property@1.0.1", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "eventemitter3": ["eventemitter3@4.0.7", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + + "follow-redirects": ["follow-redirects@1.15.11", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], + + "function-bind": ["function-bind@1.1.2", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "gopd": ["gopd@1.2.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/gopd/-/gopd-1.2.0.tgz", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-flag": ["has-flag@4.0.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/has-flag/-/has-flag-4.0.0.tgz", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "hasown": ["hasown@2.0.2", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "he": ["he@1.2.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/he/-/he-1.2.0.tgz", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], + + "html-encoding-sniffer": ["html-encoding-sniffer@3.0.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", { "dependencies": { "whatwg-encoding": "^2.0.0" } }, "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA=="], + + "http-proxy": ["http-proxy@1.18.1", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", { "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" } }, "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ=="], + + "http-server": ["http-server@14.1.1", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/http-server/-/http-server-14.1.1.tgz", { "dependencies": { "basic-auth": "^2.0.1", "chalk": "^4.1.2", "corser": "^2.0.1", "he": "^1.2.0", "html-encoding-sniffer": "^3.0.0", "http-proxy": "^1.18.1", "mime": "^1.6.0", "minimist": "^1.2.6", "opener": "^1.5.1", "portfinder": "^1.0.28", "secure-compare": "3.0.1", "union": "~0.5.0", "url-join": "^4.0.1" }, "bin": { "http-server": "bin/http-server" } }, "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A=="], + + "iconv-lite": ["iconv-lite@0.6.3", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mime": ["mime@1.6.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/mime/-/mime-1.6.0.tgz", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + + "minimist": ["minimist@1.2.8", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/minimist/-/minimist-1.2.8.tgz", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "ms": ["ms@2.1.3", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "object-inspect": ["object-inspect@1.13.4", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "opener": ["opener@1.5.2", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/opener/-/opener-1.5.2.tgz", { "bin": { "opener": "bin/opener-bin.js" } }, "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="], + + "portfinder": ["portfinder@1.0.38", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/portfinder/-/portfinder-1.0.38.tgz", { "dependencies": { "async": "^3.2.6", "debug": "^4.3.6" } }, "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg=="], + + "qs": ["qs@6.14.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/qs/-/qs-6.14.0.tgz", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], + + "requires-port": ["requires-port@1.0.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/requires-port/-/requires-port-1.0.0.tgz", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], + + "safe-buffer": ["safe-buffer@5.1.2", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "safer-buffer": ["safer-buffer@2.1.2", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "secure-compare": ["secure-compare@3.0.1", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", {}, "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw=="], + + "side-channel": ["side-channel@1.1.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/side-channel/-/side-channel-1.1.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "supports-color": ["supports-color@7.2.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/supports-color/-/supports-color-7.2.0.tgz", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "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=="], + + "union": ["union@0.5.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/union/-/union-0.5.0.tgz", { "dependencies": { "qs": "^6.4.0" } }, "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA=="], + + "url-join": ["url-join@4.0.1", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/url-join/-/url-join-4.0.1.tgz", {}, "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="], + + "whatwg-encoding": ["whatwg-encoding@2.0.0", "https://metoffice.jfrog.io/artifactory/api/npm/npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg=="], } } diff --git a/index.ts b/index.ts deleted file mode 100644 index 3913b44..0000000 --- a/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -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 index c8487fa..6981999 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,13 @@ "module": "index.ts", "type": "module", "scripts": { - "dev": "bun index.ts", - "build": "bun build src/index.ts --outdir=public/static" + "dev": "bun run build:dev && http-server public -c-1 -o", + "build:dev": "bun build src/index.ts --outdir=public/static", + "build": "bun build src/index.ts --outdir=dist --minify" }, "devDependencies": { - "@types/bun": "latest" + "@types/bun": "latest", + "http-server": "^14.1.1" }, "peerDependencies": { "typescript": "^5.0.0" diff --git a/public/colour-picker.html b/public/colour-picker.html index a87d7e4..60067bf 100644 --- a/public/colour-picker.html +++ b/public/colour-picker.html @@ -1,29 +1,87 @@
-

Colour Picker

- -
- - -
- -
-

Colour Values

-

Hex:

-

RGB:

-

HSL:

-
+ > +

Colours

+ +
+ Colour Picker + +
+ + +
+ +
+

Colour Values

+
    +
  • HEX:
  • +
  • RGB:
  • +
  • HSL:
  • +
+
+
+ +
+ Contrast Checker + +
+ + +
+ +
+ + +
+ +
+

Contrast Results

+
    +
  • Contrast Ratio:
  • +
  • WCAG AA (Normal Text):
  • +
  • WCAG AA (Large Text):
  • +
  • WCAG AAA (Normal Text):
  • +
  • WCAG AAA (Large Text):
  • +
+
+
diff --git a/public/index.html b/public/index.html index 30f5a35..ce11442 100644 --- a/public/index.html +++ b/public/index.html @@ -18,6 +18,8 @@ convertUnixTimestamp, hexToRgb, hexToHsl, + contrastRatio, + checkWcagCompliance, toScientificNotation } from './static/index.js'; @@ -26,6 +28,8 @@ window.convertIsoString = convertIsoString; window.hexToRgb = hexToRgb; window.hexToHsl = hexToHsl; + window.contrastRatio = contrastRatio; + window.checkWcagCompliance = checkWcagCompliance; window.toScientificNotation = toScientificNotation; window.convertUnit = convertUnit; diff --git a/public/static/style.css b/public/static/style.css index 070c1ea..2ba199c 100644 --- a/public/static/style.css +++ b/public/static/style.css @@ -194,27 +194,59 @@ fieldset { } } -#colour-picker { - .form-input { - margin: 0 auto; - max-width: fit-content; - margin-bottom: var(--size-2); +#colours { + fieldset { + display: grid; + gap: var(--size-2); + } - input { - justify-self: center; - } + .form-input { + margin: 0 auto; + max-width: fit-content; + margin-bottom: var(--size-2); } - #colour-values { + .colour-results { margin-inline: var(--size-fluid-3); - p { - display: grid; - gap: var(--size-1); - grid-template-columns: 1fr 1fr; + h3 { + margin-bottom: var(--size-2); + } + + ul { + list-style: none; + padding: 0; + margin: 0; + } + + li:not(:last-child) { margin-bottom: var(--size-2); } } + + fieldset#colour-picker { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + + .form-input { + grid-template-rows: 1fr; + grid-template-columns: auto auto; + + input { + justify-self: center; + } + } + } + + fieldset#contrast-checker { + grid-template-rows: auto 1fr; + grid-template-columns: repeat(2, 1fr); + + .form-input { + grid-template-columns: 1fr auto; + grid-template-rows: 1fr; + } + } } #timestamps { @@ -223,4 +255,4 @@ fieldset { grid-template-rows: repeat(2, 1fr); gap: var(--size-2); } -} +} \ No newline at end of file diff --git a/src/colour/contrast-ratio.test.ts b/src/colour/contrast-ratio.test.ts new file mode 100644 index 0000000..42c4d8b --- /dev/null +++ b/src/colour/contrast-ratio.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'bun:test'; +import { contrastRatio } from './contrast-ratio'; + +describe('contrastRatio', () => { + const testCases = [ + { hex1: '#FFFFFF', hex2: '#000000', expected: '21.00' }, // Maximum contrast + { hex1: '#FFFFFF', hex2: '#FFFFFF', expected: '1.00' }, // Minimum contrast + { hex1: '#000000', hex2: '#000000', expected: '1.00' }, // Minimum contrast + { hex1: '#FFFFFF', hex2: '#777777', expected: '4.48' }, // AA compliant + { hex1: '#000000', hex2: '#777777', expected: '4.69' }, // AA compliant + { hex1: '#0000FF', hex2: '#FFFF00', expected: '8.00' }, // AAA compliant + { hex1: '#FF0000', hex2: '#00FF00', expected: '2.91' }, // Not AA compliant + ]; + + testCases.forEach(({ hex1, hex2, expected }) => { + it(`should calculate contrast ratio between ${hex1} and ${hex2}`, () => { + expect(contrastRatio(hex1, hex2)).toBe(expected); + }); + }); + + it('should return the same ratio regardless of order', () => { + const ratio1 = contrastRatio('#FFFFFF', '#000000'); + const ratio2 = contrastRatio('#000000', '#FFFFFF'); + expect(ratio1).toBe(ratio2); + }); + + it('should handle lowercase hex values', () => { + const result = contrastRatio('#ffffff', '#000000'); + expect(result).toBe('21.00'); + }); +}); diff --git a/src/colour/contrast-ratio.ts b/src/colour/contrast-ratio.ts new file mode 100644 index 0000000..ba4ae05 --- /dev/null +++ b/src/colour/contrast-ratio.ts @@ -0,0 +1,14 @@ +import Decimal from 'decimal.js'; +import { relativeLuminance } from './relative-luminance'; + +export const contrastRatio = (hex1: string, hex2: string): string => { + const lum1 = relativeLuminance(hex1); + const lum2 = relativeLuminance(hex2); + + const lighter = Decimal.max(lum1, lum2); + const darker = Decimal.min(lum1, lum2); + + const ratio = lighter.plus(0.05).div(darker.plus(0.05)); + + return ratio.toDecimalPlaces(2, Decimal.ROUND_HALF_UP).toFixed(2); +}; diff --git a/src/colour/relative-luminance.test.ts b/src/colour/relative-luminance.test.ts new file mode 100644 index 0000000..63092cb --- /dev/null +++ b/src/colour/relative-luminance.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'bun:test'; +import { relativeLuminance } from './relative-luminance'; +import Decimal from 'decimal.js'; + +describe('relativeLuminance', () => { + const testCases = [ + { hex: '#FFFFFF', expected: '1.000' }, // White + { hex: '#000000', expected: '0.000' }, // Black + { hex: '#FF0000', expected: '0.213' }, // Red + { hex: '#00FF00', expected: '0.715' }, // Green + { hex: '#0000FF', expected: '0.072' }, // Blue + { hex: '#808080', expected: '0.216' }, // Gray + { hex: '#FFFF00', expected: '0.928' }, // Yellow + ]; + + testCases.forEach(({ hex, expected }) => { + it(`should calculate relative luminance for ${hex}`, () => { + const result = relativeLuminance(hex); + expect(result.toDecimalPlaces(3, Decimal.ROUND_HALF_UP).toFixed(3)).toBe(expected); + }); + }); + + it('should handle lowercase hex values', () => { + const result = relativeLuminance('#ffffff'); + expect(result.toDecimalPlaces(3, Decimal.ROUND_HALF_UP).toFixed(3)).toBe('1.000'); + }); +}); diff --git a/src/colour/relative-luminance.ts b/src/colour/relative-luminance.ts new file mode 100644 index 0000000..a75988a --- /dev/null +++ b/src/colour/relative-luminance.ts @@ -0,0 +1,21 @@ +import Decimal from 'decimal.js'; + +export const relativeLuminance = (hex: string): Decimal => { + 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 rLinear = red.lte(0.03928) + ? red.div(12.92) + : red.plus(0.055).div(1.055).pow(2.4); + + const gLinear = green.lte(0.03928) + ? green.div(12.92) + : green.plus(0.055).div(1.055).pow(2.4); + + const bLinear = blue.lte(0.03928) + ? blue.div(12.92) + : blue.plus(0.055).div(1.055).pow(2.4); + + return rLinear.times(0.2126).plus(gLinear.times(0.7152)).plus(bLinear.times(0.0722)); +}; diff --git a/src/colour/wcag-compliance.test.ts b/src/colour/wcag-compliance.test.ts new file mode 100644 index 0000000..3c2c835 --- /dev/null +++ b/src/colour/wcag-compliance.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from 'bun:test'; +import { checkWcagCompliance } from './wcag-compliance'; + +describe('checkWcagCompliance', () => { + const testCases = [ + { + ratio: '21.00', + expected: { aaNormal: true, aaLarge: true, aaaNormal: true, aaaLarge: true }, + description: 'Maximum contrast (white on black)', + }, + { + ratio: '1.00', + expected: { aaNormal: false, aaLarge: false, aaaNormal: false, aaaLarge: false }, + description: 'Minimum contrast (same color)', + }, + { + ratio: '7.00', + expected: { aaNormal: true, aaLarge: true, aaaNormal: true, aaaLarge: true }, + description: 'Exactly AAA normal threshold', + }, + { + ratio: '4.50', + expected: { aaNormal: true, aaLarge: true, aaaNormal: false, aaaLarge: true }, + description: 'Exactly AA normal and AAA large threshold', + }, + { + ratio: '4.49', + expected: { aaNormal: false, aaLarge: true, aaaNormal: false, aaaLarge: false }, + description: 'Just below AA normal threshold', + }, + { + ratio: '3.00', + expected: { aaNormal: false, aaLarge: true, aaaNormal: false, aaaLarge: false }, + description: 'Exactly AA large threshold', + }, + { + ratio: '2.99', + expected: { aaNormal: false, aaLarge: false, aaaNormal: false, aaaLarge: false }, + description: 'Just below AA large threshold', + }, + ]; + + testCases.forEach(({ ratio, expected, description }) => { + it(`should correctly evaluate ${description}`, () => { + const result = checkWcagCompliance(ratio); + expect(result).toEqual(expected); + }); + }); +}); diff --git a/src/colour/wcag-compliance.ts b/src/colour/wcag-compliance.ts new file mode 100644 index 0000000..d6aa150 --- /dev/null +++ b/src/colour/wcag-compliance.ts @@ -0,0 +1,17 @@ +export interface WcagCompliance { + aaNormal: boolean; + aaLarge: boolean; + aaaNormal: boolean; + aaaLarge: boolean; +} + +export const checkWcagCompliance = (ratio: string): WcagCompliance => { + const numericRatio = parseFloat(ratio); + + return { + aaNormal: numericRatio >= 4.5, + aaLarge: numericRatio >= 3, + aaaNormal: numericRatio >= 7, + aaaLarge: numericRatio >= 4.5, + }; +}; diff --git a/src/index.ts b/src/index.ts index 5694bfb..e19ecda 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,12 @@ 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 { contrastRatio } from './colour/contrast-ratio'; +export { checkWcagCompliance } from './colour/wcag-compliance'; + export { toScientificNotation } from './numbers/scientific-notation'; + export { convertUnit } from './units/convert-unit'; From ce42234a931b62ed4c365d2700adc7308dd1b228 Mon Sep 17 00:00:00 2001 From: dankcgi Date: Fri, 14 Nov 2025 13:20:40 +0000 Subject: [PATCH 2/3] icon for github --- public/index.html | 17 ++++++++- public/static/images/github-mark-white.png | Bin 0 -> 4837 bytes public/static/style.css | 42 ++++++++++++++++----- 3 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 public/static/images/github-mark-white.png diff --git a/public/index.html b/public/index.html index ce11442..10c914f 100644 --- a/public/index.html +++ b/public/index.html @@ -114,7 +114,22 @@

Web Tools

diff --git a/public/static/images/github-mark-white.png b/public/static/images/github-mark-white.png new file mode 100644 index 0000000000000000000000000000000000000000..50b81752278d084ba9d449fff25f4051df162b0f GIT binary patch literal 4837 zcmVt<80drDELIAGL9O(c600d`2O+f$vv5yPT|5N-v!bF3pQmi>^l zGt!*V`+FY6AAw};-FMG?3m_sQqSIEOaL(NYi~t{q?tg ze#=Tb9R@QZA4CaWfu;(|M+e&~G$H-!uacED9tJZY?F&9fQw?aTqFOgI97$Gnto(Rhhs2%(lAOB z^)(pAp(->Xy<&5>9|rRX9YtNEsg4CG1Q{@T@2}53q~Ae%F_?SkXzE{JQ#B?DrSwNx zMfYGZJG8m_7Oaj_E71hB1l?mW!9XUYLKDy}7H-kO^nqNX38Vw1q{6}jy2xN^h5P^p zGIbRe8qh@rlTB8$Du2CPQXg~?!PKR4QXvbFWm_y{6gTT&>OABte{DcH+4$>y&hwzz z2GfU9)~>z-`;ob-ka7PryI``}x;R^8*t~s&jQCJWv-KMo$|YI*>zjY>Un3(~R7_S$ zQYD(v+X}{+ub4iRvZj?)l0@OJ8(lbJn%Q8=h^xP3aAylHG^Yp7UmxVPp`-F9nQY4H z?vGF4h$|ge`Rkd*rmeY(sRKMWU?}M{2crW+rYfd3U9%c}qsd(R%J~LHmz%&Vl9OB?Q-4t#5KU*}`F zguVvRe6~KEFOh&Gg2_-)LXrsQ?1Mkrd|iVm4QnkFvzj%SI?%&DC8cIP_h{{GO<9h< zk^!>~2+a~qhLQ}KC7hE7Q%@Y&g2;}w59dcrXwqQn2Ip@evPI6Xm4)xOn8;*bcz$;r>dB|vlivRp?NJw7d@Cd0-N;SH=+TaPcg?C zwJEC`oo_&tpJy>|3m7e!JQ9R5C;iN)v5qK-8B7Uffq8w`t91dMh+x(Coy%eVH~rEF z^BE$D63j$a_U!$o=?L)?z5dXT4wMoJp3E73)sMIPDpMj|r8oYu1wU;gcrdjIdx!bG z?0fG-UHGu}*PmcW=OSVJ>@QhibK7@HB9WF^@cw4dU?w(S`FPBHlZI4wyhupd?2WHP z6UNUYpD%f?-eF!90?%)T4rVGxgM9J7q_d`I^i4+o8`3OyppfJR+=j8l8T5Jj7xN2x z(tEIACN?$FyBXVu-qwu)J)Z>fJ(?GBu3@%#2us?&A`Krx-TE&`Fm)8xAq}_D=9U=HF}7&>UoisNDv<_rCg{0BKPo`XccD*bg8b9GEhtCYM3Q+XaP&n*rif+<_M&KhV5 zOz!6N857Yrrj5V;LO2zg`8%mF|KMR#y~59nCcYo5Li&R3Uc%`mU;m~bpCH_eS{~1v zkbV3<{Ld=00jb;#?(BsJX9ZISMN;Zpilhh*|YP z{m=8HZh~;5KjZ8_pMMO`>-20e(x|3vo$k(&Xp4#|ZFPEskV2aDmt>W2Z|}oouf_ zOEr1Fwg+iRjG7@B987&@S|d&WfEHOM4H}{C6-=#`1=7dG(;LsbHqGBfPIaK#Nj08_%tEVUBhY4+c{^s1EiN>}M`c0eg-P0v)TEmIi%x zS!{yScvfGl2VbYhf?2>WHfI;2ez<#^MF-zd_6E~%Ggee+PW`3@&<)ZrVbjH-=Io)0 zX|-ukp}BuV1zHR}!`AAX@!sa_-ov`2R$GhMBrDE#P zvx7ZX4CUgzfV~6R_BLntHDxW1XjXF58qlH{?r#>m-`E#SizAvmOP22GO^n{dmR~aW zQy;TV=kB~iT(MeGm%fhWRDK6L9(Rx6+^v`eY^nTp4WbTxfd{+o`b3KE7uJJ$mGD8o zG$S1dEMZ5{{bDzmmim{~)c0T{b1cnm{*=8R!8EwEiK~0)C>;nYVZ)Q|=8JB{v=mBK zOX|zg8~Be5c7s{K4pvL*MXP278}fO!hl;4jrSGlyKlXkYRc-I6wz2E()ZKg zkA)H05=7^*(BirunSG>3iCFMAh|W{Nh6|~fR^~4&5S>9s^ed$Ai3HQZh6+UItB}46 zOTpy)C57-0(&yNerKPd(25+j5$%;uKSa==%SAzK)4B%2c3dF+e$ep@zEm3aFG-Vx# zC?yxHm_!M(H26cb6sAUHi9&ElpPi;`_smVA+*#^lGMKa&9Q>iBG4Td(DVPpK=VLGf zV^fwwFtO5&!K9@zQ!%ZqL3JQHpF{e-TMDL$CI}_ZLdE=UsVVyyL}xH`zLlw_td+BG zDP3j`1u)geX-Nv$a6c+r!46Be zqo;)U@reR<*lWsi0EkAi)Y`farnOt!u{ld)SZZyVTKUs@4x-@-7_nNdZXX%C(MpT` zOd3S{m!=Ljf7JcL2=+5+C`+xZ`>tghOl$X^T!W~;KVipx7TaK28vwHOi>4WAGuFY5 zO8)Vv`-LHerJVvatG{5&Pfghp_HcBT`Y2$_Lojt@*4nhmD-HtDG5+CStH!iXVfpmMf-k`UDW|vQ{lc*?zKWKhgf$ zzpzKz_YTuvoKdkgKtyi6E-#mB&%9alH+`#rh;IcmUa`&5uZYuN<_Py4jbIMRA zp%mr5ZypNfXXIhSaONkYP>Q`paCPWUXVRQ)v00l5?NiDaf`ff~o3Y~9{V{WB&bFjk z`;DuEZ1c~bY>v;RQi}4>zc?1mT$-~jd8fT$IBn7{iB!s*ros*uzZH%!zLMgYjc-C+ zfs&_hq_W(yKwb_uW5uakz30@N?UF$uR?o!g!hvtdFO=eFVK`MWt*@Q!gVi%JdgP=u zT?^z(_7GQx{^ik%nZerGKBRiy@g#)#Nejkb(rlFho&x#$ax9eMR8v+gp_({~Hkjhi>)?eOnioc z^i5*puUD8)J18dm=;RP3i-(v+qtB5n=xBq;&FhV=f33Xi^9P3nGse`(=&1^=p0aB_ zg_R%`nm+PZ{dl{i<21D*7I+vFU=a7a>^o-BJD9>h0b7JW{rsG8I;6XHQUcl@2`YnI z6$}Sf-xP$rRXz{`Gfw4V=U8q?XPe3h|y1dOww1aU_*uGG(QuS(?3pm6L}9h$9Cwn+n|am zB38}T7ESf62K=3NpPp3Cl;7DUj884jjr!lO?CjvQ(KwewpYuT#Q|SL7=4zldMr_a0 zk&R{%3gs!|G_VsOP2+CPfj?{H`;=g{zPkmftP`J+vAVMPh*>*LrK(x{3lG%&JP&LOVB3lS20 zXCE|Fo-$U=-p*PRJE~#|t(sF*fue4Xzwb@o*;6_iC7T^OteU-@^_-8cm@OZgsrJr2 z8?r`q!is*%sHKM~W7RzA?D2#U!E}f_ebTDXa{+KGkr$9GB-kP|bzaAthBkP5WY_4X zY-@t)la|B4Mf6%>=N@z^k*8eGgF07`DY3IFrkJ?dIH*Z0BJ7OmE4yZFOIK;}=1o5f zwh8*|iYc^tIn}7+;DG7A&p8HQ{zkq^(5_(f)IowNw2Do!rn0CwU<5xj~w;tqGg7@}jt0joXb z1g-4S?~6TnQRW;?hv?fj8{@NmXYwK95CNCW++9}irK2;A4|ciIfI2(%t5n7@HDnyvCJY=eh+3rG-CP1to?41ra5ykLg z%K6I4f+=(*Ow7dxpK9K|ox*!L^(wAOgDG^=aIBG9nRmQlI4Pj3IX1da9!wE=r-wsx zs{0y5=NWvf$Sl-xZiw6Uj@2`sx>?GYs|}W{Zq}K`bXT)_Mp5S*%q?a%OH;PXHx*=> zBjy$?=dTa72DD}crQ<&8&ZAjPvht^odfH95vYblp23^J&0&l}_YCF&fb$%;y->Z#FC6`@U~7xqi5Tt6Z-0QFftpZ{(Wgv6Wq!1v8mYivJ)XG6LqG zZ25G`a5}wyS<9=Bh4Po&=n^jwZ0WG~6gLT?^p!B$blqh>n4)u&AXd+1YOAD~QP)$l2xg1bbCF79QYE{x3Z`K7 zT#W3hWLI{m)!r7ixTo9qw$xyRmrYwgW1wW388OLOY_{oprIP$Uw?gKAZe7kIlcX+9%h4usGC;C5OTvOIi~aibkP3+1_x?|B?wK3 literal 0 HcmV?d00001 diff --git a/public/static/style.css b/public/static/style.css index 2ba199c..36123b1 100644 --- a/public/static/style.css +++ b/public/static/style.css @@ -15,9 +15,12 @@ html { body { margin: 0 auto; - max-width: clamp(350px, 80vw, 600px); + max-width: clamp(350px, 80vw, 400px); + display: grid; + grid-template-rows: auto 1fr auto; + justify-items: center; - @media (max-width: 685px) { + @media (max-width: 600px) { max-width: 90vw; } } @@ -101,18 +104,14 @@ header#main-header { main { overflow-y: scroll; height: 100%; - position: relative; - padding-bottom: var(--size-8) + /* position: relative; */ + /* padding-bottom: var(--size-8); */ + margin: 0 auto; } 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; @@ -120,6 +119,24 @@ footer#main-footer { z-index: 1; margin: var(--size-2) 0; } + + menu { + list-style: none; + padding: 0; + margin: 0; + display: flex; + justify-content: center; + gap: var(--size-4); + } + + a { + display: grid; + grid-template-columns: auto 1fr; + align-items: center; + gap: var(--size-1); + } + + } h2, @@ -255,4 +272,11 @@ fieldset { grid-template-rows: repeat(2, 1fr); gap: var(--size-2); } +} + +@media (min-width: 600px) { + #precision, + label[for="include-decimals"] { + width: 50%; + } } \ No newline at end of file From 57bcf478d36ffd8d3326de5fc7fc03574b92bc0e Mon Sep 17 00:00:00 2001 From: dankcgi Date: Fri, 14 Nov 2025 13:31:41 +0000 Subject: [PATCH 3/3] alt text for images --- public/index.html | 4 ++-- public/static/style.css | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/public/index.html b/public/index.html index 10c914f..09e6b52 100644 --- a/public/index.html +++ b/public/index.html @@ -118,14 +118,14 @@

Web Tools

  • - + GitHub Octocat logo dankcgi
  • - + GitHub Octocat logo Macavitymadcap
  • diff --git a/public/static/style.css b/public/static/style.css index 36123b1..00ef482 100644 --- a/public/static/style.css +++ b/public/static/style.css @@ -15,7 +15,7 @@ html { body { margin: 0 auto; - max-width: clamp(350px, 80vw, 400px); + max-width: clamp(350px, 80vw, 500px); display: grid; grid-template-rows: auto 1fr auto; justify-items: center; @@ -104,8 +104,6 @@ header#main-header { main { overflow-y: scroll; height: 100%; - /* position: relative; */ - /* padding-bottom: var(--size-8); */ margin: 0 auto; } @@ -135,8 +133,6 @@ footer#main-footer { align-items: center; gap: var(--size-1); } - - } h2, @@ -259,6 +255,11 @@ fieldset { grid-template-rows: auto 1fr; grid-template-columns: repeat(2, 1fr); + @media (max-width: 413px) { + grid-template-columns: 1fr; + grid-template-rows: auto auto; + } + .form-input { grid-template-columns: 1fr auto; grid-template-rows: 1fr;