diff --git a/examples/tip-calculator/README.md b/examples/tip-calculator/README.md new file mode 100644 index 00000000..756444d1 --- /dev/null +++ b/examples/tip-calculator/README.md @@ -0,0 +1,83 @@ +# Tip Calculator App + +A simple tip calculator web application that demonstrates basic math operations, DOM manipulation, event handling, and input validation in JavaScript. + +## Features + +- **Bill Amount Input**: Enter the total bill amount +- **Tip Percentage Selection**: Choose from preset percentages (10%, 15%, 20%, 25%) or enter a custom percentage +- **Split Bill**: Divide the total among multiple people +- **Real-time Calculations**: Automatically calculates tip amount and total +- **Input Validation**: Ensures numeric inputs are valid +- **Animations**: Smooth transitions when updating results + +## How It Works + +### HTML Structure (`index.html`) +- Input field for bill amount +- Buttons for common tip percentages +- Custom tip percentage input +- Number of people input for bill splitting +- Display areas for tip amount, total amount, and per-person cost + +### CSS Styling (`style.css`) +- Modern, responsive design with gradient backgrounds +- Smooth animations and transitions +- Visual feedback for active buttons and input validation + +### JavaScript Functionality (`script.js`) + +#### Variable Management +```javascript +let billAmount = 0; +let tipPercentage = 20; // Default to 20% +let numPeople = 1; +``` + +#### Input Validation +The `validateNumericInput()` function checks if inputs are valid numbers within acceptable ranges: +- Bill amount must be >= 0 +- Custom tip percentage must be >= 0 +- Number of people must be >= 1 + +#### Tip Selection +- Preset buttons update the `tipPercentage` variable and highlight the active button +- Custom tip input overrides preset selection when entered + +#### Calculation Logic +The `calculateTip()` function performs the core math: +1. Validates bill amount input +2. Calculates tip amount: `tipAmount = (billAmount * tipPercentage) / 100` +3. Calculates total: `totalAmount = billAmount + tipAmount` +4. If splitting, calculates per-person cost: `amountPerPerson = totalAmount / numPeople` + +#### DOM Manipulation +- Results are updated with `animateResult()` function for smooth visual feedback +- Split bill section shows/hides based on number of people + +#### Event Handling +- Tip buttons: Update tip percentage and visual state +- Custom tip input: Override button selection +- Calculate button: Trigger calculations +- Input fields: Real-time validation + +## Usage + +1. Open `index.html` in a web browser +2. Enter the bill amount +3. Select a tip percentage or enter a custom one +4. Optionally, enter number of people to split the bill +5. Click "Calculate" to see results + +## Concepts Demonstrated + +- **Basic Math Operations**: Multiplication and division for tip calculations +- **DOM Manipulation**: Updating text content and element visibility +- **Event Listeners**: Handling user interactions (clicks, input changes) +- **Input Validation**: Ensuring user inputs are valid before calculations +- **CSS Animations**: Smooth transitions and visual feedback +- **Responsive Design**: Works on different screen sizes + +## Browser Compatibility + +Works in all modern browsers that support ES6 features. diff --git a/examples/tip-calculator/index.html b/examples/tip-calculator/index.html new file mode 100644 index 00000000..89674685 --- /dev/null +++ b/examples/tip-calculator/index.html @@ -0,0 +1,56 @@ + + + + + + Tip Calculator + + + +
+

Tip Calculator

+
+ + +
+ +
+ +
+ + + + +
+
+ +
+
+ +
+ + + people +
+ + + +
+
+ Tip Amount: + $0.00 +
+
+ Total Amount: + $0.00 +
+ +
+
+ + + + diff --git a/examples/tip-calculator/script.js b/examples/tip-calculator/script.js new file mode 100644 index 00000000..99ff011c --- /dev/null +++ b/examples/tip-calculator/script.js @@ -0,0 +1,114 @@ +// Get DOM elements +const billAmountInput = document.getElementById('bill-amount'); +const tipButtons = document.querySelectorAll('.tip-btn'); +const customTipInput = document.getElementById('custom-tip'); +const numPeopleInput = document.getElementById('num-people'); +const calculateBtn = document.getElementById('calculate-btn'); +const tipAmountSpan = document.getElementById('tip-amount'); +const totalAmountSpan = document.getElementById('total-amount'); +const perPersonDiv = document.getElementById('per-person'); +const amountPerPersonSpan = document.getElementById('amount-per-person'); + +// Variables to store current values +let billAmount = 0; +let tipPercentage = 20; // Default to 20% +let numPeople = 1; + +// Function to validate numeric input +function validateNumericInput(input, min = 0) { + const value = parseFloat(input.value); + if (isNaN(value) || value < min) { + input.style.borderColor = '#e74c3c'; + return false; + } else { + input.style.borderColor = '#ddd'; + return true; + } +} + +// Function to update tip percentage from buttons +function updateTipPercentage(percentage) { + tipPercentage = percentage; + customTipInput.value = ''; + + // Update active button + tipButtons.forEach(btn => { + btn.classList.remove('active'); + if (parseInt(btn.dataset.tip) === percentage) { + btn.classList.add('active'); + } + }); +} + +// Function to calculate tip and total +function calculateTip() { + if (!validateNumericInput(billAmountInput, 0)) { + alert('Please enter a valid bill amount.'); + return; + } + + billAmount = parseFloat(billAmountInput.value); + + // Check if custom tip is entered + if (customTipInput.value !== '') { + if (!validateNumericInput(customTipInput, 0)) { + alert('Please enter a valid tip percentage.'); + return; + } + tipPercentage = parseFloat(customTipInput.value); + tipButtons.forEach(btn => btn.classList.remove('active')); + } + + numPeople = parseInt(numPeopleInput.value) || 1; + + const tipAmount = (billAmount * tipPercentage) / 100; + const totalAmount = billAmount + tipAmount; + + // Animate result updates + animateResult(tipAmountSpan, tipAmount.toFixed(2)); + animateResult(totalAmountSpan, totalAmount.toFixed(2)); + + // Handle split bill + if (numPeople > 1) { + const amountPerPerson = totalAmount / numPeople; + perPersonDiv.style.display = 'block'; + animateResult(amountPerPersonSpan, amountPerPerson.toFixed(2)); + } else { + perPersonDiv.style.display = 'none'; + } +} + +// Function to animate result updates +function animateResult(element, newValue) { + element.textContent = '$' + newValue; + element.parentElement.classList.add('updated'); + setTimeout(() => { + element.parentElement.classList.remove('updated'); + }, 500); +} + +// Event listeners for tip buttons +tipButtons.forEach(button => { + button.addEventListener('click', () => { + updateTipPercentage(parseInt(button.dataset.tip)); + }); +}); + +// Event listener for custom tip input +customTipInput.addEventListener('input', () => { + if (customTipInput.value !== '') { + tipButtons.forEach(btn => btn.classList.remove('active')); + tipPercentage = parseFloat(customTipInput.value) || 0; + } +}); + +// Event listener for calculate button +calculateBtn.addEventListener('click', calculateTip); + +// Event listeners for real-time validation +billAmountInput.addEventListener('input', () => validateNumericInput(billAmountInput, 0)); +customTipInput.addEventListener('input', () => validateNumericInput(customTipInput, 0)); +numPeopleInput.addEventListener('input', () => validateNumericInput(numPeopleInput, 1)); + +// Initialize with default values +updateTipPercentage(20); diff --git a/examples/tip-calculator/style.css b/examples/tip-calculator/style.css new file mode 100644 index 00000000..3f8bb84d --- /dev/null +++ b/examples/tip-calculator/style.css @@ -0,0 +1,174 @@ +body { + font-family: 'Arial', sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + margin: 0; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; +} + +.calculator { + background: white; + border-radius: 15px; + padding: 30px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); + width: 100%; + max-width: 400px; + animation: fadeIn 0.5s ease-in-out; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(-20px); } + to { opacity: 1; transform: translateY(0); } +} + +h1 { + text-align: center; + color: #333; + margin-bottom: 30px; + font-size: 2.5em; +} + +.input-group, .tip-selection, .split-bill { + margin-bottom: 20px; +} + +label { + display: block; + margin-bottom: 8px; + font-weight: bold; + color: #555; +} + +input[type="number"] { + width: 100%; + padding: 12px; + border: 2px solid #ddd; + border-radius: 8px; + font-size: 16px; + transition: border-color 0.3s ease; + box-sizing: border-box; +} + +input[type="number"]:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 5px rgba(102, 126, 234, 0.3); +} + +input[type="number"]:invalid { + border-color: #e74c3c; +} + +.tip-buttons { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 10px; + margin-bottom: 15px; +} + +.tip-btn { + padding: 12px; + border: 2px solid #667eea; + background: white; + color: #667eea; + border-radius: 8px; + cursor: pointer; + font-size: 16px; + font-weight: bold; + transition: all 0.3s ease; +} + +.tip-btn:hover { + background: #667eea; + color: white; + transform: translateY(-2px); +} + +.tip-btn.active { + background: #667eea; + color: white; +} + +.custom-tip input { + width: calc(100% - 24px); + display: inline-block; +} + +.split-bill { + display: flex; + align-items: center; + gap: 10px; +} + +.split-bill input { + width: 80px; + flex-shrink: 0; +} + +.split-bill span { + color: #555; +} + +#calculate-btn { + width: 100%; + padding: 15px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 8px; + font-size: 18px; + font-weight: bold; + cursor: pointer; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +#calculate-btn:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); +} + +#calculate-btn:active { + transform: translateY(0); +} + +.results { + margin-top: 30px; + padding-top: 20px; + border-top: 2px solid #eee; +} + +.result-item { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + font-size: 18px; + padding: 10px; + background: #f8f9fa; + border-radius: 8px; + transition: all 0.3s ease; +} + +.result-item span:first-child { + font-weight: bold; + color: #555; +} + +.result-item span:last-child { + font-weight: bold; + color: #667eea; + font-size: 20px; +} + +.result-item.updated { + animation: highlight 0.5s ease; +} + +@keyframes highlight { + 0% { background: #f8f9fa; } + 50% { background: #e3f2fd; } + 100% { background: #f8f9fa; } +}