diff --git a/examples/mortgage-repayment-calculator/.gitignore b/examples/mortgage-repayment-calculator/.gitignore
new file mode 100644
index 00000000..a547bf36
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/examples/mortgage-repayment-calculator/.npmrc b/examples/mortgage-repayment-calculator/.npmrc
new file mode 100644
index 00000000..6b5f38e8
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/.npmrc
@@ -0,0 +1,2 @@
+save-exact = true
+package-lock = false
diff --git a/examples/mortgage-repayment-calculator/README.md b/examples/mortgage-repayment-calculator/README.md
new file mode 100644
index 00000000..157ed120
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/README.md
@@ -0,0 +1,23 @@
+# Frontend Mentor Mortgage Repayment Calculator
+
+Here is the implementation in [Bau.js](https://github.com/grucloud/bau) of the [Frontend Mentor Mortgage Repayment Calculator code challenge](https://www.frontendmentor.io/challenges/mortgage-repayment-calculator-Galx1LXK73)
+
+## Workflow
+
+Install the dependencies:
+
+```sh
+npm install
+```
+
+Start a development server:
+
+```sh
+npm run dev
+```
+
+Build a production version:
+
+```sh
+npm run build
+```
diff --git a/examples/mortgage-repayment-calculator/index.html b/examples/mortgage-repayment-calculator/index.html
new file mode 100644
index 00000000..a5c174a1
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ Mortgage Repayment Calculator | FrontendMentor
+
+
+
+
+
+
diff --git a/examples/mortgage-repayment-calculator/package.json b/examples/mortgage-repayment-calculator/package.json
new file mode 100644
index 00000000..c17282b6
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "frontendmentor-mortgage-repayment-calculator",
+ "private": true,
+ "version": "0.85.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "typescript": "^5.0.2",
+ "vite": "^5.2.11"
+ },
+ "dependencies": {
+ "@grucloud/bau": "^0.85.0",
+ "@grucloud/bau-css": "^0.85.0",
+ "@grucloud/bau-ui": "^0.85.0",
+ "bignumber.js": "9.1.2"
+ }
+}
diff --git a/examples/mortgage-repayment-calculator/public/assets/images/favicon-32x32.png b/examples/mortgage-repayment-calculator/public/assets/images/favicon-32x32.png
new file mode 100644
index 00000000..1e2df7f0
Binary files /dev/null and b/examples/mortgage-repayment-calculator/public/assets/images/favicon-32x32.png differ
diff --git a/examples/mortgage-repayment-calculator/public/assets/images/icon-calculator.svg b/examples/mortgage-repayment-calculator/public/assets/images/icon-calculator.svg
new file mode 100644
index 00000000..4510a5ce
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/public/assets/images/icon-calculator.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/mortgage-repayment-calculator/public/assets/images/illustration-empty.svg b/examples/mortgage-repayment-calculator/public/assets/images/illustration-empty.svg
new file mode 100644
index 00000000..8f164f26
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/public/assets/images/illustration-empty.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/mortgage-repayment-calculator/src/main.ts b/examples/mortgage-repayment-calculator/src/main.ts
new file mode 100644
index 00000000..94bc7da7
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/src/main.ts
@@ -0,0 +1,20 @@
+import { createContext, type Context } from "@grucloud/bau-ui/context";
+import mortgageRepaymentCalculator from "./mortgageRepaymentCalculator";
+
+import "./style.css";
+
+const context = createContext();
+
+const app = (context: Context) => {
+ const { bau } = context;
+ const { main } = bau.tags;
+
+ const MortgageRepaymentCalculator = mortgageRepaymentCalculator(context);
+
+ return function () {
+ return main(MortgageRepaymentCalculator());
+ };
+};
+
+const App = app(context);
+document.getElementById("app")?.replaceChildren(App());
diff --git a/examples/mortgage-repayment-calculator/src/mortgageRepaymentCalculator.ts b/examples/mortgage-repayment-calculator/src/mortgageRepaymentCalculator.ts
new file mode 100644
index 00000000..14a5004e
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/src/mortgageRepaymentCalculator.ts
@@ -0,0 +1,295 @@
+import { type Context } from "@grucloud/bau-ui/context";
+
+import BN from "bignumber.js";
+
+const locale = "en-GB";
+const currency = "GBP";
+
+const formatCurrency = (number: number) =>
+ new Intl.NumberFormat(locale, { style: "currency", currency }).format(number);
+
+export default function (context: Context) {
+ const { bau, css } = context;
+ const {
+ h1,
+ form,
+ p,
+ article,
+ section,
+ header,
+ span,
+ label,
+ input,
+ div,
+ button,
+ hr,
+ img,
+ } = bau.tags;
+
+ const monthlyRepaymentState = bau.state("");
+ const totalState = bau.state("");
+ const className = css`
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 400px));
+ border-radius: 1rem;
+ overflow: hidden;
+ margin-inline: 0.5rem;
+ @media (max-width: 600px) {
+ grid-template-columns: 1fr;
+ }
+ > section {
+ }
+
+ .calculator-form {
+ padding: 1rem;
+ background-color: var(--white);
+ header {
+ display: flex;
+ justify-content: space-between;
+ button {
+ text-decoration: underline;
+ background: none;
+ color: var(--grey-700);
+ }
+ }
+ form {
+ button[type="submit"] {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ &::before {
+ content: url("./assets/images/icon-calculator.svg");
+ }
+ border-radius: 2rem;
+ }
+ }
+ }
+ .result-container {
+ background-color: var(--white);
+ }
+ .result {
+ padding: 1rem;
+
+ color: var(--grey-100);
+ background-color: var(--grey-900);
+ border-bottom-left-radius: 4rem;
+ display: flex;
+ height: 100%;
+ flex-direction: column;
+ gap: 1rem;
+ &.no-result {
+ align-items: center;
+ > img {
+ width: 192px;
+ height: 192px;
+ }
+ }
+
+ p,
+ span {
+ color: var(--grey-300);
+ }
+ .payments {
+ background-color: var(--grey-1000);
+ display: flex;
+ flex-direction: column;
+ padding: 1rem;
+ border-radius: 0.5rem;
+ border-top: 3px solid var(--primary);
+ hr {
+ border: 1px solid var(--grey-900);
+ }
+ .monthly-payments-value {
+ color: var(--primary);
+ font-size: 2.6rem;
+ font-weight: 700;
+ line-height: 3.5rem;
+ }
+ .total-payments-value {
+ font-size: 2rem;
+ line-height: 2.5rem;
+ color: var(--grey-100);
+ }
+ }
+ }
+ `;
+
+ const clearAll = (event: any) => {
+ event.target.closest("form").reset();
+ monthlyRepaymentState.val = "";
+ totalState.val;
+ };
+
+ const onsubmit = (event: any) => {
+ event.preventDefault();
+ const result = Object.fromEntries(new FormData(event.currentTarget));
+ const { amount, term, rate, mortgateType } = result;
+ if (mortgateType == "repayment") {
+ const month = BN(term.toString()).times(12);
+
+ const ratePerMonth = BN(1)
+ .plus(BN(rate.toString()).dividedBy(100).dividedBy(12))
+ .pow(month);
+
+ const newMonthlyRepayment = BN(amount.toString())
+ .times(BN(rate.toString()).dividedBy(100).dividedBy(12))
+ .times(ratePerMonth)
+ .dividedBy(ratePerMonth.minus(1));
+ monthlyRepaymentState.val = formatCurrency(
+ newMonthlyRepayment.toNumber()
+ );
+ totalState.val = formatCurrency(
+ newMonthlyRepayment.times(12).times(term.toString()).toNumber()
+ );
+ } else if (mortgateType == "interestOnly") {
+ const newMonthlyRepayment = BN(amount.toString()).times(
+ BN(rate.toString()).dividedBy(100).dividedBy(12)
+ );
+ monthlyRepaymentState.val = formatCurrency(
+ newMonthlyRepayment.toNumber()
+ );
+ totalState.val = formatCurrency(
+ newMonthlyRepayment.times(term.toString()).times(12).toNumber()
+ );
+ }
+ };
+
+ return function MortgageRepaymentCalculator() {
+ return article(
+ { class: className },
+ section(
+ { class: "calculator-form" },
+ form(
+ { onsubmit },
+ header(
+ h1("Mortgage Calculator"),
+ button({ type: "button", onclick: clearAll }, "Clear all")
+ ),
+ label(
+ span("Mortgage Amount"),
+ div(
+ {
+ class: "input-unit",
+ },
+ span("£"),
+ input({
+ autofocus: true,
+ name: "amount",
+ type: "number",
+ required: true,
+ })
+ )
+ ),
+ div(
+ {
+ class: css`
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 0.5rem;
+ `,
+ },
+ label(
+ "Mortgage Terms",
+ div(
+ {
+ class: "input-unit",
+ },
+
+ input({
+ name: "term",
+ type: "number",
+ required: true,
+ min: 1,
+ //defaultValue: 25,
+ }),
+ span("years")
+ )
+ ),
+ label(
+ "Interest Rate",
+ div(
+ {
+ class: "input-unit",
+ },
+ input({
+ name: "rate",
+ type: "number",
+ required: true,
+ step: 0.1,
+ min: 0.1,
+ //defaultValue: 5,
+ }),
+ span("%")
+ )
+ )
+ ),
+ label(
+ "Mortgage Type",
+ div(
+ {
+ class: css`
+ > label {
+ display: flex;
+ gap: 0.5rem;
+ }
+ `,
+ },
+ label(
+ input({
+ type: "radio",
+ name: "mortgateType",
+ value: "repayment",
+ required: true,
+ }),
+ "Repayment"
+ ),
+ label(
+ input({
+ type: "radio",
+ name: "mortgateType",
+ value: "interestOnly",
+ }),
+ "Interest Only"
+ )
+ )
+ ),
+ button({ type: "submit" }, "Calculate Repayment")
+ )
+ ),
+ section({ class: "result-container" }, () =>
+ monthlyRepaymentState.val == ""
+ ? section(
+ { class: "result no-result" },
+ img({ src: "./assets/images/illustration-empty.svg", alt: "" }),
+ h1("Results shown here"),
+ p(
+ "Complete the form and click “calculate repayments” to see what your monthly repayments would be."
+ )
+ )
+ : section(
+ { class: "result ok" },
+ h1("Your results"),
+ p(
+ "Your results are shown below based on the information you provided. To adjust the results, edit the form and click “calculate repayments” again."
+ ),
+ div(
+ { class: "payments" },
+ div(
+ { class: "monthly-payments" },
+ p("Your monthly repayments"),
+ p({ class: "monthly-payments-value" }, monthlyRepaymentState)
+ ),
+ hr,
+ div(
+ { class: "total-payments" },
+ p("Total you'll repay over the term"),
+ span({ class: "total-payments-value" }, totalState)
+ )
+ )
+ )
+ )
+ );
+ };
+}
diff --git a/examples/mortgage-repayment-calculator/src/style.css b/examples/mortgage-repayment-calculator/src/style.css
new file mode 100644
index 00000000..66599dd6
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/src/style.css
@@ -0,0 +1,120 @@
+/**@import url("https://fonts.googleapis.com/css2?family=Fraunces:wght@700&family=Montserrat:wght@500;700&display=swap");**/
+
+* {
+ margin: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ /** Primary **/
+ --primary: hsl(61, 70%, 52%);
+ --primary-200: hsl(61, 70%, 92%);
+
+ --danger: hsl(4, 69%, 50%);
+
+ /** Neutral **/
+
+ --white: hsl(0, 0%, 100%);
+ --grey-100: hsl(202, 86%, 94%);
+ --grey-300: hsl(203, 41%, 72%);
+ --grey-500: hsl(200, 26%, 54%);
+ --grey-700: hsl(200, 24%, 40%);
+ --grey-900: hsl(202, 55%, 16%);
+ --grey-1000: hsl(202, 55%, 8%);
+}
+
+button {
+ border: none;
+ cursor: pointer;
+ padding: 0.7rem;
+ font-weight: 600;
+}
+
+button[type="submit"] {
+ font-weight: 600;
+ font-size: 1rem;
+ border-radius: 0.5rem;
+ color: var(--grey-900);
+ background-color: var(--primary);
+}
+
+label {
+ color: var(--grey-700);
+ font-size: 0.8rem;
+ font-weight: 600;
+ margin-bottom: 1rem;
+ line-height: 2rem;
+}
+
+label:has(> input[type="radio"]) {
+ cursor: pointer;
+ border-radius: 0.5rem;
+ padding: 0.5rem;
+ border: 1px solid var(--grey-500);
+ font-weight: 600;
+ font-size: 0.9rem;
+ &:hover {
+ border-color: var(--grey-900);
+ }
+}
+
+label:has(> input[type="radio"]:checked) {
+ background-color: var(--primary-200);
+}
+
+input[type="radio"] {
+ accent-color: var(--primary);
+}
+
+input[type="number"] {
+ width: 100%;
+ padding: 0.4rem;
+ font-size: 1rem;
+ font-weight: 600;
+ color: var(--grey-700);
+ border-radius: 0.5rem;
+ border: 1px solid var(--grey-500);
+}
+
+.input-unit {
+ display: flex;
+ outline: 1px solid var(--grey-500);
+ border-radius: 0.3rem;
+ overflow: hidden;
+ &:focus-within {
+ outline: 2px solid var(--primary);
+ > span {
+ background-color: var(--primary);
+ }
+ }
+ &:has(input[type="number"]:user-invalid) {
+ outline: 2px solid var(--danger);
+ span {
+ color: white;
+ background-color: var(--danger);
+ }
+ }
+
+ > span {
+ padding-inline: 0.8rem;
+ text-align: center;
+ background-color: var(--grey-100);
+ }
+ > input {
+ border: transparent;
+ &:focus {
+ outline: none;
+ }
+ }
+}
+
+body {
+ background-color: var(--grey-100);
+ font-family: "Montserrat", sans-serif;
+ min-height: 100vh;
+ display: grid;
+ place-items: center;
+ @media (max-width: 600px) {
+ place-items: flex-start;
+ }
+}
diff --git a/examples/mortgage-repayment-calculator/src/vite-env.d.ts b/examples/mortgage-repayment-calculator/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/examples/mortgage-repayment-calculator/tsconfig.json b/examples/mortgage-repayment-calculator/tsconfig.json
new file mode 100644
index 00000000..75abdef2
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"]
+}
diff --git a/examples/mortgage-repayment-calculator/vite.config.js b/examples/mortgage-repayment-calculator/vite.config.js
new file mode 100644
index 00000000..41713bec
--- /dev/null
+++ b/examples/mortgage-repayment-calculator/vite.config.js
@@ -0,0 +1,9 @@
+import { defineConfig } from "vite";
+
+export default defineConfig(({ command, mode, ssrBuild }) => {
+ return {
+ server: {
+ open: true,
+ },
+ };
+});
diff --git a/examples/product-list-cart/src/productListCart.ts b/examples/product-list-cart/src/productListCart.ts
index 9c9bca13..d285d699 100644
--- a/examples/product-list-cart/src/productListCart.ts
+++ b/examples/product-list-cart/src/productListCart.ts
@@ -4,7 +4,7 @@ import BN from "bignumber.js";
import data from "./data.json";
const locale = "en-US";
-const currency = "EUR";
+const currency = "USD";
const formatCurrency = (number: number) =>
new Intl.NumberFormat(locale, { style: "currency", currency }).format(number);