{{ zone.name }}
+{{ getHRZoneDescription(index) }}
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..c82f863
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,7 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(npm run lint:*)"
+ ]
+ }
+}
diff --git a/src/App.vue b/src/App.vue
index 48f8df3..2ad9e4c 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -9,6 +9,13 @@ export default {
\ No newline at end of file
diff --git a/src/components/PasswordReset.vue b/src/components/PasswordReset.vue
index 070ef9b..3213f94 100644
--- a/src/components/PasswordReset.vue
+++ b/src/components/PasswordReset.vue
@@ -178,8 +178,8 @@ function resetForm() {
flex-direction: column;
align-items: center;
justify-content: center;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- padding: 1rem;
+ background: var(--gradient-primary);
+ padding: var(--spacing-md);
position: relative;
overflow: hidden;
}
@@ -188,14 +188,14 @@ function resetForm() {
RESET CARD
============================================ */
.reset-card {
- background: rgba(255, 255, 255, 0.95);
- backdrop-filter: blur(20px);
- border-radius: 2rem;
+ background: var(--glass-background);
+ backdrop-filter: var(--glass-blur);
+ border-radius: var(--radius-2xl);
max-width: 480px;
width: 100%;
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
- border: 1px solid rgba(255, 255, 255, 0.2);
- animation: slideUp 0.6s cubic-bezier(0.4, 0, 0.2, 1);
+ box-shadow: var(--shadow-2xl);
+ border: 1px solid var(--glass-border);
+ animation: slideUp var(--transition-slower) cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
z-index: 10;
}
@@ -204,22 +204,22 @@ function resetForm() {
CARD HEADER
============================================ */
.card-header {
- padding: 2.5rem 2rem 1rem;
+ padding: var(--spacing-2xl) var(--spacing-xl) var(--spacing-md);
text-align: center;
}
.logo-wrapper {
width: 80px;
height: 80px;
- margin: 0 auto 1.5rem;
- padding: 1.5rem;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- border-radius: 2rem;
- color: white;
+ margin: 0 auto var(--spacing-lg);
+ padding: var(--spacing-lg);
+ background: var(--gradient-primary);
+ border-radius: var(--radius-2xl);
+ color: var(--color-text-inverse);
display: flex;
align-items: center;
justify-content: center;
- animation: iconBounce 0.6s ease-out 0.2s both;
+ animation: iconBounce var(--transition-slower) ease-out 0.2s both;
}
.logo-wrapper svg {
@@ -228,42 +228,42 @@ function resetForm() {
}
h1 {
- margin: 0 0 0.75rem 0;
- color: #1f2937;
- font-size: 2rem;
- font-weight: 800;
- animation: titleSlide 0.6s ease-out 0.3s both;
+ margin: 0 0 var(--spacing-sm) 0;
+ color: var(--color-text-primary);
+ font-size: var(--font-size-3xl);
+ font-weight: var(--font-weight-bold);
+ animation: titleSlide var(--transition-slower) ease-out 0.3s both;
}
.subtitle {
- color: #6b7280;
- font-size: 1rem;
- line-height: 1.6;
+ color: var(--color-text-secondary);
+ font-size: var(--font-size-base);
+ line-height: var(--line-height-relaxed);
margin: 0;
- animation: subtitleFade 0.6s ease-out 0.4s both;
+ animation: subtitleFade var(--transition-slower) ease-out 0.4s both;
}
/* ============================================
RESET FORM
============================================ */
.reset-form {
- padding: 1rem 2rem;
+ padding: var(--spacing-md) var(--spacing-xl);
display: flex;
flex-direction: column;
- gap: 1.5rem;
- animation: formSlide 0.6s ease-out 0.5s both;
+ gap: var(--spacing-lg);
+ animation: formSlide var(--transition-slower) ease-out 0.5s both;
}
.form-group {
display: flex;
flex-direction: column;
- gap: 0.75rem;
+ gap: var(--spacing-sm);
}
label {
- font-weight: 600;
- color: #374151;
- font-size: 0.95rem;
+ font-weight: var(--font-weight-semibold);
+ color: var(--color-text-secondary);
+ font-size: var(--font-size-sm);
}
.input-wrapper {
@@ -274,45 +274,46 @@ label {
input {
width: 100%;
- padding: 1rem 1rem 1rem 3rem;
- border: 2px solid #e5e7eb;
- border-radius: 1rem;
- font-size: 1rem;
+ padding: var(--spacing-md) var(--spacing-md) var(--spacing-md) 3rem;
+ border: 2px solid var(--color-border);
+ border-radius: var(--radius-lg);
+ font-size: var(--font-size-base);
font-family: inherit;
- transition: all 0.3s ease;
- background: white;
+ transition: all var(--transition-slow);
+ background: var(--color-surface);
+ color: var(--color-text-primary);
box-sizing: border-box;
}
input:focus {
outline: none;
- border-color: #667eea;
+ border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
transform: translateY(-2px);
}
input:disabled {
- background: #f9fafb;
+ background: var(--color-surface-hover);
cursor: not-allowed;
opacity: 0.6;
}
input::placeholder {
- color: #9ca3af;
+ color: var(--color-text-tertiary);
}
.input-icon {
position: absolute;
- left: 1rem;
+ left: var(--spacing-md);
width: 20px;
height: 20px;
- color: #9ca3af;
+ color: var(--color-text-tertiary);
pointer-events: none;
}
.helper-text {
- font-size: 0.8rem;
- color: #6b7280;
+ font-size: var(--font-size-xs);
+ color: var(--color-text-secondary);
margin: 0;
}
@@ -321,16 +322,16 @@ input::placeholder {
============================================ */
.btn-primary {
width: 100%;
- padding: 1.125rem;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
+ padding: var(--spacing-md);
+ background: var(--gradient-primary);
+ color: var(--color-text-inverse);
border: none;
- border-radius: 1rem;
- font-size: 1rem;
- font-weight: 700;
+ border-radius: var(--radius-lg);
+ font-size: var(--font-size-base);
+ font-weight: var(--font-weight-bold);
cursor: pointer;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
+ transition: all var(--transition-slow);
+ box-shadow: var(--shadow-md);
display: flex;
align-items: center;
justify-content: center;
@@ -339,7 +340,7 @@ input::placeholder {
.btn-primary:hover:not(:disabled) {
transform: translateY(-3px);
- box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
+ box-shadow: var(--shadow-lg);
}
.btn-primary:active:not(:disabled) {
@@ -355,7 +356,7 @@ input::placeholder {
.loading-content {
display: flex;
align-items: center;
- gap: 0.75rem;
+ gap: var(--spacing-sm);
}
.spinner {
@@ -368,23 +369,23 @@ input::placeholder {
SUCCESS SECTION
============================================ */
.success-section {
- padding: 1rem 2rem 2rem;
+ padding: var(--spacing-md) var(--spacing-xl) var(--spacing-xl);
text-align: center;
- animation: formSlide 0.6s ease-out;
+ animation: formSlide var(--transition-slower) ease-out;
}
.check-animation {
width: 80px;
height: 80px;
- margin: 0 auto 1.5rem;
- padding: 1.5rem;
- background: linear-gradient(135deg, #10b981, #059669);
- border-radius: 50%;
- color: white;
+ margin: 0 auto var(--spacing-lg);
+ padding: var(--spacing-lg);
+ background: var(--gradient-secondary);
+ border-radius: var(--radius-full);
+ color: var(--color-text-inverse);
display: flex;
align-items: center;
justify-content: center;
- animation: checkBounce 0.6s ease-out;
+ animation: checkBounce var(--transition-slower) ease-out;
}
.check-animation svg {
@@ -393,27 +394,27 @@ input::placeholder {
}
.success-section h3 {
- margin: 0 0 1rem 0;
- color: #065f46;
- font-size: 1.5rem;
- font-weight: 700;
+ margin: 0 0 var(--spacing-md) 0;
+ color: var(--color-secondary);
+ font-size: var(--font-size-2xl);
+ font-weight: var(--font-weight-bold);
}
.success-message {
- margin: 0.5rem 0;
- color: #6b7280;
- line-height: 1.6;
- font-size: 0.95rem;
+ margin: var(--spacing-sm) 0;
+ color: var(--color-text-secondary);
+ line-height: var(--line-height-relaxed);
+ font-size: var(--font-size-sm);
}
.email-display {
- color: #667eea !important;
- font-weight: 600;
- font-size: 1.1rem;
+ color: var(--color-primary) !important;
+ font-weight: var(--font-weight-semibold);
+ font-size: var(--font-size-lg);
background: rgba(102, 126, 234, 0.1);
- padding: 0.75rem;
- border-radius: 0.75rem;
- margin: 1rem 0 !important;
+ padding: var(--spacing-sm);
+ border-radius: var(--radius-md);
+ margin: var(--spacing-md) 0 !important;
word-break: break-all;
}
@@ -423,18 +424,18 @@ input::placeholder {
.instructions {
display: flex;
flex-direction: column;
- gap: 1rem;
- margin: 1.5rem 0;
+ gap: var(--spacing-md);
+ margin: var(--spacing-lg) 0;
text-align: left;
}
.instruction-step {
display: flex;
- gap: 1rem;
- padding: 1rem;
- background: #f8fafc;
- border-radius: 0.75rem;
- border-left: 4px solid #667eea;
+ gap: var(--spacing-md);
+ padding: var(--spacing-md);
+ background: var(--color-surface-hover);
+ border-radius: var(--radius-md);
+ border-left: 4px solid var(--color-primary);
}
.step-number {
@@ -444,23 +445,23 @@ input::placeholder {
display: flex;
align-items: center;
justify-content: center;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- border-radius: 50%;
- font-weight: 700;
- font-size: 1rem;
+ background: var(--gradient-primary);
+ color: var(--color-text-inverse);
+ border-radius: var(--radius-full);
+ font-weight: var(--font-weight-bold);
+ font-size: var(--font-size-base);
}
.step-content p {
margin: 0;
- color: #374151;
- font-size: 0.9rem;
- line-height: 1.4;
+ color: var(--color-text-secondary);
+ font-size: var(--font-size-sm);
+ line-height: var(--line-height-normal);
}
.step-content p:first-child {
- font-weight: 600;
- color: #1f2937;
+ font-weight: var(--font-weight-semibold);
+ color: var(--color-text-primary);
margin-bottom: 0.25rem;
}
@@ -469,46 +470,46 @@ input::placeholder {
============================================ */
.spam-notice {
display: flex;
- gap: 1rem;
- padding: 1rem;
- background: #fef3c7;
- border: 1px solid #fcd34d;
- border-radius: 0.75rem;
- margin: 1rem 0;
+ gap: var(--spacing-md);
+ padding: var(--spacing-md);
+ background: rgba(245, 158, 11, 0.1);
+ border: 1px solid var(--color-warning);
+ border-radius: var(--radius-md);
+ margin: var(--spacing-md) 0;
}
.spam-notice svg {
width: 20px;
height: 20px;
- color: #92400e;
+ color: var(--color-warning);
flex-shrink: 0;
margin-top: 2px;
}
.spam-notice p {
margin: 0;
- color: #92400e;
- font-size: 0.9rem;
- line-height: 1.5;
+ color: var(--color-warning);
+ font-size: var(--font-size-sm);
+ line-height: var(--line-height-normal);
}
.spam-notice strong {
- color: #78350f;
+ color: var(--color-warning);
}
/* ============================================
SECONDARY BUTTON
============================================ */
.btn-secondary {
- padding: 0.875rem 1.5rem;
+ padding: var(--spacing-sm) var(--spacing-lg);
background: rgba(102, 126, 234, 0.1);
- color: #667eea;
+ color: var(--color-primary);
border: 2px solid rgba(102, 126, 234, 0.2);
- border-radius: 0.75rem;
- font-weight: 600;
+ border-radius: var(--radius-md);
+ font-weight: var(--font-weight-semibold);
cursor: pointer;
- transition: all 0.3s ease;
- margin-top: 1rem;
+ transition: all var(--transition-slow);
+ margin-top: var(--spacing-md);
}
.btn-secondary:hover {
@@ -522,14 +523,14 @@ input::placeholder {
.error-message {
display: flex;
align-items: center;
- gap: 0.75rem;
- margin-top: 1rem;
- padding: 1rem;
+ gap: var(--spacing-sm);
+ margin-top: var(--spacing-md);
+ padding: var(--spacing-md);
background: #fef2f2;
border: 1px solid #fecaca;
- color: #dc2626;
- border-radius: 0.75rem;
- font-size: 0.9rem;
+ color: var(--color-danger);
+ border-radius: var(--radius-md);
+ font-size: var(--font-size-sm);
animation: shakeError 0.5s ease-out;
}
@@ -541,13 +542,13 @@ input::placeholder {
.error-title {
margin: 0 0 0.25rem 0;
- font-weight: 600;
- font-size: 0.95rem;
+ font-weight: var(--font-weight-semibold);
+ font-size: var(--font-size-sm);
}
.error-text {
margin: 0;
- font-size: 0.85rem;
+ font-size: var(--font-size-sm);
opacity: 0.9;
}
@@ -557,7 +558,7 @@ input::placeholder {
.divider {
position: relative;
text-align: center;
- margin: 2rem 0 1.5rem;
+ margin: var(--spacing-xl) 0 var(--spacing-lg);
z-index: 10;
}
@@ -573,11 +574,11 @@ input::placeholder {
.divider span {
position: relative;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- padding: 0 1rem;
- color: white;
- font-size: 0.9rem;
- font-weight: 500;
+ background: var(--gradient-primary);
+ padding: 0 var(--spacing-md);
+ color: var(--color-text-inverse);
+ font-size: var(--font-size-sm);
+ font-weight: var(--font-weight-medium);
}
/* ============================================
@@ -585,7 +586,7 @@ input::placeholder {
============================================ */
.links-section {
text-align: center;
- padding: 0 2rem 2rem;
+ padding: 0 var(--spacing-xl) var(--spacing-xl);
z-index: 10;
position: relative;
}
@@ -598,15 +599,15 @@ input::placeholder {
display: inline-flex;
align-items: center;
justify-content: center;
- gap: 0.5rem;
+ gap: var(--spacing-sm);
color: rgba(255, 255, 255, 0.9);
text-decoration: none;
- font-weight: 600;
- padding: 0.75rem 1.5rem;
- border-radius: 0.75rem;
+ font-weight: var(--font-weight-semibold);
+ padding: var(--spacing-sm) var(--spacing-lg);
+ border-radius: var(--radius-md);
background: rgba(255, 255, 255, 0.1);
- transition: all 0.2s ease;
- margin-bottom: 1rem;
+ transition: all var(--transition-base);
+ margin-bottom: var(--spacing-md);
}
.back-link svg {
@@ -620,33 +621,33 @@ input::placeholder {
}
.signup-prompt {
- margin-top: 1rem;
+ margin-top: var(--spacing-md);
}
.signup-prompt p {
- margin: 0 0 0.5rem 0;
+ margin: 0 0 var(--spacing-sm) 0;
color: rgba(255, 255, 255, 0.8);
- font-size: 0.95rem;
+ font-size: var(--font-size-sm);
}
.signup-link {
display: inline-flex;
align-items: center;
- gap: 0.5rem;
- color: white;
+ gap: var(--spacing-sm);
+ color: var(--color-text-inverse);
text-decoration: none;
- font-weight: 700;
- transition: all 0.2s ease;
+ font-weight: var(--font-weight-bold);
+ transition: all var(--transition-base);
}
.signup-link svg {
width: 16px;
height: 16px;
- transition: all 0.2s ease;
+ transition: all var(--transition-base);
}
.signup-link:hover {
- gap: 0.75rem;
+ gap: var(--spacing-sm);
text-decoration: underline;
}
@@ -662,7 +663,7 @@ input::placeholder {
.decoration-blob {
position: absolute;
- border-radius: 50%;
+ border-radius: var(--radius-full);
background: rgba(255, 255, 255, 0.1);
animation: float 8s ease-in-out infinite;
}
@@ -789,66 +790,66 @@ input::placeholder {
============================================ */
@media (max-width: 640px) {
.password-reset-container {
- padding: 1rem;
+ padding: var(--spacing-md);
}
.reset-card {
- border-radius: 1.5rem;
+ border-radius: var(--radius-xl);
}
.card-header {
- padding: 2rem 1.5rem 1rem;
+ padding: var(--spacing-xl) var(--spacing-lg) var(--spacing-md);
}
.reset-form {
- padding: 1rem 1.5rem;
+ padding: var(--spacing-md) var(--spacing-lg);
}
.success-section {
- padding: 1rem 1.5rem 1.5rem;
+ padding: var(--spacing-md) var(--spacing-lg) var(--spacing-lg);
}
.links-section {
- padding: 0 1.5rem 1.5rem;
+ padding: 0 var(--spacing-lg) var(--spacing-lg);
}
h1 {
- font-size: 1.75rem;
+ font-size: var(--font-size-3xl);
}
.subtitle {
- font-size: 0.95rem;
+ font-size: var(--font-size-sm);
}
.logo-wrapper {
width: 64px;
height: 64px;
- margin-bottom: 1rem;
- padding: 1rem;
+ margin-bottom: var(--spacing-md);
+ padding: var(--spacing-md);
}
.instructions {
- gap: 0.75rem;
+ gap: var(--spacing-sm);
}
.instruction-step {
- gap: 0.75rem;
- padding: 0.75rem;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-sm);
}
.step-number {
width: 32px;
height: 32px;
- font-size: 0.9rem;
+ font-size: var(--font-size-sm);
}
.step-content p {
- font-size: 0.85rem;
+ font-size: var(--font-size-sm);
}
.spam-notice {
- gap: 0.75rem;
- padding: 0.75rem;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-sm);
}
.spam-notice svg {
@@ -857,25 +858,25 @@ input::placeholder {
}
.spam-notice p {
- font-size: 0.85rem;
+ font-size: var(--font-size-sm);
}
.email-display {
- font-size: 1rem;
+ font-size: var(--font-size-base);
word-break: break-word;
}
input {
- padding: 0.875rem 0.875rem 0.875rem 2.75rem;
+ padding: var(--spacing-sm) var(--spacing-sm) var(--spacing-sm) 2.75rem;
font-size: 16px;
}
.input-icon {
- left: 0.75rem;
+ left: var(--spacing-sm);
}
.divider {
- margin: 1.5rem 0 1rem;
+ margin: var(--spacing-lg) 0 var(--spacing-md);
}
.divider::before {
@@ -885,11 +886,11 @@ input::placeholder {
.back-link {
width: 100%;
- margin-bottom: 1rem;
+ margin-bottom: var(--spacing-md);
}
.signup-prompt p {
- font-size: 0.9rem;
+ font-size: var(--font-size-sm);
}
.blob-1 {
@@ -910,28 +911,28 @@ input::placeholder {
@media (max-width: 480px) {
.reset-card {
- border-radius: 1.25rem;
+ border-radius: var(--radius-lg);
}
.card-header {
- padding: 1.5rem 1rem 0.75rem;
+ padding: var(--spacing-lg) var(--spacing-md) var(--spacing-sm);
}
.reset-form {
- padding: 0.75rem 1rem;
- gap: 1rem;
+ padding: var(--spacing-sm) var(--spacing-md);
+ gap: var(--spacing-md);
}
.success-section {
- padding: 0.75rem 1rem 1rem;
+ padding: var(--spacing-sm) var(--spacing-md) var(--spacing-md);
}
.links-section {
- padding: 0 1rem 1rem;
+ padding: 0 var(--spacing-md) var(--spacing-md);
}
h1 {
- font-size: 1.5rem;
+ font-size: var(--font-size-2xl);
}
.logo-wrapper {
@@ -942,28 +943,28 @@ input::placeholder {
.check-animation {
width: 64px;
height: 64px;
- margin-bottom: 1rem;
- padding: 1rem;
+ margin-bottom: var(--spacing-md);
+ padding: var(--spacing-md);
}
label {
- font-size: 0.9rem;
+ font-size: var(--font-size-sm);
}
.btn-primary {
- padding: 1rem;
- font-size: 0.95rem;
+ padding: var(--spacing-md);
+ font-size: var(--font-size-sm);
}
.btn-secondary {
- padding: 0.75rem 1rem;
- font-size: 0.9rem;
+ padding: var(--spacing-sm) var(--spacing-md);
+ font-size: var(--font-size-sm);
}
.error-message {
- gap: 0.5rem;
- padding: 0.75rem;
- font-size: 0.85rem;
+ gap: var(--spacing-xs);
+ padding: var(--spacing-sm);
+ font-size: var(--font-size-sm);
}
.error-message svg {
@@ -972,21 +973,21 @@ input::placeholder {
}
.divider span {
- font-size: 0.85rem;
+ font-size: var(--font-size-sm);
}
.signup-prompt {
- margin-top: 0.75rem;
+ margin-top: var(--spacing-sm);
}
.signup-prompt p {
- font-size: 0.85rem;
- margin-bottom: 0.375rem;
+ font-size: var(--font-size-sm);
+ margin-bottom: var(--spacing-xs);
}
.back-link {
- padding: 0.625rem 1rem;
- font-size: 0.9rem;
+ padding: var(--spacing-xs) var(--spacing-md);
+ font-size: var(--font-size-sm);
}
}
\ No newline at end of file
diff --git a/src/components/TrainingZones.vue b/src/components/TrainingZones.vue
index 9cb1d7c..e14d9d0 100644
--- a/src/components/TrainingZones.vue
+++ b/src/components/TrainingZones.vue
@@ -4,20 +4,67 @@
Your personalized training zones based on FTP and Max HR
+Your personalized training zones for optimal performance
Training zones are intensity ranges that help you train more effectively. Heart rate zones are based on your maximum heart rate, while power zones are calculated from your Functional Threshold Power (FTP). Training in different zones targets specific physiological adaptations to improve your cycling performance.
+Train at the right intensity based on your cardiovascular capacity. Each zone targets specific fitness adaptations.