From 43fed81a62a2557991e658c67b06ec9068d5707f Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 20 Jul 2025 10:32:21 +0000 Subject: [PATCH] First commit --- app.js | 438 +++++++++++++++++++++ index.html | 130 +++++++ style.css | 1096 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1664 insertions(+) create mode 100644 app.js create mode 100644 index.html create mode 100644 style.css diff --git a/app.js b/app.js new file mode 100644 index 0000000..4874b66 --- /dev/null +++ b/app.js @@ -0,0 +1,438 @@ +// Application data from provided JSON +const recipeData = { + "recipe": { + "name": "Barista-Recommended Pour-Over", + "coffee_weight": "21g", + "water_weight": "340g", + "ratio": "1:16", + "water_temp": "94-96°C", + "grind": "Medium-fine", + "total_time": "3:30-4:00" + }, + "steps": [ + { + "id": 1, + "name": "Preparation", + "description": "Set up your equipment and ingredients", + "details": [ + "Grind 21g coffee to medium-fine consistency", + "Heat 340g filtered water to 94-96°C", + "Place metal filter in Chemex", + "Zero your scale with Chemex on it" + ], + "timer": 0, + "water_amount": "0g", + "icon": "🔧" + }, + { + "id": 2, + "name": "Bloom", + "description": "Pour water to bloom the coffee grounds", + "details": [ + "Add ground coffee to filter", + "Pour 50g water slowly over grounds", + "Start from center, spiral outward", + "Wait for CO2 to release" + ], + "timer": 45, + "water_amount": "50g", + "icon": "🌸" + }, + { + "id": 3, + "name": "Second Pour", + "description": "Continue pouring to 200g total", + "details": [ + "Pour slowly in circular motion", + "Avoid pouring near edges", + "Bring total weight to 200g", + "Maintain steady pour rate" + ], + "timer": 30, + "water_amount": "200g total", + "icon": "💧" + }, + { + "id": 4, + "name": "Final Pour", + "description": "Complete the pour to 340g total", + "details": [ + "Continue circular pouring motion", + "Pour remaining water to 340g total", + "Keep consistent height and speed", + "Finish all water by 2:30-3:00" + ], + "timer": 90, + "water_amount": "340g total", + "icon": "🫗" + }, + { + "id": 5, + "name": "Finish", + "description": "Complete the brewing process", + "details": [ + "Let coffee finish dripping", + "Remove filter when dripping stops", + "Give brew a gentle swirl", + "Serve immediately and enjoy!" + ], + "timer": 60, + "water_amount": "Complete", + "icon": "☕" + } + ] +}; + +// Application state +let currentStepIndex = 0; +let timerInterval = null; +let timeRemaining = 0; +let isTimerRunning = false; +let isTimerComplete = false; + +// DOM elements +const welcomeScreen = document.getElementById('welcome-screen'); +const brewingScreen = document.getElementById('brewing-screen'); +const completionScreen = document.getElementById('completion-screen'); + +// Screen management +function showScreen(screenName) { + // Hide all screens + document.querySelectorAll('.screen').forEach(screen => { + screen.classList.remove('active'); + }); + + // Show target screen + const targetScreen = document.getElementById(screenName + '-screen'); + if (targetScreen) { + targetScreen.classList.add('active'); + } +} + +// Start brewing process +function startBrewing() { + currentStepIndex = 0; + showScreen('brewing'); + loadCurrentStep(); +} + +// Load current step data +function loadCurrentStep() { + const step = recipeData.steps[currentStepIndex]; + const stepNumber = currentStepIndex + 1; + const totalSteps = recipeData.steps.length; + + // Update step indicator + document.getElementById('current-step').textContent = `Step ${stepNumber}`; + + // Update progress bar + const progressPercentage = (stepNumber / totalSteps) * 100; + document.getElementById('progress-fill').style.width = `${progressPercentage}%`; + + // Update step content + document.getElementById('step-icon').textContent = step.icon; + document.getElementById('step-title').textContent = step.name; + document.getElementById('step-description').textContent = step.description; + document.getElementById('water-amount').textContent = step.water_amount; + + // Update step details + const detailsList = document.getElementById('step-details'); + detailsList.innerHTML = ''; + step.details.forEach(detail => { + const li = document.createElement('li'); + li.textContent = detail; + detailsList.appendChild(li); + }); + + // Handle timer visibility and setup + const timerSection = document.getElementById('timer-section'); + const nextStepBtn = document.getElementById('next-step-btn'); + + if (step.timer > 0) { + // Show timer for timed steps + timerSection.style.display = 'block'; + timeRemaining = step.timer; + isTimerComplete = false; + updateTimerDisplay(); + nextStepBtn.textContent = 'Next Step'; + nextStepBtn.style.display = 'none'; // Hide until timer completes + + // Reset timer controls + resetTimerControls(); + } else { + // Hide timer for preparation step + timerSection.style.display = 'none'; + nextStepBtn.textContent = 'Ready'; + nextStepBtn.style.display = 'block'; + } + + // Update water indicator visibility + const waterIndicator = document.getElementById('water-indicator'); + if (step.water_amount !== '0g') { + waterIndicator.style.display = 'flex'; + } else { + waterIndicator.style.display = 'none'; + } +} + +// Timer functionality +function updateTimerDisplay() { + const minutes = Math.floor(timeRemaining / 60); + const seconds = timeRemaining % 60; + const display = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + document.getElementById('timer-display').textContent = display; +} + +function toggleTimer() { + if (isTimerRunning) { + pauseTimer(); + } else { + startTimer(); + } +} + +function startTimer() { + if (timeRemaining <= 0 && !isTimerComplete) { + timeRemaining = recipeData.steps[currentStepIndex].timer; + } + + isTimerRunning = true; + document.getElementById('play-pause-text').textContent = '⏸'; + + timerInterval = setInterval(() => { + timeRemaining--; + updateTimerDisplay(); + + if (timeRemaining <= 0) { + completeTimer(); + } + }, 1000); +} + +function pauseTimer() { + isTimerRunning = false; + document.getElementById('play-pause-text').textContent = '▶'; + clearInterval(timerInterval); +} + +function resetTimer() { + pauseTimer(); + timeRemaining = recipeData.steps[currentStepIndex].timer; + isTimerComplete = false; + updateTimerDisplay(); + resetTimerControls(); + + // Hide next step button until timer completes again + const nextStepBtn = document.getElementById('next-step-btn'); + nextStepBtn.style.display = 'none'; +} + +function completeTimer() { + pauseTimer(); + isTimerComplete = true; + + // Visual feedback + const timerDisplay = document.getElementById('timer-display'); + timerDisplay.classList.add('timer-complete'); + + // Audio notification + playNotificationSound(); + + // Show next step button + const nextStepBtn = document.getElementById('next-step-btn'); + nextStepBtn.style.display = 'block'; + nextStepBtn.textContent = 'Next Step'; + + // Remove visual feedback after animation + setTimeout(() => { + timerDisplay.classList.remove('timer-complete'); + }, 3000); + + // Auto-advance after a short delay + setTimeout(() => { + if (currentStepIndex < recipeData.steps.length - 1) { + nextStep(); + } + }, 2000); +} + +function resetTimerControls() { + document.getElementById('play-pause-text').textContent = '▶'; + const nextStepBtn = document.getElementById('next-step-btn'); + if (recipeData.steps[currentStepIndex].timer > 0) { + nextStepBtn.style.display = 'none'; + } +} + +// Audio notification +function playNotificationSound() { + // Create a simple beep sound using Web Audio API + try { + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const oscillator = audioContext.createOscillator(); + const gainNode = audioContext.createGain(); + + oscillator.connect(gainNode); + gainNode.connect(audioContext.destination); + + oscillator.frequency.setValueAtTime(800, audioContext.currentTime); + gainNode.gain.setValueAtTime(0, audioContext.currentTime); + gainNode.gain.linearRampToValueAtTime(0.3, audioContext.currentTime + 0.1); + gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + 0.3); + + oscillator.start(audioContext.currentTime); + oscillator.stop(audioContext.currentTime + 0.3); + } catch (error) { + // Fallback: show visual notification if audio fails + console.log('Timer complete!'); + } +} + +// Step navigation +function nextStep() { + // If current step has a timer and it's not complete, start the timer + const currentStep = recipeData.steps[currentStepIndex]; + if (currentStep.timer > 0 && !isTimerComplete && !isTimerRunning) { + startTimer(); + return; + } + + // Move to next step + currentStepIndex++; + + if (currentStepIndex >= recipeData.steps.length) { + // Brewing complete + showScreen('completion'); + } else { + // Load next step + loadCurrentStep(); + } +} + +// Completion screen functions +function restartBrewing() { + currentStepIndex = 0; + pauseTimer(); + timeRemaining = 0; + isTimerComplete = false; + showScreen('brewing'); + loadCurrentStep(); +} + +function goToWelcome() { + currentStepIndex = 0; + pauseTimer(); + timeRemaining = 0; + isTimerComplete = false; + showScreen('welcome'); +} + +// Keyboard shortcuts +document.addEventListener('keydown', (event) => { + if (event.code === 'Space') { + event.preventDefault(); + const currentScreen = document.querySelector('.screen.active'); + + if (currentScreen && currentScreen.id === 'brewing-screen') { + const currentStep = recipeData.steps[currentStepIndex]; + if (currentStep.timer > 0) { + toggleTimer(); + } + } + } + + if (event.key === 'Enter') { + const currentScreen = document.querySelector('.screen.active'); + + if (currentScreen && currentScreen.id === 'welcome-screen') { + startBrewing(); + } else if (currentScreen && currentScreen.id === 'brewing-screen') { + nextStep(); + } + } + + if (event.key === 'Escape') { + const currentScreen = document.querySelector('.screen.active'); + + if (currentScreen && currentScreen.id !== 'welcome-screen') { + goToWelcome(); + } + } +}); + +// Initialize app +document.addEventListener('DOMContentLoaded', () => { + // Show welcome screen by default + showScreen('welcome'); + + // Add some helpful console instructions + console.log('☕ Pour-Over Coffee Timer'); + console.log('Keyboard shortcuts:'); + console.log('- Space: Start/pause timer'); + console.log('- Enter: Start brewing or next step'); + console.log('- Escape: Return to welcome screen'); +}); + +// Prevent accidental page refresh during brewing +window.addEventListener('beforeunload', (event) => { + const currentScreen = document.querySelector('.screen.active'); + if (currentScreen && currentScreen.id === 'brewing-screen') { + event.preventDefault(); + event.returnValue = 'Are you sure you want to leave? Your brewing progress will be lost.'; + return event.returnValue; + } +}); + +// Handle visibility change (when user switches tabs) +document.addEventListener('visibilitychange', () => { + if (document.hidden && isTimerRunning) { + // Optionally pause timer when tab is not visible + // pauseTimer(); + } +}); + +// Responsive handling for timer display +function updateTimerSize() { + const timerDisplay = document.getElementById('timer-display'); + const containerWidth = timerDisplay.parentElement.offsetWidth; + + if (containerWidth < 400) { + timerDisplay.style.fontSize = '3rem'; + } else { + timerDisplay.style.fontSize = '4rem'; + } +} + +// Call on resize +window.addEventListener('resize', updateTimerSize); + +// Accessibility enhancements +function announceStepChange() { + const step = recipeData.steps[currentStepIndex]; + const announcement = `Step ${currentStepIndex + 1}: ${step.name}. ${step.description}`; + + // Create and use aria-live region for screen reader announcements + let liveRegion = document.getElementById('aria-live-region'); + if (!liveRegion) { + liveRegion = document.createElement('div'); + liveRegion.id = 'aria-live-region'; + liveRegion.setAttribute('aria-live', 'polite'); + liveRegion.setAttribute('aria-atomic', 'true'); + liveRegion.style.position = 'absolute'; + liveRegion.style.left = '-10000px'; + liveRegion.style.width = '1px'; + liveRegion.style.height = '1px'; + liveRegion.style.overflow = 'hidden'; + document.body.appendChild(liveRegion); + } + + liveRegion.textContent = announcement; +} + +// Export functions for global access (for onclick handlers) +window.startBrewing = startBrewing; +window.nextStep = nextStep; +window.toggleTimer = toggleTimer; +window.resetTimer = resetTimer; +window.restartBrewing = restartBrewing; +window.goToWelcome = goToWelcome; \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..b08b0e2 --- /dev/null +++ b/index.html @@ -0,0 +1,130 @@ + + + + + + Pour-Over Coffee Timer + + + +
+ +
+
+
+

Pour-Over Coffee Timer

+

Barista-Recommended Recipe

+
+ +
+
+
+ Coffee + 21g +
+
+ Water + 340g +
+
+ Ratio + 1:16 +
+
+ Time + 3:30-4:00 +
+
+ +
+

Water Temperature: 94-96°C

+

Grind Size: Medium-fine

+
+
+ + +
+ + +
+
+
+ Step 1 of 5 +
+
+
+
+
+ +
+
🔧
+

Preparation

+

Set up your equipment and ingredients

+ +
+ Water Amount + 0g +
+ +
    +
  • Grind 21g coffee to medium-fine consistency
  • +
  • Heat 340g filtered water to 94-96°C
  • +
  • Place metal filter in Chemex
  • +
  • Zero your scale with Chemex on it
  • +
+ + + + +
+
+ + +
+
+
🎉
+

Perfect Brew!

+

Your pour-over coffee is ready to enjoy.

+ +
+

Brewing Complete

+

You've successfully completed the barista-recommended pour-over process.

+
+

Pro Tips:

+
    +
  • Give your brew a gentle swirl before serving
  • +
  • Serve immediately for best flavor
  • +
  • Clean your equipment while the coffee is hot
  • +
+
+
+ +
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..d7a7670 --- /dev/null +++ b/style.css @@ -0,0 +1,1096 @@ + +:root { + /* Colors */ + --color-background: rgba(252, 252, 249, 1); + --color-surface: rgba(255, 255, 253, 1); + --color-text: rgba(19, 52, 59, 1); + --color-text-secondary: rgba(98, 108, 113, 1); + --color-primary: rgba(33, 128, 141, 1); + --color-primary-hover: rgba(29, 116, 128, 1); + --color-primary-active: rgba(26, 104, 115, 1); + --color-secondary: rgba(94, 82, 64, 0.12); + --color-secondary-hover: rgba(94, 82, 64, 0.2); + --color-secondary-active: rgba(94, 82, 64, 0.25); + --color-border: rgba(94, 82, 64, 0.2); + --color-btn-primary-text: rgba(252, 252, 249, 1); + --color-card-border: rgba(94, 82, 64, 0.12); + --color-card-border-inner: rgba(94, 82, 64, 0.12); + --color-error: rgba(192, 21, 47, 1); + --color-success: rgba(33, 128, 141, 1); + --color-warning: rgba(168, 75, 47, 1); + --color-info: rgba(98, 108, 113, 1); + --color-focus-ring: rgba(33, 128, 141, 0.4); + --color-select-caret: rgba(19, 52, 59, 0.8); + + /* Common style patterns */ + --focus-ring: 0 0 0 3px var(--color-focus-ring); + --focus-outline: 2px solid var(--color-primary); + --status-bg-opacity: 0.15; + --status-border-opacity: 0.25; + --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + + /* RGB versions for opacity control */ + --color-success-rgb: 33, 128, 141; + --color-error-rgb: 192, 21, 47; + --color-warning-rgb: 168, 75, 47; + --color-info-rgb: 98, 108, 113; + + /* Typography */ + --font-family-base: "FKGroteskNeue", "Geist", "Inter", -apple-system, + BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --font-family-mono: "Berkeley Mono", ui-monospace, SFMono-Regular, Menlo, + Monaco, Consolas, monospace; + --font-size-xs: 11px; + --font-size-sm: 12px; + --font-size-base: 14px; + --font-size-md: 14px; + --font-size-lg: 16px; + --font-size-xl: 18px; + --font-size-2xl: 20px; + --font-size-3xl: 24px; + --font-size-4xl: 30px; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 550; + --font-weight-bold: 600; + --line-height-tight: 1.2; + --line-height-normal: 1.5; + --letter-spacing-tight: -0.01em; + + /* Spacing */ + --space-0: 0; + --space-1: 1px; + --space-2: 2px; + --space-4: 4px; + --space-6: 6px; + --space-8: 8px; + --space-10: 10px; + --space-12: 12px; + --space-16: 16px; + --space-20: 20px; + --space-24: 24px; + --space-32: 32px; + + /* Border Radius */ + --radius-sm: 6px; + --radius-base: 8px; + --radius-md: 10px; + --radius-lg: 12px; + --radius-full: 9999px; + + /* Shadows */ + --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.02); + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.04), + 0 2px 4px -1px rgba(0, 0, 0, 0.02); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.04), + 0 4px 6px -2px rgba(0, 0, 0, 0.02); + --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.15), + inset 0 -1px 0 rgba(0, 0, 0, 0.03); + + /* Animation */ + --duration-fast: 150ms; + --duration-normal: 250ms; + --ease-standard: cubic-bezier(0.16, 1, 0.3, 1); + + /* Layout */ + --container-sm: 640px; + --container-md: 768px; + --container-lg: 1024px; + --container-xl: 1280px; +} + +/* Dark mode colors */ +@media (prefers-color-scheme: dark) { + :root { + --color-background: rgba(31, 33, 33, 1); + --color-surface: rgba(38, 40, 40, 1); + --color-text: rgba(245, 245, 245, 1); + --color-text-secondary: rgba(167, 169, 169, 0.7); + --color-primary: rgba(50, 184, 198, 1); + --color-primary-hover: rgba(45, 166, 178, 1); + --color-primary-active: rgba(41, 150, 161, 1); + --color-secondary: rgba(119, 124, 124, 0.15); + --color-secondary-hover: rgba(119, 124, 124, 0.25); + --color-secondary-active: rgba(119, 124, 124, 0.3); + --color-border: rgba(119, 124, 124, 0.3); + --color-error: rgba(255, 84, 89, 1); + --color-success: rgba(50, 184, 198, 1); + --color-warning: rgba(230, 129, 97, 1); + --color-info: rgba(167, 169, 169, 1); + --color-focus-ring: rgba(50, 184, 198, 0.4); + --color-btn-primary-text: rgba(19, 52, 59, 1); + --color-card-border: rgba(119, 124, 124, 0.2); + --color-card-border-inner: rgba(119, 124, 124, 0.15); + --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.1), + inset 0 -1px 0 rgba(0, 0, 0, 0.15); + --button-border-secondary: rgba(119, 124, 124, 0.2); + --color-border-secondary: rgba(119, 124, 124, 0.2); + --color-select-caret: rgba(245, 245, 245, 0.8); + + /* Common style patterns - updated for dark mode */ + --focus-ring: 0 0 0 3px var(--color-focus-ring); + --focus-outline: 2px solid var(--color-primary); + --status-bg-opacity: 0.15; + --status-border-opacity: 0.25; + --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + + /* RGB versions for dark mode */ + --color-success-rgb: 50, 184, 198; + --color-error-rgb: 255, 84, 89; + --color-warning-rgb: 230, 129, 97; + --color-info-rgb: 167, 169, 169; + } +} + +/* Data attribute for manual theme switching */ +[data-color-scheme="dark"] { + --color-background: rgba(31, 33, 33, 1); + --color-surface: rgba(38, 40, 40, 1); + --color-text: rgba(245, 245, 245, 1); + --color-text-secondary: rgba(167, 169, 169, 0.7); + --color-primary: rgba(50, 184, 198, 1); + --color-primary-hover: rgba(45, 166, 178, 1); + --color-primary-active: rgba(41, 150, 161, 1); + --color-secondary: rgba(119, 124, 124, 0.15); + --color-secondary-hover: rgba(119, 124, 124, 0.25); + --color-secondary-active: rgba(119, 124, 124, 0.3); + --color-border: rgba(119, 124, 124, 0.3); + --color-error: rgba(255, 84, 89, 1); + --color-success: rgba(50, 184, 198, 1); + --color-warning: rgba(230, 129, 97, 1); + --color-info: rgba(167, 169, 169, 1); + --color-focus-ring: rgba(50, 184, 198, 0.4); + --color-btn-primary-text: rgba(19, 52, 59, 1); + --color-card-border: rgba(119, 124, 124, 0.15); + --color-card-border-inner: rgba(119, 124, 124, 0.15); + --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.1), + inset 0 -1px 0 rgba(0, 0, 0, 0.15); + --color-border-secondary: rgba(119, 124, 124, 0.2); + --color-select-caret: rgba(245, 245, 245, 0.8); + + /* Common style patterns - updated for dark mode */ + --focus-ring: 0 0 0 3px var(--color-focus-ring); + --focus-outline: 2px solid var(--color-primary); + --status-bg-opacity: 0.15; + --status-border-opacity: 0.25; + --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + + /* RGB versions for dark mode */ + --color-success-rgb: 50, 184, 198; + --color-error-rgb: 255, 84, 89; + --color-warning-rgb: 230, 129, 97; + --color-info-rgb: 167, 169, 169; +} + +[data-color-scheme="light"] { + --color-background: rgba(252, 252, 249, 1); + --color-surface: rgba(255, 255, 253, 1); + --color-text: rgba(19, 52, 59, 1); + --color-text-secondary: rgba(98, 108, 113, 1); + --color-primary: rgba(33, 128, 141, 1); + --color-primary-hover: rgba(29, 116, 128, 1); + --color-primary-active: rgba(26, 104, 115, 1); + --color-secondary: rgba(94, 82, 64, 0.12); + --color-secondary-hover: rgba(94, 82, 64, 0.2); + --color-secondary-active: rgba(94, 82, 64, 0.25); + --color-border: rgba(94, 82, 64, 0.2); + --color-btn-primary-text: rgba(252, 252, 249, 1); + --color-card-border: rgba(94, 82, 64, 0.12); + --color-card-border-inner: rgba(94, 82, 64, 0.12); + --color-error: rgba(192, 21, 47, 1); + --color-success: rgba(33, 128, 141, 1); + --color-warning: rgba(168, 75, 47, 1); + --color-info: rgba(98, 108, 113, 1); + --color-focus-ring: rgba(33, 128, 141, 0.4); + + /* RGB versions for light mode */ + --color-success-rgb: 33, 128, 141; + --color-error-rgb: 192, 21, 47; + --color-warning-rgb: 168, 75, 47; + --color-info-rgb: 98, 108, 113; +} + +/* Base styles */ +html { + font-size: var(--font-size-base); + font-family: var(--font-family-base); + line-height: var(--line-height-normal); + color: var(--color-text); + background-color: var(--color-background); + -webkit-font-smoothing: antialiased; + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +/* Typography */ +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; + font-weight: var(--font-weight-semibold); + line-height: var(--line-height-tight); + color: var(--color-text); + letter-spacing: var(--letter-spacing-tight); +} + +h1 { + font-size: var(--font-size-4xl); +} +h2 { + font-size: var(--font-size-3xl); +} +h3 { + font-size: var(--font-size-2xl); +} +h4 { + font-size: var(--font-size-xl); +} +h5 { + font-size: var(--font-size-lg); +} +h6 { + font-size: var(--font-size-md); +} + +p { + margin: 0 0 var(--space-16) 0; +} + +a { + color: var(--color-primary); + text-decoration: none; + transition: color var(--duration-fast) var(--ease-standard); +} + +a:hover { + color: var(--color-primary-hover); +} + +code, +pre { + font-family: var(--font-family-mono); + font-size: calc(var(--font-size-base) * 0.95); + background-color: var(--color-secondary); + border-radius: var(--radius-sm); +} + +code { + padding: var(--space-1) var(--space-4); +} + +pre { + padding: var(--space-16); + margin: var(--space-16) 0; + overflow: auto; + border: 1px solid var(--color-border); +} + +pre code { + background: none; + padding: 0; +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: var(--space-8) var(--space-16); + border-radius: var(--radius-base); + font-size: var(--font-size-base); + font-weight: 500; + line-height: 1.5; + cursor: pointer; + transition: all var(--duration-normal) var(--ease-standard); + border: none; + text-decoration: none; + position: relative; +} + +.btn:focus-visible { + outline: none; + box-shadow: var(--focus-ring); +} + +.btn--primary { + background: var(--color-primary); + color: var(--color-btn-primary-text); +} + +.btn--primary:hover { + background: var(--color-primary-hover); +} + +.btn--primary:active { + background: var(--color-primary-active); +} + +.btn--secondary { + background: var(--color-secondary); + color: var(--color-text); +} + +.btn--secondary:hover { + background: var(--color-secondary-hover); +} + +.btn--secondary:active { + background: var(--color-secondary-active); +} + +.btn--outline { + background: transparent; + border: 1px solid var(--color-border); + color: var(--color-text); +} + +.btn--outline:hover { + background: var(--color-secondary); +} + +.btn--sm { + padding: var(--space-4) var(--space-12); + font-size: var(--font-size-sm); + border-radius: var(--radius-sm); +} + +.btn--lg { + padding: var(--space-10) var(--space-20); + font-size: var(--font-size-lg); + border-radius: var(--radius-md); +} + +.btn--full-width { + width: 100%; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Form elements */ +.form-control { + display: block; + width: 100%; + padding: var(--space-8) var(--space-12); + font-size: var(--font-size-md); + line-height: 1.5; + color: var(--color-text); + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-base); + transition: border-color var(--duration-fast) var(--ease-standard), + box-shadow var(--duration-fast) var(--ease-standard); +} + +textarea.form-control { + font-family: var(--font-family-base); + font-size: var(--font-size-base); +} + +select.form-control { + padding: var(--space-8) var(--space-12); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-image: var(--select-caret-light); + background-repeat: no-repeat; + background-position: right var(--space-12) center; + background-size: 16px; + padding-right: var(--space-32); +} + +/* Add a dark mode specific caret */ +@media (prefers-color-scheme: dark) { + select.form-control { + background-image: var(--select-caret-dark); + } +} + +/* Also handle data-color-scheme */ +[data-color-scheme="dark"] select.form-control { + background-image: var(--select-caret-dark); +} + +[data-color-scheme="light"] select.form-control { + background-image: var(--select-caret-light); +} + +.form-control:focus { + border-color: var(--color-primary); + outline: var(--focus-outline); +} + +.form-label { + display: block; + margin-bottom: var(--space-8); + font-weight: var(--font-weight-medium); + font-size: var(--font-size-sm); +} + +.form-group { + margin-bottom: var(--space-16); +} + +/* Card component */ +.card { + background-color: var(--color-surface); + border-radius: var(--radius-lg); + border: 1px solid var(--color-card-border); + box-shadow: var(--shadow-sm); + overflow: hidden; + transition: box-shadow var(--duration-normal) var(--ease-standard); +} + +.card:hover { + box-shadow: var(--shadow-md); +} + +.card__body { + padding: var(--space-16); +} + +.card__header, +.card__footer { + padding: var(--space-16); + border-bottom: 1px solid var(--color-card-border-inner); +} + +/* Status indicators - simplified with CSS variables */ +.status { + display: inline-flex; + align-items: center; + padding: var(--space-6) var(--space-12); + border-radius: var(--radius-full); + font-weight: var(--font-weight-medium); + font-size: var(--font-size-sm); +} + +.status--success { + background-color: rgba( + var(--color-success-rgb, 33, 128, 141), + var(--status-bg-opacity) + ); + color: var(--color-success); + border: 1px solid + rgba(var(--color-success-rgb, 33, 128, 141), var(--status-border-opacity)); +} + +.status--error { + background-color: rgba( + var(--color-error-rgb, 192, 21, 47), + var(--status-bg-opacity) + ); + color: var(--color-error); + border: 1px solid + rgba(var(--color-error-rgb, 192, 21, 47), var(--status-border-opacity)); +} + +.status--warning { + background-color: rgba( + var(--color-warning-rgb, 168, 75, 47), + var(--status-bg-opacity) + ); + color: var(--color-warning); + border: 1px solid + rgba(var(--color-warning-rgb, 168, 75, 47), var(--status-border-opacity)); +} + +.status--info { + background-color: rgba( + var(--color-info-rgb, 98, 108, 113), + var(--status-bg-opacity) + ); + color: var(--color-info); + border: 1px solid + rgba(var(--color-info-rgb, 98, 108, 113), var(--status-border-opacity)); +} + +/* Container layout */ +.container { + width: 100%; + margin-right: auto; + margin-left: auto; + padding-right: var(--space-16); + padding-left: var(--space-16); +} + +@media (min-width: 640px) { + .container { + max-width: var(--container-sm); + } +} +@media (min-width: 768px) { + .container { + max-width: var(--container-md); + } +} +@media (min-width: 1024px) { + .container { + max-width: var(--container-lg); + } +} +@media (min-width: 1280px) { + .container { + max-width: var(--container-xl); + } +} + +/* Utility classes */ +.flex { + display: flex; +} +.flex-col { + flex-direction: column; +} +.items-center { + align-items: center; +} +.justify-center { + justify-content: center; +} +.justify-between { + justify-content: space-between; +} +.gap-4 { + gap: var(--space-4); +} +.gap-8 { + gap: var(--space-8); +} +.gap-16 { + gap: var(--space-16); +} + +.m-0 { + margin: 0; +} +.mt-8 { + margin-top: var(--space-8); +} +.mb-8 { + margin-bottom: var(--space-8); +} +.mx-8 { + margin-left: var(--space-8); + margin-right: var(--space-8); +} +.my-8 { + margin-top: var(--space-8); + margin-bottom: var(--space-8); +} + +.p-0 { + padding: 0; +} +.py-8 { + padding-top: var(--space-8); + padding-bottom: var(--space-8); +} +.px-8 { + padding-left: var(--space-8); + padding-right: var(--space-8); +} +.py-16 { + padding-top: var(--space-16); + padding-bottom: var(--space-16); +} +.px-16 { + padding-left: var(--space-16); + padding-right: var(--space-16); +} + +.block { + display: block; +} +.hidden { + display: none; +} + +/* Accessibility */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +:focus-visible { + outline: var(--focus-outline); + outline-offset: 2px; +} + +/* Dark mode specifics */ +[data-color-scheme="dark"] .btn--outline { + border: 1px solid var(--color-border-secondary); +} + +@font-face { + font-family: 'FKGroteskNeue'; + src: url('https://r2cdn.perplexity.ai/fonts/FKGroteskNeue.woff2') + format('woff2'); +} + +/* END PERPLEXITY DESIGN SYSTEM */ +/* Coffee-inspired color overrides */ +:root { + /* Coffee-themed colors */ + --coffee-dark: #6B3410; + --coffee-medium: #8B4513; + --coffee-light: #A0522D; + --coffee-cream: #F5F5DC; + --coffee-amber: #D2691E; + --coffee-white: #FEFEFE; + --coffee-warm-bg: #FDF8F3; + + /* Override design system colors with coffee theme */ + --color-primary: var(--coffee-medium); + --color-primary-hover: var(--coffee-dark); + --color-primary-active: var(--coffee-dark); + --color-background: var(--coffee-warm-bg); + --color-surface: var(--coffee-white); + --color-text: var(--coffee-dark); + --color-text-secondary: var(--coffee-light); + --color-border: rgba(139, 69, 19, 0.15); + --color-card-border: rgba(139, 69, 19, 0.1); + --color-secondary: rgba(245, 245, 220, 0.6); + --color-secondary-hover: rgba(245, 245, 220, 0.8); + + /* Custom coffee gradients */ + --coffee-gradient: linear-gradient(135deg, var(--coffee-medium), var(--coffee-amber)); + --coffee-gradient-light: linear-gradient(135deg, var(--coffee-cream), var(--coffee-white)); +} + +body { + background: var(--coffee-gradient-light); + min-height: 100vh; + font-family: var(--font-family-base); +} + +.container { + max-width: 480px; + margin: 0 auto; + padding: var(--space-20); + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; +} + +/* Screen management */ +.screen { + display: none; + width: 100%; + animation: fadeIn 0.5s ease-in-out; +} + +.screen.active { + display: block; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Welcome Screen */ +.coffee-header { + text-align: center; + margin-bottom: var(--space-32); +} + +.coffee-icon { + font-size: 4rem; + margin-bottom: var(--space-16); + animation: float 3s ease-in-out infinite; +} + +@keyframes float { + 0%, 100% { transform: translateY(0px); } + 50% { transform: translateY(-5px); } +} + +.coffee-header h1 { + font-size: var(--font-size-4xl); + color: var(--coffee-dark); + margin-bottom: var(--space-8); + font-weight: var(--font-weight-bold); +} + +.subtitle { + color: var(--coffee-light); + font-size: var(--font-size-lg); + margin: 0; +} + +.recipe-overview { + margin-bottom: var(--space-32); + background: var(--coffee-white); + border: 1px solid var(--color-card-border); + box-shadow: 0 4px 20px rgba(139, 69, 19, 0.1); +} + +.recipe-stats { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-16); + margin-bottom: var(--space-20); +} + +.stat { + text-align: center; + padding: var(--space-12); + background: var(--coffee-gradient-light); + border-radius: var(--radius-base); + border: 1px solid rgba(139, 69, 19, 0.08); +} + +.stat-label { + display: block; + font-size: var(--font-size-sm); + color: var(--coffee-light); + margin-bottom: var(--space-4); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.stat-value { + display: block; + font-size: var(--font-size-xl); + font-weight: var(--font-weight-bold); + color: var(--coffee-dark); +} + +.recipe-details { + padding-top: var(--space-16); + border-top: 1px solid var(--color-border); +} + +.recipe-details p { + margin: var(--space-8) 0; + color: var(--coffee-medium); + font-size: var(--font-size-base); +} + +/* Brewing Screen */ +.brewing-header { + margin-bottom: var(--space-32); +} + +.step-indicator { + text-align: center; + font-size: var(--font-size-lg); + font-weight: var(--font-weight-medium); + color: var(--coffee-medium); + margin-bottom: var(--space-16); +} + +.progress-bar { + height: 6px; + background: rgba(139, 69, 19, 0.1); + border-radius: var(--radius-full); + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: var(--coffee-gradient); + border-radius: var(--radius-full); + transition: width 0.5s ease-in-out; + width: 0%; +} + +.step-content { + text-align: center; +} + +.step-icon { + font-size: 3rem; + margin-bottom: var(--space-16); + animation: bounce 2s infinite; +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } + 40% { transform: translateY(-5px); } + 60% { transform: translateY(-3px); } +} + +.step-content h2 { + font-size: var(--font-size-3xl); + color: var(--coffee-dark); + margin-bottom: var(--space-8); +} + +.step-content p { + color: var(--coffee-light); + font-size: var(--font-size-lg); + margin-bottom: var(--space-24); +} + +.water-indicator { + display: flex; + justify-content: space-between; + align-items: center; + background: var(--coffee-gradient-light); + padding: var(--space-16); + border-radius: var(--radius-lg); + margin-bottom: var(--space-24); + border: 1px solid rgba(139, 69, 19, 0.08); +} + +.water-label { + font-size: var(--font-size-base); + color: var(--coffee-medium); + font-weight: var(--font-weight-medium); +} + +.water-amount { + font-size: var(--font-size-xl); + font-weight: var(--font-weight-bold); + color: var(--coffee-dark); +} + +.step-details { + text-align: left; + background: var(--coffee-white); + padding: var(--space-20); + border-radius: var(--radius-lg); + margin-bottom: var(--space-24); + box-shadow: 0 2px 10px rgba(139, 69, 19, 0.05); + border: 1px solid var(--color-card-border); +} + +.step-details li { + margin: var(--space-12) 0; + color: var(--coffee-medium); + line-height: 1.6; + position: relative; + padding-left: var(--space-20); +} + +.step-details li::before { + content: "☕"; + position: absolute; + left: 0; + color: var(--coffee-amber); + font-size: var(--font-size-sm); +} + +/* Timer Section */ +.timer-section { + margin: var(--space-32) 0; + text-align: center; +} + +.timer-display { + font-size: 4rem; + font-weight: var(--font-weight-bold); + color: var(--coffee-dark); + margin-bottom: var(--space-20); + font-family: var(--font-family-mono); + background: var(--coffee-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-shadow: 0 2px 4px rgba(139, 69, 19, 0.1); +} + +.timer-controls { + display: flex; + gap: var(--space-16); + justify-content: center; + margin-bottom: var(--space-24); +} + +.timer-btn { + width: 60px; + height: 60px; + border-radius: var(--radius-full); + display: flex; + align-items: center; + justify-content: center; + font-size: var(--font-size-xl); + border: 2px solid var(--coffee-medium); + background: var(--coffee-white); + color: var(--coffee-medium); + transition: all var(--duration-normal) var(--ease-standard); +} + +.timer-btn:hover { + background: var(--coffee-medium); + color: var(--coffee-white); + transform: scale(1.05); +} + +.timer-btn:active { + transform: scale(0.95); +} + +/* Buttons */ +.btn--primary { + background: var(--coffee-gradient); + border: none; + color: var(--coffee-white); + font-weight: var(--font-weight-semibold); + text-transform: uppercase; + letter-spacing: 0.5px; + transition: all var(--duration-normal) var(--ease-standard); +} + +.btn--primary:hover { + transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(139, 69, 19, 0.3); +} + +.btn--outline { + border: 2px solid var(--coffee-medium); + color: var(--coffee-medium); + background: var(--coffee-white); +} + +.btn--outline:hover { + background: var(--coffee-medium); + color: var(--coffee-white); +} + +/* Completion Screen */ +.completion-content { + text-align: center; +} + +.completion-icon { + font-size: 4rem; + margin-bottom: var(--space-20); + animation: celebration 1s ease-in-out; +} + +@keyframes celebration { + 0% { transform: scale(0); } + 50% { transform: scale(1.2); } + 100% { transform: scale(1); } +} + +.completion-content h1 { + font-size: var(--font-size-4xl); + color: var(--coffee-dark); + margin-bottom: var(--space-12); +} + +.completion-message { + font-size: var(--font-size-lg); + color: var(--coffee-light); + margin-bottom: var(--space-32); +} + +.completion-stats { + text-align: left; + margin-bottom: var(--space-32); +} + +.completion-stats h3 { + color: var(--coffee-dark); + margin-bottom: var(--space-12); +} + +.completion-stats p { + color: var(--coffee-medium); + margin-bottom: var(--space-20); +} + +.completion-tips h4 { + color: var(--coffee-medium); + margin-bottom: var(--space-12); + font-size: var(--font-size-base); +} + +.completion-tips ul { + margin: 0; + padding-left: var(--space-20); +} + +.completion-tips li { + color: var(--coffee-light); + margin: var(--space-8) 0; + line-height: 1.5; +} + +.completion-actions { + display: flex; + flex-direction: column; + gap: var(--space-16); +} + +/* Responsive adjustments */ +@media (max-width: 480px) { + .container { + padding: var(--space-16); + } + + .coffee-header h1 { + font-size: var(--font-size-3xl); + } + + .timer-display { + font-size: 3rem; + } + + .completion-actions { + flex-direction: column; + } + + .recipe-stats { + grid-template-columns: repeat(2, 1fr); + gap: var(--space-12); + } +} + +/* Enhanced accessibility */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +/* Focus states for better accessibility */ +.btn:focus-visible, +.timer-btn:focus-visible { + outline: 3px solid var(--coffee-amber); + outline-offset: 2px; +} + +/* Alert styling for timer completion */ +.timer-complete { + animation: pulse 1s ease-in-out 3; +} + +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); } + 100% { transform: scale(1); } +} \ No newline at end of file