Design overhoul
This commit is contained in:
parent
edc0a00ef9
commit
9cff95367c
16 changed files with 454 additions and 515 deletions
72
README.md
72
README.md
|
|
@ -1,47 +1,43 @@
|
||||||
# Svelte + TS + Vite
|
# Kalkylatorer
|
||||||
|
|
||||||
This template should help get you started developing with Svelte and TypeScript in Vite.
|
This is 2 things:
|
||||||
|
|
||||||
## Recommended IDE Setup
|
- A set of formulaes for speedy calculation for
|
||||||
|
those times when a spreadsheet is to overwhelming. Basically related to strength training and body fat.
|
||||||
|
- An personal exercise to learn CSS subgrids, as well
|
||||||
|
as grinding code with Svelte.
|
||||||
|
|
||||||
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
|
## How it works
|
||||||
|
|
||||||
## Need an official Svelte framework?
|
- Choose a formula (top row), and set the values
|
||||||
|
with the keypad.
|
||||||
|
- Separate the values using semicolons.
|
||||||
|
- Add decimals by using a comma (sorry not sorry).
|
||||||
|
- Get result by pressing "=" button.
|
||||||
|
|
||||||
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
|
|
||||||
|
|
||||||
## Technical considerations
|
## Built-in calculators
|
||||||
|
|
||||||
**Why use this over SvelteKit?**
|
### 1 repetition max calculator
|
||||||
|
|
||||||
- It brings its own routing solution which might not be preferable for some users.
|
```
|
||||||
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
1rm(weight: number, reps: number, variant: "lower" | "upper")
|
||||||
|
```
|
||||||
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
|
||||||
|
### KG to LBS converter
|
||||||
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
|
||||||
|
```
|
||||||
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
lbs(weight: number)
|
||||||
|
```
|
||||||
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
|
||||||
|
### Army body fat composition calculator
|
||||||
**Why include `.vscode/extensions.json`?**
|
|
||||||
|
```
|
||||||
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
abf(length: number, neck: number, waist: number, hips?: number, gender: "male" | "female", metric: boolean)
|
||||||
|
```
|
||||||
**Why enable `allowJs` in the TS template?**
|
|
||||||
|
### Navy body fat composition calculator
|
||||||
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
|
|
||||||
|
```
|
||||||
**Why is HMR not preserving my local component state?**
|
nbf(length: number, neck: number, waist: number, hips?: number, gender: "male" | "female", metric: boolean)
|
||||||
|
|
||||||
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
|
||||||
|
|
||||||
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// store.ts
|
|
||||||
// An extremely simple external store
|
|
||||||
import { writable } from 'svelte/store'
|
|
||||||
export default writable(0)
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
:root{font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0}button{border:5px solid crimson;background-color:transparent;border-radius:0;color:inherit}button:hover,button:focus{border-color:#fff;background-color:#ffffff1a}header{background-color:#dc143c}h1,h2{font-size:1em;margin:0;text-wrap:balance}main{min-width:100vw;min-height:100vh;display:grid;gap:1em;padding:1em;box-sizing:border-box;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(7,1fr)}output{background:#00000040;display:grid;grid-column:1 / 4;grid-row:2 / 4;grid-template:subgrid / subgrid;font-size:2em}output>span{grid-column:1 / 6;grid-row:1 / 2;text-align:right}output>span:before{content:"=";color:#888}output>span:after{content:"%";color:#888}form{background:#ffffff40;display:grid;grid-column:1 / 4;grid-row:4 / 8;grid-template:subgrid / subgrid}input{max-width:4em;display:block}header{display:grid;grid-column:1 / 4;grid-row:1 / 1;grid-template:subgrid / subgrid}h1{grid-column:2 / 4}.gender.svelte-5wgr0y{grid-column:1 / 2;grid-row:1 / 4}nav.svelte-1mcqfv8{display:grid;grid-template:subgrid / subgrid;grid-column:2 / 2;grid-row:2 / 6}
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -2,11 +2,10 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<title>Kalkylatorer</title>
|
||||||
<title>Vite + Svelte + TS</title>
|
<script type="module" crossorigin src="./assets/index-hkCEeA96.js"></script>
|
||||||
<script type="module" crossorigin src="./assets/index-BnxD8QrO.js"></script>
|
<link rel="stylesheet" crossorigin href="./assets/index-CIaqKvO4.css">
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-BWHSjgtQ.css">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<title>Kalkylatorer</title>
|
||||||
<title>Vite + Svelte + TS</title>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,29 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import svelteLogo from "./assets/svelte.svg";
|
import Display from "./lib/Display.svelte";
|
||||||
import viteLogo from "/vite.svg";
|
import Keypad from "./lib/Keypad.svelte";
|
||||||
import { navigate } from "./lib/common";
|
|
||||||
import { currentView } from "./lib/store";
|
|
||||||
import ArmyFatPercentage from "./lib/ArmyFatPercentage.svelte";
|
|
||||||
import NavyFatPercentage from "./lib/NavyFatPercentage.svelte";
|
|
||||||
import OneRepMax from "./lib/OneRepMax.svelte";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $currentView === "armyfatcalc"}
|
<main>
|
||||||
<ArmyFatPercentage />
|
<Display />
|
||||||
{/if}
|
<Keypad />
|
||||||
{#if $currentView === "navyfatcalc"}
|
</main>
|
||||||
<NavyFatPercentage />
|
<footer>
|
||||||
{/if}
|
<p>
|
||||||
{#if $currentView === "onerepmax"}
|
Made by <a href="https://madr.se">Anders</a>. Source on
|
||||||
<OneRepMax />
|
<a href="https://github.com/madr/kalkylatorer">Github</a>.
|
||||||
{/if}
|
</p>
|
||||||
{#if $currentView === "start"}
|
</footer>
|
||||||
<main>
|
|
||||||
<nav>
|
|
||||||
<button onclick={() => navigate("armyfatcalc")}
|
|
||||||
>Kroppsfettkalkylator, Army</button
|
|
||||||
>
|
|
||||||
<button onclick={() => navigate("navyfatcalc")}
|
|
||||||
>Kroppsfettkalkylator, Navy</button
|
|
||||||
>
|
|
||||||
<button onclick={() => navigate("onerepmax")}>1RM-kalkylator</button
|
|
||||||
>
|
|
||||||
</nav>
|
|
||||||
</main>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
nav {
|
footer {
|
||||||
display: grid;
|
font-size: x-small;
|
||||||
grid-template: subgrid / subgrid;
|
position: absolute;
|
||||||
grid-column: 2 / 2;
|
bottom: 0.25rem;
|
||||||
grid-row: 2 / 6;
|
right: 0.25rem;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
> p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
105
src/app.css
105
src/app.css
|
|
@ -5,7 +5,13 @@
|
||||||
|
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
color: rgba(255, 255, 255, 0.87);
|
color: rgba(255, 255, 255, 0.87);
|
||||||
background-color: #242424;
|
background-color: #333;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
hsl(0 25% 10%),
|
||||||
|
hsl(90 25% 10%),
|
||||||
|
hsl(180 25% 10%),
|
||||||
|
hsl(270 25% 10%)
|
||||||
|
);
|
||||||
|
|
||||||
font-synthesis: none;
|
font-synthesis: none;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
|
|
@ -14,90 +20,27 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
min-width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
button {
|
align-items: center;
|
||||||
border: 5px solid crimson;
|
|
||||||
background-color: transparent;
|
|
||||||
border-radius: 0;
|
|
||||||
color: inherit;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
border-color: #fff;
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
background-color: crimson;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h2 {
|
|
||||||
font-size: 1em;
|
|
||||||
margin: 0;
|
|
||||||
text-wrap: balance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
min-width: 100vw;
|
width: 20rem;
|
||||||
min-height: 100vh;
|
height: 22rem;
|
||||||
|
max-width: 97%;
|
||||||
|
max-height: 97%;
|
||||||
|
background: #543;
|
||||||
|
padding: 0.5em;
|
||||||
|
border: 3px solid #000;
|
||||||
|
background-image: linear-gradient(135deg, #432, #654, #432);
|
||||||
|
border-radius: 5px;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1em;
|
gap: 0.2em;
|
||||||
padding: 1em;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
grid-template-rows: repeat(7, 1fr);
|
grid-template-rows: repeat(8, 1fr);
|
||||||
}
|
|
||||||
|
|
||||||
output {
|
|
||||||
background: rgba(0, 0, 0, 0.25);
|
|
||||||
display: grid;
|
|
||||||
grid-column: 1 / 4;
|
|
||||||
grid-row: 2 / 4;
|
|
||||||
grid-template: subgrid / subgrid;
|
|
||||||
font-size: 2em;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
grid-column: 1 / 6;
|
|
||||||
grid-row: 1 / 2;
|
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "=";
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: "%";
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
background: rgba(255, 255, 255, 0.25);
|
|
||||||
display: grid;
|
|
||||||
grid-column: 1 / 4;
|
|
||||||
grid-row: 4 / 8;
|
|
||||||
grid-template: subgrid / subgrid;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
max-width: 4em;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
display: grid;
|
|
||||||
grid-column: 1 / 4;
|
|
||||||
grid-row: 1 / 1;
|
|
||||||
grid-template: subgrid / subgrid;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
grid-column: 2 / 4;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { navigate } from "./common";
|
|
||||||
|
|
||||||
let fatPercentage = $state(0);
|
|
||||||
let gender = $state("male");
|
|
||||||
let height = $state(null);
|
|
||||||
let waist = $state(null);
|
|
||||||
let neck = $state(null);
|
|
||||||
let hips = $state(null);
|
|
||||||
|
|
||||||
// https://www.gigacalculator.com/calculators/army-body-fat-calculator.php
|
|
||||||
|
|
||||||
const male = (waist: number, neck: number, height: number) =>
|
|
||||||
86.01 * Math.log10(waist - neck) - 70.041 * Math.log10(height) + 30.3;
|
|
||||||
|
|
||||||
const female = (
|
|
||||||
waist: number,
|
|
||||||
neck: number,
|
|
||||||
hips: number,
|
|
||||||
height: number,
|
|
||||||
) =>
|
|
||||||
163.205 * Math.log10(waist + hips - neck) -
|
|
||||||
97.684 * Math.log10(height) -
|
|
||||||
104.912;
|
|
||||||
|
|
||||||
const calculate = () => {
|
|
||||||
if (gender == "male") {
|
|
||||||
if (!waist || !neck || !height) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fatPercentage = male(waist, neck, height);
|
|
||||||
} else {
|
|
||||||
if (!waist || !neck || !height || !hips) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fatPercentage = female(waist, neck, hips, height);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<header>
|
|
||||||
<span>
|
|
||||||
<button onclick={() => navigate("start")}>Tillbaka</button>
|
|
||||||
</span>
|
|
||||||
<h1>Kroppsfettkalkylator, Army</h1>
|
|
||||||
</header>
|
|
||||||
<output>
|
|
||||||
{#if fatPercentage > 0}
|
|
||||||
<span>{Math.round(fatPercentage * 100) / 100}</span>
|
|
||||||
{/if}
|
|
||||||
</output>
|
|
||||||
<form>
|
|
||||||
<div class="gender">
|
|
||||||
<label>
|
|
||||||
Man
|
|
||||||
<input
|
|
||||||
bind:group={gender}
|
|
||||||
type="radio"
|
|
||||||
name="gender"
|
|
||||||
value="male"
|
|
||||||
id="male"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Kvinna
|
|
||||||
<input
|
|
||||||
bind:group={gender}
|
|
||||||
type="radio"
|
|
||||||
name="gender"
|
|
||||||
value="female"
|
|
||||||
id="female"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<label>
|
|
||||||
Kroppslängd
|
|
||||||
<input
|
|
||||||
onchange={() => calculate()}
|
|
||||||
bind:value={height}
|
|
||||||
type="number"
|
|
||||||
id="height"
|
|
||||||
name="height"
|
|
||||||
size="5"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Midja
|
|
||||||
<input
|
|
||||||
onchange={() => calculate()}
|
|
||||||
type="number"
|
|
||||||
bind:value={waist}
|
|
||||||
id="waist"
|
|
||||||
name="waist"
|
|
||||||
size="5"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
{#if gender == "female"}
|
|
||||||
<label>
|
|
||||||
Höft
|
|
||||||
<input
|
|
||||||
onchange={() => calculate()}
|
|
||||||
type="number"
|
|
||||||
bind:value={hips}
|
|
||||||
id="hips"
|
|
||||||
name="hips"
|
|
||||||
size="5"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
{/if}
|
|
||||||
<label>
|
|
||||||
Hals
|
|
||||||
<input
|
|
||||||
onchange={() => calculate()}
|
|
||||||
type="number"
|
|
||||||
bind:value={neck}
|
|
||||||
id="neck"
|
|
||||||
name="neck"
|
|
||||||
size="5"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</form>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.gender {
|
|
||||||
grid-column: 1 / 2;
|
|
||||||
grid-row: 1 / 4;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
let count: number = $state(0)
|
|
||||||
const increment = () => {
|
|
||||||
count += 1
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button onclick={increment}>
|
|
||||||
count is {count}
|
|
||||||
</button>
|
|
||||||
119
src/lib/Display.svelte
Normal file
119
src/lib/Display.svelte
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { display, formula, calculated } from "./store";
|
||||||
|
|
||||||
|
const paramCount = $derived($display.split(";").length);
|
||||||
|
|
||||||
|
const copyToClipboard = () => {
|
||||||
|
const text = $calculated;
|
||||||
|
navigator.clipboard.writeText(text).then(
|
||||||
|
() => {
|
||||||
|
console.info("clipboard successfully set");
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
console.error("clipboard write failed");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="output">
|
||||||
|
<div class="modifiers">
|
||||||
|
{#if $formula}
|
||||||
|
<b>{$formula}</b>
|
||||||
|
{#if $formula === "1rm"}
|
||||||
|
<i class={paramCount > 0 ? "done" : ""}>weight</i>
|
||||||
|
<i class={paramCount > 1 ? "done" : ""}>reps</i>
|
||||||
|
{/if}
|
||||||
|
{#if $formula === "lbs"}
|
||||||
|
<i class={paramCount > 0 ? "done" : ""}>weight</i>
|
||||||
|
{/if}
|
||||||
|
{#if $formula === "akf" || $formula === "nkf"}
|
||||||
|
<i class={paramCount > 0 ? "done" : ""}>hgt</i>
|
||||||
|
<i class={paramCount > 1 ? "done" : ""}>nck</i>
|
||||||
|
<i class={paramCount > 2 ? "done" : ""}>wst</i>
|
||||||
|
<i class={paramCount > 3 ? "done" : ""}>hps?</i>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<pre class={formula}>{$display}</pre>
|
||||||
|
<output onclick={() => copyToClipboard()}>
|
||||||
|
{#if $calculated}=
|
||||||
|
{/if}
|
||||||
|
{$calculated}
|
||||||
|
</output>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.modifiers {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid rgba(128, 128, 128, 0.75);
|
||||||
|
gap: 1em;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.66em;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
border-top: 1px solid rgba(128, 128, 128, 0.75);
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 0.33rem;
|
||||||
|
place-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
font-weight: normal;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: #888;
|
||||||
|
|
||||||
|
&.done {
|
||||||
|
color: #000;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.output {
|
||||||
|
background: hsl(120 16% 66%);
|
||||||
|
display: grid;
|
||||||
|
color: #444;
|
||||||
|
grid-column: 1 / 5;
|
||||||
|
grid-row: 1 / 4;
|
||||||
|
grid-template: subgrid / subgrid;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
grid-column: 1 / 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
> pre {
|
||||||
|
--mod-txt: "akf(";
|
||||||
|
|
||||||
|
font-size: 125%;
|
||||||
|
place-content: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 0.33rem;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
color: #b74;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
color: #b74;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.akf {
|
||||||
|
&::before {
|
||||||
|
content: var(--mod-txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
132
src/lib/Keypad.svelte
Normal file
132
src/lib/Keypad.svelte
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { display, formula, calculated } from "./store";
|
||||||
|
import { calculate } from "./formulaes";
|
||||||
|
|
||||||
|
const setFormula = (value: string) => {
|
||||||
|
if ($display == "hi.") {
|
||||||
|
display.set("");
|
||||||
|
}
|
||||||
|
if ($formula == value) {
|
||||||
|
formula.set("");
|
||||||
|
display.set("");
|
||||||
|
} else {
|
||||||
|
formula.set(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const append = (value: string) => {
|
||||||
|
if ($display == "hi.") {
|
||||||
|
display.set("");
|
||||||
|
}
|
||||||
|
display.set($display + value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
display.set("");
|
||||||
|
calculated.set("");
|
||||||
|
formula.set("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const popright = () => {
|
||||||
|
display.set($display.substring(0, $display.length - 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
const eq = () => {
|
||||||
|
try {
|
||||||
|
const params = $display.split(";");
|
||||||
|
calculated.set(calculate($formula, params));
|
||||||
|
} catch {
|
||||||
|
calculated.set("!Error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<button on:click={() => setFormula("akf")}>akf</button>
|
||||||
|
<button on:click={() => setFormula("nkf")}>nkf</button>
|
||||||
|
<button on:click={() => setFormula("1rm")}>1rm</button>
|
||||||
|
<button on:click={() => setFormula("lbs")}>lbs</button>
|
||||||
|
<button on:click={() => clear()} data-clear>ac</button>
|
||||||
|
<button on:click={() => popright()} data-erase>del</button>
|
||||||
|
<button on:click={() => append("7")}>7</button>
|
||||||
|
<button on:click={() => append("8")}>8</button>
|
||||||
|
<button on:click={() => append("9")}>9</button>
|
||||||
|
<button on:click={() => append("4")}>4</button>
|
||||||
|
<button on:click={() => append("5")}>5</button>
|
||||||
|
<button on:click={() => append("6")}>6</button>
|
||||||
|
<button on:click={() => append("1")}>1</button>
|
||||||
|
<button on:click={() => append("2")}>2</button>
|
||||||
|
<button on:click={() => append("3")}>3</button>
|
||||||
|
<button on:click={() => append(",")}>,</button>
|
||||||
|
<button on:click={() => append("0")}>0</button>
|
||||||
|
<button on:click={() => append(";")} data-separator>;</button>
|
||||||
|
<button on:click={() => eq()} data-equals>=</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
nav {
|
||||||
|
display: grid;
|
||||||
|
grid-template: subgrid / subgrid;
|
||||||
|
grid-column: 1 / 5;
|
||||||
|
grid-row: 4 / 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
--btn-bg: #222;
|
||||||
|
--btn-lg-s: #333;
|
||||||
|
--btn-lg-e: #111;
|
||||||
|
border: 2px solid #000;
|
||||||
|
background-color: var(--btn-bg);
|
||||||
|
background-image: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
var(--btn-lg-s),
|
||||||
|
var(--btn-lg-s) 33%,
|
||||||
|
var(--btn-lg-e) 66%,
|
||||||
|
var(--btn-lg-e)
|
||||||
|
);
|
||||||
|
color: inherit;
|
||||||
|
|
||||||
|
&[data-lbs] {
|
||||||
|
grid-column: 4 / 5;
|
||||||
|
grid-row: 1 / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-erase] {
|
||||||
|
--btn-bg: #422;
|
||||||
|
--btn-lg-s: #533;
|
||||||
|
--btn-lg-e: #311;
|
||||||
|
grid-column: 4 / 5;
|
||||||
|
grid-row: 3 / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-clear] {
|
||||||
|
grid-column: 4 / 5;
|
||||||
|
grid-row: 2 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-separator] {
|
||||||
|
grid-column: 4 / 5;
|
||||||
|
grid-row: 4 / 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-equals] {
|
||||||
|
--btn-bg: #242;
|
||||||
|
--btn-lg-s: #353;
|
||||||
|
--btn-lg-e: #131;
|
||||||
|
grid-column: 3 / 5;
|
||||||
|
grid-row: 5 / 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: #333;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
#555,
|
||||||
|
#555 33%,
|
||||||
|
#333 66%,
|
||||||
|
#333
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { navigate } from "./common";
|
|
||||||
|
|
||||||
let fatPercentage = $state(0);
|
|
||||||
let gender = $state("male");
|
|
||||||
let height = $state(null);
|
|
||||||
let waist = $state(null);
|
|
||||||
let neck = $state(null);
|
|
||||||
let hips = $state(null);
|
|
||||||
|
|
||||||
// https://www.omnicalculator.com/health/navy-body-fat
|
|
||||||
|
|
||||||
const male = (waist: number, neck: number, height: number) =>
|
|
||||||
495 /
|
|
||||||
(1.0324 -
|
|
||||||
0.19077 * Math.log10(waist - neck) +
|
|
||||||
0.15456 * Math.log10(height)) -
|
|
||||||
450;
|
|
||||||
|
|
||||||
const female = (
|
|
||||||
waist: number,
|
|
||||||
neck: number,
|
|
||||||
hips: number,
|
|
||||||
height: number,
|
|
||||||
) =>
|
|
||||||
495 /
|
|
||||||
(1.29579 -
|
|
||||||
0.35004 * Math.log10(waist + hips - neck) +
|
|
||||||
0.221 * Math.log10(height)) -
|
|
||||||
450;
|
|
||||||
|
|
||||||
const calculate = () => {
|
|
||||||
if (gender == "male") {
|
|
||||||
if (!waist || !neck || !height) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fatPercentage = male(waist, neck, height);
|
|
||||||
} else {
|
|
||||||
if (!waist || !neck || !height || !hips) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fatPercentage = female(waist, neck, hips, height);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<header>
|
|
||||||
<span>
|
|
||||||
<button onclick={() => navigate("start")}>Tillbaka</button>
|
|
||||||
</span>
|
|
||||||
<h1>Kroppsfettkalkylator, Navy</h1>
|
|
||||||
</header>
|
|
||||||
<output>
|
|
||||||
{#if fatPercentage > 0}
|
|
||||||
<span>{Math.round(fatPercentage * 100) / 100}</span>
|
|
||||||
{/if}
|
|
||||||
</output>
|
|
||||||
<form>
|
|
||||||
<div class="gender">
|
|
||||||
<label>
|
|
||||||
Man
|
|
||||||
<input
|
|
||||||
bind:group={gender}
|
|
||||||
type="radio"
|
|
||||||
name="gender"
|
|
||||||
value="male"
|
|
||||||
id="male"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Kvinna
|
|
||||||
<input
|
|
||||||
bind:group={gender}
|
|
||||||
type="radio"
|
|
||||||
name="gender"
|
|
||||||
value="female"
|
|
||||||
id="female"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<label>
|
|
||||||
Kroppslängd
|
|
||||||
<input
|
|
||||||
onchange={() => calculate()}
|
|
||||||
bind:value={height}
|
|
||||||
type="number"
|
|
||||||
id="height"
|
|
||||||
name="height"
|
|
||||||
size="5"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Midja
|
|
||||||
<input
|
|
||||||
onchange={() => calculate()}
|
|
||||||
type="number"
|
|
||||||
bind:value={waist}
|
|
||||||
id="waist"
|
|
||||||
name="waist"
|
|
||||||
size="5"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
{#if gender == "female"}
|
|
||||||
<label>
|
|
||||||
Höft
|
|
||||||
<input
|
|
||||||
onchange={() => calculate()}
|
|
||||||
type="number"
|
|
||||||
bind:value={hips}
|
|
||||||
id="hips"
|
|
||||||
name="hips"
|
|
||||||
size="5"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
{/if}
|
|
||||||
<label>
|
|
||||||
Hals
|
|
||||||
<input
|
|
||||||
onchange={() => calculate()}
|
|
||||||
type="number"
|
|
||||||
bind:value={neck}
|
|
||||||
id="neck"
|
|
||||||
name="neck"
|
|
||||||
size="5"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</form>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.gender {
|
|
||||||
grid-column: 1 / 2;
|
|
||||||
grid-row: 1 / 4;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { navigate } from "./common";
|
|
||||||
|
|
||||||
let oneRepMax = $state(0);
|
|
||||||
let reps = $state(null);
|
|
||||||
let weight = $state(null);
|
|
||||||
|
|
||||||
// https://www.athlegan.com/calculate-1rm
|
|
||||||
|
|
||||||
const calculate = () => {
|
|
||||||
if (weight && reps) {
|
|
||||||
oneRepMax = weight / (1.0278 - 0.0278 * reps);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<header>
|
|
||||||
<span>
|
|
||||||
<button onclick={() => navigate("start")}>Tillbaka</button>
|
|
||||||
</span>
|
|
||||||
<h1>1RM</h1>
|
|
||||||
</header>
|
|
||||||
<output>
|
|
||||||
{#if oneRepMax > 0}
|
|
||||||
<span>{Math.round(oneRepMax * 100) / 100}</span>
|
|
||||||
{/if}
|
|
||||||
</output>
|
|
||||||
<form>
|
|
||||||
<label>
|
|
||||||
Reps
|
|
||||||
<input
|
|
||||||
onchange={() => calculate()}
|
|
||||||
bind:value={reps}
|
|
||||||
type="number"
|
|
||||||
id="reps"
|
|
||||||
name="reps"
|
|
||||||
size="5"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Vikt
|
|
||||||
<input
|
|
||||||
onchange={() => calculate()}
|
|
||||||
type="number"
|
|
||||||
bind:value={weight}
|
|
||||||
id="weight"
|
|
||||||
name="weight"
|
|
||||||
size="5"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</form>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { currentView } from "./store";
|
|
||||||
|
|
||||||
export const navigate = (
|
|
||||||
page: "start" | "armyfatcalc" | "navyfatcalc" | "onerepmax",
|
|
||||||
) => currentView.update((_) => page);
|
|
||||||
101
src/lib/formulaes.ts
Normal file
101
src/lib/formulaes.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
type Gender = "male" | "female";
|
||||||
|
type Formula = "akf" | "nkf" | "1rm" | "lbs";
|
||||||
|
|
||||||
|
export const calculate = (f: Formula, params: string[]) => {
|
||||||
|
let G: Gender = "male";
|
||||||
|
let H = undefined;
|
||||||
|
switch (f) {
|
||||||
|
case "1rm":
|
||||||
|
return round(oneRepMax(parseFloat(params[0]), parseInt(params[1], 10)));
|
||||||
|
case "lbs":
|
||||||
|
return round(kg2lbs(parseFloat(params[0].replace(",", "."))));
|
||||||
|
case "akf":
|
||||||
|
if (params.length > 3) {
|
||||||
|
H = parseFloat(params[3].replace(",", "."));
|
||||||
|
G = "female";
|
||||||
|
}
|
||||||
|
return round(
|
||||||
|
armyBodyFatComposition(
|
||||||
|
G,
|
||||||
|
parseFloat(params[0].replace(",", ".")),
|
||||||
|
parseFloat(params[1].replace(",", ".")),
|
||||||
|
parseFloat(params[2].replace(",", ".")),
|
||||||
|
H,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case "nkf":
|
||||||
|
if (params.length > 3) {
|
||||||
|
H = parseFloat(params[3].replace(",", "."));
|
||||||
|
G = "female";
|
||||||
|
}
|
||||||
|
return round(
|
||||||
|
navyBodyFatComposition(
|
||||||
|
G,
|
||||||
|
parseFloat(params[0].replace(",", ".")),
|
||||||
|
parseFloat(params[1].replace(",", ".")),
|
||||||
|
parseFloat(params[2].replace(",", ".")),
|
||||||
|
H,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://www.athlegan.com/calculate-1rm
|
||||||
|
const oneRepMax = (weight: number, reps: number) => {
|
||||||
|
return weight / (1.0278 - 0.0278 * reps);
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://www.gigacalculator.com/calculators/army-body-fat-calculator.php
|
||||||
|
const armyBodyFatComposition = (
|
||||||
|
gender: Gender,
|
||||||
|
height: number,
|
||||||
|
neck: number,
|
||||||
|
waist: number,
|
||||||
|
hips?: number,
|
||||||
|
) => {
|
||||||
|
if (gender == "male") {
|
||||||
|
return (
|
||||||
|
86.01 * Math.log10(waist - neck) - 70.041 * Math.log10(height) + 30.3
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
163.205 * Math.log10(waist + hips! - neck) -
|
||||||
|
97.684 * Math.log10(height) -
|
||||||
|
104.912
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://www.omnicalculator.com/health/navy-body-fat
|
||||||
|
const navyBodyFatComposition = (
|
||||||
|
gender: Gender,
|
||||||
|
height: number,
|
||||||
|
neck: number,
|
||||||
|
waist: number,
|
||||||
|
hips?: number,
|
||||||
|
) => {
|
||||||
|
if (gender == "male") {
|
||||||
|
return (
|
||||||
|
495 /
|
||||||
|
(1.0324 -
|
||||||
|
0.19077 * Math.log10(waist - neck) +
|
||||||
|
0.15456 * Math.log10(height)) -
|
||||||
|
450
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
495 /
|
||||||
|
(1.29579 -
|
||||||
|
0.35004 * Math.log10(waist + hips! - neck) +
|
||||||
|
0.221 * Math.log10(height)) -
|
||||||
|
450
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://www.unitconverters.net/weight-and-mass/kg-to-lbs.htm
|
||||||
|
const kg2lbs = (weight: number) => 2.2046226218 * weight;
|
||||||
|
|
||||||
|
const round = (i: number) => {
|
||||||
|
return Math.round(i * 1000) / 1000;
|
||||||
|
};
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
export const currentView = writable("start");
|
export const calculated = writable("");
|
||||||
|
export const formula = writable("");
|
||||||
|
export const display = writable("hi.");
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue