Compare commits

...

20 commits
v2 ... main

Author SHA1 Message Date
516feae195 Add Fast goal 2025-04-29 23:17:01 +02:00
82475127ed Display Arc on summary 2025-04-16 00:22:04 +02:00
35366651ea Add Svelte-prettier 2025-04-16 00:21:34 +02:00
e36914714f Add Arc component 2025-04-16 00:21:07 +02:00
0db5ab19ed Fix progress bar bug in Chrome
Unfixed since 2015. FUCK OFF, Chrome.
2025-03-20 23:55:03 +01:00
e44f71cd92 Fix iOS bug 2025-03-20 16:58:52 +01:00
9cb8a75f74 Add progress bars to summary page 2025-03-20 16:51:40 +01:00
b34bb19fb8 Add PWA: manifest, icons 2025-02-12 23:27:44 +01:00
026b090426 Fix bad default data 2025-02-12 13:29:39 +01:00
394e1394e0 Solve Diet view not showing excluded days 2025-02-06 13:04:37 +01:00
e176843da9 Adjust typo in summary view 2025-02-06 12:47:19 +01:00
9a5bff18bf Format all files 2025-02-06 12:44:53 +01:00
5cadecd692 Display the same date formatting on all views 2025-02-06 12:43:19 +01:00
cb20a55e9b Hide checkboxes on all views 2025-02-06 12:18:20 +01:00
e1430713df Adjust design after feedback 2025-02-06 12:10:05 +01:00
8067b5ae94 Disable color scheme preference 2025-02-06 12:04:07 +01:00
e4386eb009 Remove unused SVG 2025-02-06 12:02:24 +01:00
260d55d3de Add content to info page 2025-02-06 11:59:30 +01:00
1fd4b6794a Improve design 2025-02-06 11:59:30 +01:00
2c52b9c266 Add diet plan 2025-02-05 12:04:02 +01:00
22 changed files with 5826 additions and 463 deletions

6
README
View file

@ -14,7 +14,5 @@ Stack
----- -----
Vanilla Svelte, with a plugin for persistent store using Vanilla Svelte, with a plugin for persistent store using
localStorage. localStorage, as well as some PWA addons for better offline
use on mobile devices.
As an academic exercise, this might as well be ported to a
PWA by introducing a webappmanifest and a ServiceWorker.

View file

@ -183,324 +183,337 @@ const gym = [
}, },
{ {
name: "A 1:1", name: "A 1:1",
planned_at: "2025-02-02",
completed: false,
completed_at: null,
},
{
name: "A 1:2",
planned_at: "2025-02-04",
completed: false,
completed_at: null,
},
{
name: "A 1:3",
planned_at: "2025-02-06",
completed: false,
completed_at: null,
},
{
name: "A 2:1",
planned_at: "2025-02-09", planned_at: "2025-02-09",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "A 2:2", name: "A 1:2",
planned_at: "2025-02-11", planned_at: "2025-02-11",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "A 2:3", name: "A 1:3",
planned_at: "2025-02-13", planned_at: "2025-02-13",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "A 3:1", name: "A 2:1",
planned_at: "2025-02-16", planned_at: "2025-02-16",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "A 3:2", name: "A 2:2",
planned_at: "2025-02-18", planned_at: "2025-02-18",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "A 3:3", name: "A 2:3",
planned_at: "2025-02-20", planned_at: "2025-02-20",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "A 4:1", name: "A 3:1",
planned_at: "2025-02-23", planned_at: "2025-02-23",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "A 4:2", name: "A 3:2",
planned_at: "2025-02-25", planned_at: "2025-02-25",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "A 4:3", name: "A 3:3",
planned_at: "2025-02-27", planned_at: "2025-02-27",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "B 1:1", name: "A 4:1",
planned_at: "2025-03-02", planned_at: "2025-03-02",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "B 1:2", name: "A 4:2",
planned_at: "2025-03-04", planned_at: "2025-03-04",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "B 1:3", name: "A 4:3",
planned_at: "2025-03-06", planned_at: "2025-03-06",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "B 2:1", name: "B 1:1",
planned_at: "2025-03-09", planned_at: "2025-03-09",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "B 2:2", name: "B 1:2",
planned_at: "2025-03-11", planned_at: "2025-03-11",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "B 2:3", name: "B 1:3",
planned_at: "2025-03-13", planned_at: "2025-03-13",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "B 3:1", name: "B 2:1",
planned_at: "2025-03-16", planned_at: "2025-03-16",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "B 3:2", name: "B 2:2",
planned_at: "2025-03-18", planned_at: "2025-03-18",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "B 3:3", name: "B 2:3",
planned_at: "2025-03-20", planned_at: "2025-03-20",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "B 4:1", name: "B 3:1",
planned_at: "2025-03-23", planned_at: "2025-03-23",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "B 4:2", name: "B 3:2",
planned_at: "2025-03-25", planned_at: "2025-03-25",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "B 4:3", name: "B 3:3",
planned_at: "2025-03-27", planned_at: "2025-03-27",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "C 1:1", name: "B 4:1",
planned_at: "2025-03-30", planned_at: "2025-03-30",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "C 1:2", name: "B 4:2",
planned_at: "2025-04-01", planned_at: "2025-04-01",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "C 1:3", name: "B 4:3",
planned_at: "2025-04-03", planned_at: "2025-04-03",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "C 2:1", name: "C 1:1",
planned_at: "2025-04-06", planned_at: "2025-04-06",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "C 2:2", name: "C 1:2",
planned_at: "2025-04-08", planned_at: "2025-04-08",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "C 2:3", name: "C 1:3",
planned_at: "2025-04-10", planned_at: "2025-04-10",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "C 3:1", name: "C 2:1",
planned_at: "2025-04-13", planned_at: "2025-04-13",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "C 3:2", name: "C 2:2",
planned_at: "2025-04-15", planned_at: "2025-04-15",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{ {
name: "C 3:3", name: "C 2:3",
planned_at: "2025-04-17", planned_at: "2025-04-17",
completed: false, completed: false,
completed_at: null, completed_at: null,
}, },
{
name: "C 3:1",
planned_at: "2025-04-20",
completed: false,
completed_at: null,
},
{
name: "C 3:2",
planned_at: "2025-04-22",
completed: false,
completed_at: null,
},
{
name: "C 3:3",
planned_at: "2025-04-24",
completed: false,
completed_at: null,
},
]; ];
const diet = { const fast = [
days: [ { name: "1:1", planned_at: "2025-04-30", completed: false },
{ date: "2025-02-10", completed: false }, { name: "1:2", planned_at: "2025-05-02", completed: false },
{ date: "2025-02-11", completed: false }, { name: "2:1", planned_at: "2025-05-05", completed: false },
{ date: "2025-02-12", completed: false }, { name: "2:2", planned_at: "2025-05-07", completed: false },
{ date: "2025-02-13", completed: false }, { name: "2:3", planned_at: "2025-05-09", completed: false },
{ date: "2025-02-14", completed: false }, { name: "3:1", planned_at: "2025-05-12", completed: false },
{ date: "2025-02-15", completed: false }, { name: "3:2", planned_at: "2025-05-14", completed: false },
{ date: "2025-02-16", completed: false }, { name: "3:3", planned_at: "2025-05-16", completed: false },
{ date: "2025-02-17", completed: false }, { name: "4:1", planned_at: "2025-05-19", completed: false },
{ date: "2025-02-18", completed: false }, { name: "4:2", planned_at: "2025-05-21", completed: false },
{ date: "2025-02-19", completed: false }, { name: "4:3", planned_at: "2025-05-23", completed: false },
{ date: "2025-02-10", completed: false }, { name: "5:1", planned_at: "2025-05-26", completed: false },
{ date: "2025-02-21", completed: false }, { name: "5:2", planned_at: "2025-05-28", completed: false },
{ date: "2025-02-22", completed: false }, { name: "5:3", planned_at: "2025-05-30", completed: false },
{ date: "2025-02-23", completed: false }, ];
{ date: "2025-02-24", completed: false },
{ date: "2025-02-25", completed: false },
{ date: "2025-02-26", completed: false },
{ date: "2025-02-27", completed: false },
{ date: "2025-02-28", completed: false },
{ date: "2025-03-01", completed: false },
{ date: "2025-03-02", completed: false },
{ date: "2025-03-03", completed: false },
{ date: "2025-03-05", completed: false },
{ date: "2025-03-06", completed: false },
{ date: "2025-03-07", completed: false },
{ date: "2025-03-08", completed: false },
{ date: "2025-03-09", completed: false },
{ date: "2025-03-10", completed: false },
{ date: "2025-03-11", completed: false },
{ date: "2025-03-12", completed: false },
{ date: "2025-03-13", completed: false },
{ date: "2025-03-14", completed: false },
{ date: "2025-03-15", completed: false },
{ date: "2025-03-16", completed: false },
{ date: "2025-03-17", completed: false },
{ date: "2025-03-18", completed: false },
{ date: "2025-03-19", completed: false },
{ date: "2025-03-10", completed: false },
{ date: "2025-03-21", completed: false },
{ date: "2025-03-22", completed: false },
{ date: "2025-03-23", completed: false },
{ date: "2025-03-24", completed: false },
{ date: "2025-03-25", completed: false },
{ date: "2025-03-26", completed: false },
{ date: "2025-03-27", completed: false },
{ date: "2025-03-28", completed: false },
{ date: "2025-03-30", completed: false },
{ date: "2025-03-31", completed: false },
{ date: "2025-04-01", completed: false },
{ date: "2025-04-02", completed: false },
{ date: "2025-04-03", completed: false },
{ date: "2025-04-04", completed: false },
{ date: "2025-04-05", completed: false },
{ date: "2025-04-06", completed: false },
{ date: "2025-04-07", completed: false },
{ date: "2025-04-08", completed: false },
{ date: "2025-04-09", completed: false },
{ date: "2025-04-10", completed: false },
{ date: "2025-04-11", completed: false },
{ date: "2025-04-12", completed: false },
{ date: "2025-04-13", completed: false },
{ date: "2025-04-14", completed: false },
{ date: "2025-04-15", completed: false },
{ date: "2025-04-16", completed: false },
{ date: "2025-04-18", completed: false },
{ date: "2025-04-19", completed: false },
{ date: "2025-04-10", completed: false },
{ date: "2025-04-21", completed: false },
{ date: "2025-04-22", completed: false },
{ date: "2025-04-23", completed: false },
{ date: "2025-04-24", completed: false },
{ date: "2025-04-25", completed: false },
{ date: "2025-04-26", completed: false },
{ date: "2025-04-27", completed: false },
{ date: "2025-04-28", completed: false },
{ date: "2025-04-29", completed: false },
{ date: "2025-04-30", completed: false },
{ date: "2025-05-01", completed: false },
{ date: "2025-05-02", completed: false },
{ date: "2025-05-03", completed: false },
{ date: "2025-05-04", completed: false },
{ date: "2025-05-05", completed: false },
{ date: "2025-05-06", completed: false },
{ date: "2025-05-07", completed: false },
{ date: "2025-05-08", completed: false },
{ date: "2025-05-09", completed: false },
{ date: "2025-05-10", completed: false },
{ date: "2025-05-11", completed: false },
{ date: "2025-05-12", completed: false },
{ date: "2025-05-13", completed: false },
{ date: "2025-05-14", completed: false },
{ date: "2025-05-15", completed: false },
{ date: "2025-05-16", completed: false },
{ date: "2025-05-17", completed: false },
{ date: "2025-05-18", completed: false },
{ date: "2025-05-19", completed: false },
{ date: "2025-05-10", completed: false },
{ date: "2025-05-21", completed: false },
{ date: "2025-05-22", completed: false },
{ date: "2025-05-23", completed: false },
{ date: "2025-05-24", completed: false },
{ date: "2025-05-25", completed: false },
{ date: "2025-05-26", completed: false },
{ date: "2025-05-27", completed: false },
{ date: "2025-05-28", completed: false },
{ date: "2025-05-29", completed: false },
{ date: "2025-05-30", completed: false },
{ date: "2025-05-31", completed: false },
],
reds: {
"2025-03-04": "Semmeldagen",
"2025-03-29": "Födelsedag",
"2025-04-17": "Påsk",
},
};
const diet = [
{ date: "2025-02-10", completed: false },
{ date: "2025-02-11", completed: false },
{ date: "2025-02-12", completed: false },
{ date: "2025-02-13", completed: false },
{ date: "2025-02-14", completed: false },
{ date: "2025-02-15", completed: false },
{ date: "2025-02-16", completed: false },
{ date: "2025-02-17", completed: false },
{ date: "2025-02-18", completed: false },
{ date: "2025-02-19", completed: false },
{ date: "2025-02-10", completed: false },
{ date: "2025-02-21", completed: false },
{ date: "2025-02-22", completed: false },
{ date: "2025-02-23", completed: false },
{ date: "2025-02-24", completed: false },
{ date: "2025-02-25", completed: false },
{ date: "2025-02-26", completed: false },
{ date: "2025-02-27", completed: false },
{ date: "2025-02-28", completed: false },
{ date: "2025-03-01", completed: false },
{ date: "2025-03-02", completed: false },
{ date: "2025-03-03", completed: false },
{ date: "2025-03-04", excluded: true, because: "Semmeldagen" },
{ date: "2025-03-05", completed: false },
{ date: "2025-03-06", completed: false },
{ date: "2025-03-07", completed: false },
{ date: "2025-03-08", completed: false },
{ date: "2025-03-09", completed: false },
{ date: "2025-03-10", completed: false },
{ date: "2025-03-11", completed: false },
{ date: "2025-03-12", completed: false },
{ date: "2025-03-13", completed: false },
{ date: "2025-03-14", completed: false },
{ date: "2025-03-15", completed: false },
{ date: "2025-03-16", completed: false },
{ date: "2025-03-17", completed: false },
{ date: "2025-03-18", completed: false },
{ date: "2025-03-19", completed: false },
{ date: "2025-03-20", completed: false },
{ date: "2025-03-21", completed: false },
{ date: "2025-03-22", completed: false },
{ date: "2025-03-23", completed: false },
{ date: "2025-03-24", completed: false },
{ date: "2025-03-25", completed: false },
{ date: "2025-03-26", completed: false },
{ date: "2025-03-27", completed: false },
{ date: "2025-03-28", completed: false },
{ date: "2025-03-29", excluded: true, because: "Födelsedag" },
{ date: "2025-03-30", completed: false },
{ date: "2025-03-31", completed: false },
{ date: "2025-04-01", completed: false },
{ date: "2025-04-02", completed: false },
{ date: "2025-04-03", completed: false },
{ date: "2025-04-04", completed: false },
{ date: "2025-04-05", completed: false },
{ date: "2025-04-06", completed: false },
{ date: "2025-04-07", completed: false },
{ date: "2025-04-08", completed: false },
{ date: "2025-04-09", completed: false },
{ date: "2025-04-10", completed: false },
{ date: "2025-04-11", completed: false },
{ date: "2025-04-12", completed: false },
{ date: "2025-04-13", completed: false },
{ date: "2025-04-14", completed: false },
{ date: "2025-04-15", completed: false },
{ date: "2025-04-16", completed: false },
{ date: "2025-04-17", excluded: true, because: "Påsk" },
{ date: "2025-04-18", completed: false },
{ date: "2025-04-19", completed: false },
{ date: "2025-04-10", completed: false },
{ date: "2025-04-21", completed: false },
{ date: "2025-04-22", completed: false },
{ date: "2025-04-23", completed: false },
{ date: "2025-04-24", completed: false },
{ date: "2025-04-25", completed: false },
{ date: "2025-04-26", completed: false },
{ date: "2025-04-27", completed: false },
{ date: "2025-04-28", completed: false },
{ date: "2025-04-29", completed: false },
{ date: "2025-04-30", completed: false },
{ date: "2025-05-01", completed: false },
{ date: "2025-05-02", completed: false },
{ date: "2025-05-03", completed: false },
{ date: "2025-05-04", completed: false },
{ date: "2025-05-05", completed: false },
{ date: "2025-05-06", completed: false },
{ date: "2025-05-07", completed: false },
{ date: "2025-05-08", completed: false },
{ date: "2025-05-09", completed: false },
{ date: "2025-05-10", completed: false },
{ date: "2025-05-11", completed: false },
{ date: "2025-05-12", completed: false },
{ date: "2025-05-13", completed: false },
{ date: "2025-05-14", completed: false },
{ date: "2025-05-15", completed: false },
{ date: "2025-05-16", completed: false },
{ date: "2025-05-17", completed: false },
{ date: "2025-05-18", completed: false },
{ date: "2025-05-19", completed: false },
{ date: "2025-05-10", completed: false },
{ date: "2025-05-21", completed: false },
{ date: "2025-05-22", completed: false },
{ date: "2025-05-23", completed: false },
{ date: "2025-05-24", completed: false },
{ date: "2025-05-25", completed: false },
{ date: "2025-05-26", completed: false },
{ date: "2025-05-27", completed: false },
{ date: "2025-05-28", completed: false },
{ date: "2025-05-29", completed: false },
{ date: "2025-05-30", completed: false },
{ date: "2025-05-31", completed: false },
];
export default { export default {
cardio, cardio,
gym, gym,
diet, diet,
fast,
}; };

View file

@ -1,13 +1,12 @@
<!doctype html> <!doctype html>
<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>Strand</title>
<title>Strand</title> </head>
</head> <body>
<body> <div id="app"></div>
<div id="app"></div> <script type="module" src="/src/main.ts"></script>
<script type="module" src="/src/main.ts"></script> </body>
</body>
</html> </html>

4802
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -7,15 +7,19 @@
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json",
"format": "prettier ./src --write"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^5.0.3", "@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tsconfig/svelte": "^5.0.4", "@tsconfig/svelte": "^5.0.4",
"prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.3.3",
"svelte": "^5.15.0", "svelte": "^5.15.0",
"svelte-check": "^4.1.1", "svelte-check": "^4.1.1",
"typescript": "~5.6.2", "typescript": "~5.6.2",
"vite": "^6.0.5" "vite": "^6.0.5",
"vite-plugin-pwa": "^0.21.1"
}, },
"dependencies": { "dependencies": {
"svelte-persisted-store": "^0.12.0" "svelte-persisted-store": "^0.12.0"

BIN
public/icon512_maskable.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/icon512_rounded.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,41 +1,35 @@
<script lang="ts"> <script lang="ts">
// import svelteLogo from "./assets/svelte.svg"; // import svelteLogo from "./assets/svelte.svg";
// import viteLogo from "/vite.svg"; // import viteLogo from "/vite.svg";
import { currentView } from "./lib/store"; import { currentView } from "./lib/store";
import Summary from "./lib/Summary.svelte"; import Summary from "./lib/Summary.svelte";
import GymProgress from "./lib/GymProgress.svelte"; import GymProgress from "./lib/GymProgress.svelte";
import CardioProgress from "./lib/CardioProgress.svelte"; import CardioProgress from "./lib/CardioProgress.svelte";
import DietProgress from "./lib/DietProgress.svelte"; import DietProgress from "./lib/DietProgress.svelte";
import FastProgress from "./lib/FastProgress.svelte";
import Info from "./lib/Info.svelte";
</script> </script>
<div class="chrome"> <div class="chrome">
<main> {#if $currentView === 0}
{#if $currentView === 0} <Summary />
<Summary /> {/if}
{/if} {#if $currentView === 1}
{#if $currentView === 1} <GymProgress />
<GymProgress /> {/if}
{/if} {#if $currentView === 2}
{#if $currentView === 2} <CardioProgress />
<CardioProgress /> {/if}
{/if} {#if $currentView === 3}
{#if $currentView === 3} <DietProgress />
<DietProgress /> {/if}
{/if} {#if $currentView === 4}
</main> <Info />
{/if}
{#if $currentView === 5}
<FastProgress />
{/if}
</div> </div>
<style> <style>
.chrome {
height: 100vh;
display: flex;
flex-direction: column-reverse;
main {
flex: 1;
overflow: auto;
text-align: center;
padding: 0.5em;
}
}
</style> </style>

View file

@ -1,68 +1,135 @@
:root { :root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; font-family: "Open Sans", Inter, system-ui, Avenir, Helvetica, Arial,
line-height: 1.5; sans-serif;
font-weight: 400; line-height: 1.5;
font-weight: 400;
color-scheme: light; font-synthesis: none;
color: rgba(255, 255, 255, 0.87); text-rendering: optimizeLegibility;
background-color: #242424; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
} }
body { body {
margin: 0; margin: 0;
}
.top-button {
position: absolute;
top: 0.25em;
right: 0.25em;
button {
padding: 1em;
background-color: #ff0;
color: #000;
&:hover {
filter: brightness(95%);
}
}
}
main {
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #e0e0e0;
box-sizing: border-box;
padding: 1em;
} }
h1 { h1 {
font-size: 3.2em; font-size: 3em;
line-height: 1.1; line-height: 1.1;
} margin: 0.2em 0;
text-wrap: balance;
.card {
padding: 2em;
} }
button { button {
border-radius: 8px; appearance: none;
border: 1px solid transparent; border-radius: 0;
padding: 0.6em 1.2em; border: 0;
font-size: 1em; padding: 0;
font-weight: 500; font-size: 1em;
font-family: inherit; font-weight: 500;
background-color: #1a1a1a; font-family: inherit;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
} }
@media (prefers-color-scheme: light) { article {
:root { opacity: 0.8;
color: #213547; label {
background-color: #ffffff; display: block;
padding: 1em 0.66em;
input {
grid-area: input;
position: absolute;
opacity: 0;
pointer-events: none;
} }
a:hover { strong {
color: #747bff; display: block;
font-size: 1.25em;
} }
button { small {
background-color: #f9f9f9; grid-area: date;
} }
}
}
article:has(:checked) {
opacity: 0.25;
+ article:not(:has(:checked)) {
opacity: 1;
}
}
.clickable {
box-shadow: 0 3px 0 rgba(0, 0, 0, 0.75);
border: 2px solid #222;
border-radius: 8px;
background-color: #f1f1f1;
display: block;
&:hover,
&:focus {
background-color: #e1e1e1;
}
&:active {
transform: translateY(1px);
}
}
.back {
background-color: #e0e0e0;
color: #000;
&:hover,
&:focus {
background-color: #d0d0d0;
}
}
.calendar {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 2em 0.5em;
}
header {
display: flex;
padding: 1em;
gap: 0.5em;
align-items: center;
button {
padding: 0.5em 1em;
}
h1 {
font-size: 1.25em;
flex: 1;
margin: 0;
}
} }

192
src/lib/Arc.svelte Normal file
View file

@ -0,0 +1,192 @@
<script lang="ts">
import DietProgress from "./DietProgress.svelte";
import P from "./svg-path";
import { gym, cardio, diet, fast } from "./store";
export const DEG_TO_RAD = Math.PI / 180;
export const RAD_TO_DEG = 180 / Math.PI;
export const FULL_CIRCLE_IN_RADIANS = 2 * Math.PI;
const size = 400;
const segmentHeight = 16;
const span = 0.8 * FULL_CIRCLE_IN_RADIANS;
const startAngle = 0.25 * FULL_CIRCLE_IN_RADIANS + span / 2;
const perimiterWidth = size * Math.PI * (span / FULL_CIRCLE_IN_RADIANS);
const pixelToRadians = span / perimiterWidth;
const x = 0;
const y = 0;
const points = (
radius: number,
radLength: number,
thickness: number,
offset?: number,
) => {
const borderRadius = thickness / 2;
const outerRadius = radius;
const innerRadius = outerRadius - thickness;
const radEndAngle = startAngle - radLength;
const borderRadiusAngle =
(borderRadius / (outerRadius * FULL_CIRCLE_IN_RADIANS)) *
FULL_CIRCLE_IN_RADIANS;
const isLongTrack = radLength - 2 * borderRadiusAngle > Math.PI;
const start = startAngle + (offset ?? 0);
return P()
.moveTo(
-Math.sin(start) * (outerRadius - borderRadius),
Math.cos(start) * (outerRadius - borderRadius),
)
.arcTo(
borderRadius,
borderRadius,
false,
true,
-Math.sin(start - borderRadiusAngle) * outerRadius,
Math.cos(start - borderRadiusAngle) * outerRadius,
)
.arcTo(
outerRadius,
outerRadius,
isLongTrack,
true,
-Math.sin(radEndAngle + borderRadiusAngle) * outerRadius,
Math.cos(radEndAngle + borderRadiusAngle) * outerRadius,
)
.arcTo(
borderRadius,
borderRadius,
false,
true,
-Math.sin(radEndAngle) * (outerRadius - borderRadius),
Math.cos(radEndAngle) * (outerRadius - borderRadius),
)
.lineTo(
-Math.sin(radEndAngle) * (innerRadius + borderRadius),
Math.cos(radEndAngle) * (innerRadius + borderRadius),
)
.arcTo(
borderRadius,
borderRadius,
false,
true,
-Math.sin(radEndAngle + borderRadiusAngle) * innerRadius,
Math.cos(radEndAngle + borderRadiusAngle) * innerRadius,
)
.arcTo(
innerRadius,
innerRadius,
isLongTrack,
false,
-Math.sin(start - borderRadiusAngle) * innerRadius,
Math.cos(start - borderRadiusAngle) * innerRadius,
)
.arcTo(
borderRadius,
borderRadius,
false,
true,
-Math.sin(start) * (innerRadius + borderRadius),
Math.cos(start) * (innerRadius + borderRadius),
)
.close()
.stringify();
};
let items = [
{
c: "gym",
progress: $gym.filter((c) => c.completed).length / $gym.length,
level: 0,
},
{
c: "cardio",
progress: $cardio.filter((c) => c.completed).length / $cardio.length,
level: 1,
},
{
c: "diet",
progress:
$diet.filter((c) => c.completed).length /
$diet.filter((c) => !c.excluded).length,
level: 2,
},
{
c: "fast",
progress: $fast.filter((c) => c.completed).length / $fast.length,
level: 3,
},
];
</script>
<figure>
<svg viewBox="0 0 {size} {size}" role="presentation">
<g transform="${`translate(${x},${y})`}" fill="none">
<g transform={`translate(${size / 2},${size / 2})`}>
{#each items as { c, progress, level }}
<path
fill="#fff"
opacity="0.066"
d={points(
size / 2 - level * (segmentHeight + 4),
span,
segmentHeight,
)}
/>
<path
class={c}
fill="currentColor"
d={points(
size / 2 - level * (segmentHeight + 4),
progress * span,
segmentHeight,
)}
/>
<path
fill="#fff"
opacity="0.4"
d={points(
size / 2 - level * (segmentHeight + 4) - 4,
progress * span * 0.98,
4,
)}
/>
{/each}
</g>
</g>
</svg>
</figure>
<style>
svg {
display: block;
max-width: 80%;
aspect-ratio: 1;
}
figure {
display: flex;
align-items: center;
place-content: center;
width: 100%;
padding: 0;
margin: 2rem 0 1.5rem;
}
.cardio {
color: #5dc5f8;
}
.gym {
color: #f62b5a;
}
.diet {
color: #35d450;
}
.fast {
color: #ff00ff;
}
</style>

View file

@ -1,23 +1,22 @@
<script lang="ts"> <script lang="ts">
import { cardio, currentView } from "./store"; import { cardio } from "./store";
import {dm, back } from "./common";
const remaining = $cardio.length; const remaining = $cardio.length;
const days = ["ons", "fre", "mån"]; const days = ["ons", "fre", "mån"];
let done = $derived($cardio.filter((c) => c.completed).length); let done = $derived($cardio.filter((c) => c.completed).length);
const back = () => {
currentView.update((_) => 0);
};
</script> </script>
<div> <header>
<button onclick={() => back()}>&lt;-</button> <button class="clickable back" onclick={() => back()}>←</button>
<h1> <h1>
Cardio {done}/{remaining} Konditionsträning: {done} av {remaining}
</h1> </h1>
<progress max={remaining} value={done}></progress> </header>
<main>
<progress hidden max={remaining} value={done}></progress>
<div class="calendar"> <div class="calendar">
{#each $cardio as col, i} {#each $cardio as col, i}
<article> <article class="clickable">
<label> <label>
<input type="checkbox" bind:checked={col.completed} /> <input type="checkbox" bind:checked={col.completed} />
<strong <strong
@ -25,47 +24,19 @@
.map((m) => `${m} min`) .map((m) => `${m} min`)
.join(" + ")}, {col.pause_for} vila</strong .join(" + ")}, {col.pause_for} vila</strong
> >
<small>{days[i % 3]} {col.planned_at}</small> <small>{days[i % 3]} {dm(col.planned_at)}</small>
</label> </label>
</article> </article>
{/each} {/each}
</div> </div>
</div> </main>
<style> <style>
.calendar { main {
display: grid; background-color: #5dc5f8;
grid-template-columns: 1fr 1fr 1fr; border-top: 4px solid rgba(0, 0, 0, 0.2)
gap: 0.5em 0.25em;
} }
article {
border: 2px solid #aaa;
border-radius: 7px;
label {
display: grid;
grid-template-columns: auto 1fr;
grid-template-areas: "input name" "input date";
padding: 1.5em;
input {
grid-area: input;
}
strong {
grid-area: name;
}
small {
grid-area: date;
}
}
}
article:has(:checked) {
border-color: #080;
background-color: #afa;
}
article:nth-child(1) { article:nth-child(1) {
grid-column: 2; grid-column: 2;
} }

View file

@ -1,79 +1,71 @@
<script lang="ts"> <script lang="ts">
import { diet, currentView } from "./store"; import { diet } from "./store";
const dayscount = $diet.days.length; import { dm, back } from "./common";
let done = $derived($diet.days.filter((c) => c.completed).length); const dayscount = $derived($diet.filter((c) => !c.excluded).length);
let done = $derived($diet.filter((c) => c.completed).length);
const back = () => {
currentView.update((_) => 0);
};
</script> </script>
<div> <header>
<button onclick={() => back()}>&lt;-</button> <button class="clickable back" onclick={() => back()}></button>
<h1> <h1>
Diet {done}/{dayscount} Diet: {done} av {dayscount}
</h1> </h1>
<progress max={dayscount} value={done}></progress> </header>
<main>
<progress max={dayscount} hidden value={done}></progress>
<div class="calendar"> <div class="calendar">
{#each $diet.days as day, i} {#each $diet as day, i}
<article> <article class="clickable">
{#if day.date in $diet.reds} {#if day.excluded}
<div class="red"> <div class="red">
{$diet.reds[day.date]} {day.because}
</div> </div>
{:else} {:else}
<label> <label>
<input type="checkbox" bind:checked={day.completed} /> <input type="checkbox" bind:checked={day.completed} />
{day.date} {dm(day.date)}
</label> </label>
{/if} {/if}
</article> </article>
{/each} {/each}
</div> </div>
</div> </main>
<style> <style>
main {
background-color: #35d450;
border-top: 4px solid rgba(0, 0, 0, 0.2)
}
.calendar { .calendar {
display: flex; grid-template-columns: repeat(7, 1fr);
flex-direction: column;
gap: 0.5em 0.25em;
} }
.red { .red {
background: #ddd; padding: 0.75em;
color: #888; overflow: hidden;
padding: 1.5em; font-size: 0.66em;
word-break: break-all;
}
article:has(.red) {
pointer-events: none;
opacity: 0.33 !important;
} }
article { article {
border: 2px solid #aaa; > * {
border-radius: 7px; padding: 0.75em 0.5em;
font-size: 0.9em;
text-align: center;
label { &:first-word {
display: grid; font-size: 2em;
grid-template-columns: auto 1fr; display: block;
grid-template-areas: "input name" "input date"; }
padding: 1.5em;
input {
grid-area: input;
}
strong {
grid-area: name;
}
small {
grid-area: date;
}
} }
} }
article:has(:checked) {
border-color: #080;
background-color: #afa;
}
article:nth-child(1) {
grid-column: 3;
}
</style> </style>

View file

@ -0,0 +1,39 @@
<script lang="ts">
import { fast } from "./store";
import { dm, back } from "./common";
const remaining = $fast.length;
const days = ["ons", "fre", "mån"];
let done = $derived($fast.filter((c) => c.completed).length);
</script>
<header>
<button class="clickable back" onclick={() => back()}>←</button>
<h1>
Fasta: {done} av {remaining}
</h1>
</header>
<main>
<progress hidden max={remaining} value={done}></progress>
<div class="calendar">
{#each $fast as col, i}
<article class="clickable">
<label>
<input type="checkbox" bind:checked={col.completed} />
<strong>{col.name}</strong>
<small>{days[i % 3]} {dm(col.planned_at)}</small>
</label>
</article>
{/each}
</div>
</main>
<style>
main {
background-color: #5dc5f8;
border-top: 4px solid rgba(0, 0, 0, 0.2);
}
article:nth-child(1) {
grid-column: 2;
}
</style>

View file

@ -1,64 +1,36 @@
<script lang="ts"> <script lang="ts">
import { gym, currentView } from "./store"; import { gym } from "./store";
import {dm, back } from "./common";
const remaining = $gym.length; const remaining = $gym.length;
const days = ["sön", "tis", "tor"];
let done = $derived($gym.filter((c) => c.completed).length); let done = $derived($gym.filter((c) => c.completed).length);
const back = () => {
currentView.update((_) => 0);
};
</script> </script>
<div> <header>
<button onclick={() => back()}>&lt;-</button> <button class="clickable back" onclick={() => back()}>←</button>
<h1> <h1>
Gym {done}/{remaining} Styrketräning: {done} av {remaining}
</h1> </h1>
<progress max={remaining} value={done}></progress> </header>
<main>
<progress hidden max={remaining} value={done}></progress>
<div class="calendar"> <div class="calendar">
{#each $gym as col} {#each $gym as col, i}
<article> <article class="clickable">
<label> <label>
<input type="checkbox" bind:checked={col.completed} /> <input type="checkbox" bind:checked={col.completed} />
<strong>{col.name}</strong> <strong>{col.name}</strong>
<small>{col.planned_at}</small> <small>{days[i % 3]} {dm(col.planned_at)}</small>
</label> </label>
</article> </article>
{/each} {/each}
</div> </div>
</div> </main>
<style> <style>
.calendar { main {
display: grid; background-color: #f62b5a;
grid-template-columns: 1fr 1fr 1fr; border-top: 4px solid rgba(0, 0, 0, 0.2)
gap: 0.5em 0.25em;
}
article {
border: 2px solid #aaa;
border-radius: 7px;
label {
display: grid;
grid-template-columns: auto 1fr;
grid-template-areas: "input name" "input date";
padding: 1.5em;
input {
grid-area: input;
}
strong {
grid-area: name;
}
small {
grid-area: date;
}
}
}
article:has(:checked) {
border-color: #080;
background-color: #afa;
} }
article:nth-child(1) { article:nth-child(1) {

92
src/lib/Info.svelte Normal file
View file

@ -0,0 +1,92 @@
<script lang="ts">
import { currentView } from "./store";
const back = () => {
currentView.update((_) => 0);
};
</script>
<header>
<button class="clickable back" onclick={() => back()}>←</button>
<h1>Va?</h1>
</header>
<main>
<div class="infobox">
<h2>Hej, Anders har en 40-årskris xD</h2>
<p><em>Dessutom hägrar en vecka på Sweden rock festival.</em>
Ryggen och benen behöver vara i form.</p>
<p>
För att göra träningen litet mer belönande skapades denna app för att ge honom
möjlighet att bocka i allt han gör.
</p>
<h2>Börja löpträna</h2>
<p>Anders har tidigare löptränat, men inte gjort det kontinuerligt sedan 2017.</p>
<p><b>Metod:</b> löpträning 3 gånger per vecka: 2 pass med 2 intervaller, ett långpass. Linjär
progression med 60&ndash;120 sekunders ökning per pass.</p>
<strong>Mål: 5km löpning efter 7 veckor.</strong>
<h2>Återuppta styrkelyft</h2>
<p>Anders föredrar baslyft med frivikter. Hans PB slogs senast januari 2023.</p>
<p>
<b>Metod</b>: styrketräning 3 gånger per vecka. 4 veckor grundträning (A-blocket),
4 veckor specialiserad träning (B-blocket), 3 veckor toppning (C-blocket).
</p>
<strong>Mål: 11 veckors träning, med avslutande toppning och
styrkelyftstotal.</strong>
<h2>Inleda diet</h2>
<p>
Anders har ett uppskattat BMI på 28-32 vid start av denna utmaning.
</p>
<p>
<b>Metod</b>: Följa
<a href="https://www.youtube.com/watch?v=fB_ESE2XwOU">Alan Thralls tips</a>, som listas nedan.
Anders cheat meals: Semla på semmeldagen, fika på födelsedagen, liten godispåse på lördagar.
</p>
<blockquote>
<ul>
<li title="Nothing except meals.">Stop snacking.</li>
<li
title="Fat source you will get anyway. Vegetables not included, eat free."
>
1 protein, 1 carb per meal.
</li>
<li title="no 'ands'. Do not add anything to the meal.">
Avoid addons.
</li>
<li title="No soda, no chips, no västerbottensticks.">
Fast food? just get 1 item
</li>
<li title="no beer, no soda, no milk - just drink water">
No liquid calories.
</li>
</ul>
</blockquote>
<p><strong>Mål: Följa ovan kostråd 16 veckor, med fördefinierade cheat meals.</strong></p>
<h2>Vem?</h2>
<p>
Anders skapade denna PWA. Han är en webbutvecklare som är pappa, älskar hårdrock och föredrar att
betrakta sina datorer som byggsatser.
</p>
</div>
</main>
<style>
h2 {
margin-bottom: 0;
&:first-child {
margin-top: 0.66rem;
}
}
main {
background-color: #ff0;
border-top: 4px solid rgba(0, 0, 0, 0.2)
}
.infobox {
margin: 0.1em;
background-color: #fff;
border: 3px solid #aaa;
border-radius: 1rem;
padding: 0.5em 1em;
}
</style>

View file

@ -1,62 +1,205 @@
<script lang="ts"> <script lang="ts">
import { gym, cardio, diet, currentView } from "./store"; import { gym, fast, cardio, diet, currentView } from "./store";
let gymProgress = $derived($gym.filter((c) => c.completed).length); import Arc from "./Arc.svelte";
let cardioProgress = $derived($cardio.filter((c) => c.completed).length); let gymProgress = $derived($gym.filter((c) => c.completed).length);
let dietProgress = $derived($diet.days.filter((c) => c.completed).length); let cardioProgress = $derived($cardio.filter((c) => c.completed).length);
let fastProgress = $derived($fast.filter((c) => c.completed).length);
let dietProgress = $derived($diet.filter((c) => c.completed).length);
let dietTotal = $derived($diet.filter((c) => !c.excluded).length);
const navigate = (v: 0 | 1 | 2 | 3) => { const navigate = (v: 0 | 1 | 2 | 3 | 4 | 5) => {
currentView.update((_) => v); currentView.update((_) => v);
}; };
</script> </script>
<div> <main>
<h1>Summary</h1> <h1>Dags att komma i form!</h1>
<ul class="cards"> <Arc />
<li> <div class="progress">
<button onclick={() => navigate(1)}> <h2>Styrketräning, 3d/v under 11 veckor</h2>
<i>{gymProgress}</i> / {$gym.length} <span>gympass gjorda</span> <div class="progress-row">
</button> <progress class="gym" value={gymProgress} max={$gym.length}></progress>
</li> <i>{gymProgress} / {$gym.length}</i>
<li> <button class="clickable gym" onclick={() => navigate(1)}>+</button>
<button onclick={() => navigate(2)}> </div>
<i>{cardioProgress}</i> / {$cardio.length} <h2>Konditionsträning, 3d/v under 7 veckor</h2>
<span>löppass lufsade</span> <div class="progress-row">
</button> <progress class="cardio" value={cardioProgress} max={$cardio.length}
</li> ></progress>
<li> <i>{cardioProgress} / {$cardio.length}</i>
<button onclick={() => navigate(3)}> <button class="clickable cardio" onclick={() => navigate(2)}>+</button>
<i>{dietProgress}</i> / {$diet.days.length} </div>
<span>dagar på diet</span> <h2>Diet, feb&ndash;maj</h2>
</button> <div class="progress-row">
</li> <progress class="diet" value={dietProgress} max={dietTotal}></progress>
</ul> <i>{dietProgress} / {dietTotal}</i>
</div> <button class="clickable diet" onclick={() => navigate(3)}>+</button>
</div>
<h2>Fasta, 3d/v under maj</h2>
<div class="progress-row">
<progress class="fast" value={fastProgress} max={$fast.length}></progress>
<i>{fastProgress} / {$fast.length}</i>
<button class="clickable fast" onclick={() => navigate(5)}>+</button>
</div>
</div>
<div class="top-button">
<button class="clickable info" onclick={() => navigate(4)}> Va? </button>
</div>
<p class="madeby">Skapad av <a href="https://madr.se">Anders</a></p>
</main>
<style> <style>
.cards { .madeby {
list-style: none; text-align: center;
display: grid; font-size: 0.75em;
grid-template-columns: 1fr 1fr; }
gap: 1em;
> li { main {
flex: 1; background: #222;
border: 2px solid #aaa; color: #fff;
border-radius: 5px; }
padding: 0;
button { progress {
padding: 1em; &.cardio {
} --pcolor: #5dc5f8;
i {
font-size: 2em;
font-style: normal;
}
span {
display: block;
}
}
} }
&.diet {
--pcolor: #35d450;
}
&.gym {
--pcolor: #f62b5a;
}
&.fast {
--pcolor: #ff00ff;
}
}
progress,
::-webkit-progress-bar {
-webkit-appearance: none;
appearance: none;
height: var(--h);
display: block;
width: calc(100% - 2rem);
border: none;
padding: 0;
background-color: rgba(128, 128, 128, 0.25);
margin: 0 1rem;
border-radius: var(--br);
}
::-webkit-progress-bar {
margin-right: 0;
margin-left: 0;
width: 100%;
}
/*
Nope, cannot comma separate selector like this:
::-moz-progress-bar,
::-webkit-progress-value { ... }
or even like this:
progress::-webkit-progress-value { ... }
Bug, closed with wontfix in 2015:
https://issues.chromium.org/issues/40564916
Sincerely, FUCK YOU, Chrome dev team.
*/
::-moz-progress-bar {
background-color: var(--pcolor);
background-image: linear-gradient(
rgba(255, 255, 255, 0.25) 0,
rgba(255, 255, 255, 0.25) 4px,
var(--pcolor) 4px,
var(--pcolor)
);
border: 0.25rem solid var(--pcolor);
border-width: 0.25rem 0.5rem;
border-radius: var(--br);
}
::-webkit-progress-value {
background-color: var(--pcolor);
background-image: linear-gradient(
rgba(255, 255, 255, 0.25) 0,
rgba(255, 255, 255, 0.25) 4px,
var(--pcolor) 4px,
var(--pcolor)
);
border: 0.25rem solid var(--pcolor);
border-width: 0.25rem 0.5rem;
border-radius: var(--br);
}
.progress {
--pcolor: #f00;
--h: 1.66rem;
--br: calc(var(--h) / 2);
--mg: 0.75rem;
--border: 3px solid #444;
margin: 2rem 0;
border: var(--border);
border-radius: 1rem;
padding-bottom: var(--mg);
> h2 {
padding: 0 1rem;
margin: var(--mg) 0;
font-size: 1em;
line-height: 1.25;
* + & {
border-top: var(--border);
padding-top: var(--mg);
}
}
}
.progress-row {
position: relative;
> i {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-style: normal;
font-size: 0.9em;
font-weight: bold;
pointer-events: none;
}
> button {
position: absolute;
top: 50%;
right: 0.5rem;
transform: translateY(-50%);
color: #fff;
background-color: var(--btn-bg);
padding: 0.25em 0.66em;
&.cardio {
--btn-bg: #5dc5f8;
}
&.diet {
--btn-bg: #35d450;
}
&.gym {
--btn-bg: #f62b5a;
}
&.fast {
--btn-bg: #ff00ff;
}
}
}
</style> </style>

25
src/lib/common.ts Normal file
View file

@ -0,0 +1,25 @@
import { currentView } from "./store";
const M = [
"jan",
"feb",
"mar",
"apr",
"maj",
"jun",
"jul",
"aug",
"sep",
"okt",
"nov",
"dec",
];
export const back = () => {
currentView.update((_) => 0);
};
export const dm = (date) => {
const d = new Date(date);
return `${d.getDate()} ${M[d.getMonth()]}`;
};

View file

@ -7,6 +7,7 @@ export const currentView = writable(0);
export const cardio = persisted("cardio", defaultState.cardio); export const cardio = persisted("cardio", defaultState.cardio);
export const gym = persisted("gym", defaultState.gym); export const gym = persisted("gym", defaultState.gym);
export const diet = persisted("diet", defaultState.diet); export const diet = persisted("diet", defaultState.diet);
export const fast = persisted("fast", defaultState.fast);
export type Exercise = { export type Exercise = {
completed: boolean; completed: boolean;

31
src/lib/svg-path.ts Normal file
View file

@ -0,0 +1,31 @@
export default function () {
const commands: string[] = [];
return {
stringify() {
return `${commands.join("\n")}`;
},
arcTo(
ry: number,
rx: number,
long: boolean,
cw: boolean,
y: number,
x: number,
) {
commands.push(`A ${rx} ${ry} 0 ${long ? 1 : 0} ${cw ? 1 : 0} ${x} ${y}`);
return this;
},
moveTo(y: number, x: number) {
commands.push(`M ${x} ${y}`);
return this;
},
lineTo(y: number, x: number) {
commands.push(`L ${x} ${y}`);
return this;
},
close() {
commands.push("z");
return this;
},
};
}

View file

@ -1,9 +1,9 @@
import { mount } from 'svelte' import { mount } from "svelte";
import './app.css' import "./app.css";
import App from './App.svelte' import App from "./App.svelte";
const app = mount(App, { const app = mount(App, {
target: document.getElementById('app')!, target: document.getElementById("app")!,
}) });
export default app export default app;

View file

@ -1,7 +1,38 @@
import { defineConfig } from 'vite' import { defineConfig } from "vite";
import { svelte } from '@sveltejs/vite-plugin-svelte' import { VitePWA } from "vite-plugin-pwa";
import { svelte } from "@sveltejs/vite-plugin-svelte";
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [svelte()], plugins: [
}) svelte(),
VitePWA({
injectRegister: "auto",
includeAssets: ["icon512_rounded.png", "icon512_maskable.png"],
manifest: {
theme_color: "#8936FF",
background_color: "#222",
icons: [
{
purpose: "maskable",
sizes: "512x512",
src: "icon512_maskable.png",
type: "image/png",
},
{
purpose: "any",
sizes: "512x512",
src: "icon512_rounded.png",
type: "image/png",
},
],
orientation: "portrait",
display: "standalone",
dir: "ltr",
lang: "sv-SE",
name: "Strand",
short_name: "strand",
},
}),
],
});