|
@@ -0,0 +1,339 @@
|
|
|
|
|
+<script>
|
|
|
|
|
+ import logoMark from '$lib/images/logo_white.svg';
|
|
|
|
|
+
|
|
|
|
|
+ const EXERCISE_TYPES = [
|
|
|
|
|
+ { value: 'weights', label: 'Weights' },
|
|
|
|
|
+ { value: 'bodyweight', label: 'Bodyweight' },
|
|
|
|
|
+ { value: 'timed', label: 'Timed' },
|
|
|
|
|
+ { value: 'distance', label: 'Distance' },
|
|
|
|
|
+ { value: 'stretch', label: 'Stretch' }
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ let nextKey = 1;
|
|
|
|
|
+
|
|
|
|
|
+ /** @returns {{ key: number, name: string, type: string }} */
|
|
|
|
|
+ function emptyExercise() {
|
|
|
|
|
+ return { key: nextKey++, name: '', type: 'weights' };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let title = $state('');
|
|
|
|
|
+ let exercises = $state([emptyExercise()]);
|
|
|
|
|
+
|
|
|
|
|
+ function addExercise() {
|
|
|
|
|
+ exercises = [...exercises, emptyExercise()];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param {number} key
|
|
|
|
|
+ */
|
|
|
|
|
+ function removeExercise(key) {
|
|
|
|
|
+ if (exercises.length <= 1) return;
|
|
|
|
|
+ exercises = exercises.filter((exercise) => exercise.key !== key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param {SubmitEvent} event
|
|
|
|
|
+ */
|
|
|
|
|
+ function handleSubmit(event) {
|
|
|
|
|
+ event.preventDefault();
|
|
|
|
|
+ }
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<svelte:head>
|
|
|
|
|
+ <title>New workout — Torus</title>
|
|
|
|
|
+</svelte:head>
|
|
|
|
|
+
|
|
|
|
|
+<div class="page">
|
|
|
|
|
+ <header class="top">
|
|
|
|
|
+ <a class="brand" href="/">
|
|
|
|
|
+ <img class="brand-mark" src={logoMark} alt="" width="28" height="28" />
|
|
|
|
|
+ <span class="brand-name">Torus</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ <div class="top-actions">
|
|
|
|
|
+ <a class="btn btn-ghost" href="/workouts">Back</a>
|
|
|
|
|
+ <a class="btn btn-ghost" href="/logout">Log out</a>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </header>
|
|
|
|
|
+
|
|
|
|
|
+ <main>
|
|
|
|
|
+ <section class="panel" aria-labelledby="new-workout-title">
|
|
|
|
|
+ <div class="panel-head">
|
|
|
|
|
+ <h1 id="new-workout-title">New workout</h1>
|
|
|
|
|
+ <p class="lede">Name the session, then add as many exercises as you need.</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <form class="form" method="POST" onsubmit={handleSubmit}>
|
|
|
|
|
+ <label class="field">
|
|
|
|
|
+ <span class="label">Workout name</span>
|
|
|
|
|
+ <input
|
|
|
|
|
+ class="input"
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ name="title"
|
|
|
|
|
+ required
|
|
|
|
|
+ placeholder="Push A"
|
|
|
|
|
+ bind:value={title}
|
|
|
|
|
+ />
|
|
|
|
|
+ </label>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="exercises" aria-label="Exercises">
|
|
|
|
|
+ <div class="exercises-head">
|
|
|
|
|
+ <h2>Exercises</h2>
|
|
|
|
|
+ <button type="button" class="btn btn-ghost" onclick={addExercise}>
|
|
|
|
|
+ Add exercise
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <ul class="exercise-list" role="list">
|
|
|
|
|
+ {#each exercises as exercise, index (exercise.key)}
|
|
|
|
|
+ <li class="exercise-row">
|
|
|
|
|
+ <p class="exercise-index">Exercise {index + 1}</p>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="exercise-fields">
|
|
|
|
|
+ <label class="field field-name">
|
|
|
|
|
+ <span class="label">Name</span>
|
|
|
|
|
+ <input
|
|
|
|
|
+ class="input"
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ name="exerciseName"
|
|
|
|
|
+ required
|
|
|
|
|
+ placeholder="Bench press"
|
|
|
|
|
+ bind:value={exercise.name}
|
|
|
|
|
+ />
|
|
|
|
|
+ </label>
|
|
|
|
|
+
|
|
|
|
|
+ <label class="field field-type">
|
|
|
|
|
+ <span class="label">Type</span>
|
|
|
|
|
+ <select class="input select" name="exerciseType" bind:value={exercise.type}>
|
|
|
|
|
+ {#each EXERCISE_TYPES as option}
|
|
|
|
|
+ <option value={option.value}>{option.label}</option>
|
|
|
|
|
+ {/each}
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </label>
|
|
|
|
|
+
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="btn btn-ghost remove"
|
|
|
|
|
+ disabled={exercises.length <= 1}
|
|
|
|
|
+ onclick={() => removeExercise(exercise.key)}
|
|
|
|
|
+ >
|
|
|
|
|
+ Remove
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ {/each}
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="form-actions">
|
|
|
|
|
+ <a class="btn btn-ghost" href="/workouts">Cancel</a>
|
|
|
|
|
+ <button class="btn btn-primary" type="submit">Create workout</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </main>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<style>
|
|
|
|
|
+ .page {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .top {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ padding: 16px 20px;
|
|
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .top-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .brand {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ letter-spacing: -0.02em;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .brand-mark {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ width: 28px;
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .brand-name {
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ main {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ width: min(720px, 100%);
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+ padding: 40px 20px 64px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .panel {
|
|
|
|
|
+ background: var(--surface);
|
|
|
|
|
+ border: 1px solid var(--border);
|
|
|
|
|
+ border-radius: var(--radius-lg);
|
|
|
|
|
+ padding: 24px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .panel-head {
|
|
|
|
|
+ margin-bottom: 24px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ h1 {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 1.6rem;
|
|
|
|
|
+ letter-spacing: -0.03em;
|
|
|
|
|
+ font-weight: 750;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ h2 {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 1rem;
|
|
|
|
|
+ letter-spacing: -0.01em;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .lede {
|
|
|
|
|
+ margin: 8px 0 0;
|
|
|
|
|
+ color: var(--text-muted);
|
|
|
|
|
+ line-height: 1.45;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .form {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ gap: 24px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .field {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .label {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 650;
|
|
|
|
|
+ color: var(--text-muted);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .input {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 11px 12px;
|
|
|
|
|
+ border: 1px solid var(--border);
|
|
|
|
|
+ border-radius: var(--radius-sm);
|
|
|
|
|
+ background: var(--surface-3);
|
|
|
|
|
+ color: var(--text);
|
|
|
|
|
+ font: inherit;
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .select {
|
|
|
|
|
+ appearance: none;
|
|
|
|
|
+ background-image: linear-gradient(45deg, transparent 50%, var(--ash) 50%),
|
|
|
|
|
+ linear-gradient(135deg, var(--ash) 50%, transparent 50%);
|
|
|
|
|
+ background-position:
|
|
|
|
|
+ calc(100% - 16px) calc(50% - 3px),
|
|
|
|
|
+ calc(100% - 11px) calc(50% - 3px);
|
|
|
|
|
+ background-size: 5px 5px;
|
|
|
|
|
+ background-repeat: no-repeat;
|
|
|
|
|
+ padding-right: 32px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .input:hover {
|
|
|
|
|
+ border-color: color-mix(in srgb, var(--border) 70%, var(--ash));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .input:focus {
|
|
|
|
|
+ outline: 2px solid var(--focus);
|
|
|
|
|
+ outline-offset: 2px;
|
|
|
|
|
+ border-color: color-mix(in srgb, var(--ember) 45%, var(--border));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .exercises-head {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .exercise-list {
|
|
|
|
|
+ list-style: none;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .exercise-row {
|
|
|
|
|
+ background: var(--surface-2);
|
|
|
|
|
+ border: 1px solid var(--border);
|
|
|
|
|
+ border-radius: var(--radius);
|
|
|
|
|
+ padding: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .exercise-index {
|
|
|
|
|
+ margin: 0 0 10px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ letter-spacing: 0.04em;
|
|
|
|
|
+ text-transform: uppercase;
|
|
|
|
|
+ color: var(--text-muted);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .exercise-fields {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .remove {
|
|
|
|
|
+ justify-self: start;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .remove:disabled {
|
|
|
|
|
+ opacity: 0.45;
|
|
|
|
|
+ cursor: not-allowed;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .form-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ padding-top: 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @media (min-width: 720px) {
|
|
|
|
|
+ .top {
|
|
|
|
|
+ padding: 16px 28px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ main {
|
|
|
|
|
+ padding: 48px 28px 80px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .panel {
|
|
|
|
|
+ padding: 28px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .exercise-fields {
|
|
|
|
|
+ grid-template-columns: minmax(0, 1fr) 180px auto;
|
|
|
|
|
+ align-items: end;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .remove {
|
|
|
|
|
+ justify-self: stretch;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+</style>
|