Design overhoul
This commit is contained in:
parent
edc0a00ef9
commit
44b6bdfea2
12 changed files with 435 additions and 492 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.
|
||||
|
||||
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.
|
||||
|
||||
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`?**
|
||||
|
||||
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.
|
||||
|
||||
**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.
|
||||
|
||||
**Why enable `allowJs` in the TS template?**
|
||||
|
||||
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?**
|
||||
|
||||
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)
|
||||
```
|
||||
1rm(weight: number, reps: number, variant: "lower" | "upper")
|
||||
```
|
||||
|
||||
### KG to LBS converter
|
||||
|
||||
```
|
||||
lbs(weight: number)
|
||||
```
|
||||
|
||||
### Army body fat composition calculator
|
||||
|
||||
```
|
||||
abf(length: number, neck: number, waist: number, hips?: number, gender: "male" | "female", metric: boolean)
|
||||
```
|
||||
|
||||
### Navy body fat composition calculator
|
||||
|
||||
```
|
||||
nbf(length: number, neck: number, waist: number, hips?: number, gender: "male" | "female", metric: boolean)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,42 +1,29 @@
|
|||
<script lang="ts">
|
||||
import svelteLogo from "./assets/svelte.svg";
|
||||
import viteLogo from "/vite.svg";
|
||||
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";
|
||||
import Display from "./lib/Display.svelte";
|
||||
import Keypad from "./lib/Keypad.svelte";
|
||||
</script>
|
||||
|
||||
{#if $currentView === "armyfatcalc"}
|
||||
<ArmyFatPercentage />
|
||||
{/if}
|
||||
{#if $currentView === "navyfatcalc"}
|
||||
<NavyFatPercentage />
|
||||
{/if}
|
||||
{#if $currentView === "onerepmax"}
|
||||
<OneRepMax />
|
||||
{/if}
|
||||
{#if $currentView === "start"}
|
||||
<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}
|
||||
<main>
|
||||
<Display />
|
||||
<Keypad />
|
||||
</main>
|
||||
<footer>
|
||||
<p>
|
||||
Made by <a href="https://madr.se">Anders</a>. Source on
|
||||
<a href="https://github.com/madr/kalkylatorer">Github</a>.
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
nav {
|
||||
display: grid;
|
||||
grid-template: subgrid / subgrid;
|
||||
grid-column: 2 / 2;
|
||||
grid-row: 2 / 6;
|
||||
footer {
|
||||
font-size: x-small;
|
||||
position: absolute;
|
||||
bottom: 0.25rem;
|
||||
right: 0.25rem;
|
||||
text-align: center;
|
||||
|
||||
> p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
105
src/app.css
105
src/app.css
|
|
@ -5,7 +5,13 @@
|
|||
|
||||
color-scheme: light dark;
|
||||
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;
|
||||
text-rendering: optimizeLegibility;
|
||||
|
|
@ -14,90 +20,27 @@
|
|||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
min-width: 100vw;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
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;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main {
|
||||
min-width: 100vw;
|
||||
min-height: 100vh;
|
||||
width: 14rem;
|
||||
height: 14rem;
|
||||
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;
|
||||
gap: 1em;
|
||||
padding: 1em;
|
||||
gap: 0.2em;
|
||||
box-sizing: border-box;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(7, 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;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-rows: repeat(8, 1fr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
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