diff --git a/02-counter/final/app.js b/02-counter/final/app.js index f34827948..537418c7a 100644 --- a/02-counter/final/app.js +++ b/02-counter/final/app.js @@ -3,6 +3,8 @@ let count = 0; // select value and buttons const value = document.querySelector("#value"); const btns = document.querySelectorAll(".btn"); +const themeToggle = document.querySelector("#theme-toggle"); +const body = document.body; btns.forEach(function (btn) { btn.addEventListener("click", function (e) { @@ -16,14 +18,33 @@ btns.forEach(function (btn) { } if (count > 0) { - value.style.color = "green"; + value.style.color = "var(--clr-green-dark)"; } if (count < 0) { - value.style.color = "red"; + value.style.color = "var(--clr-red-dark)"; } if (count === 0) { - value.style.color = "#222"; + value.style.color = "var(--clr-primary-2)"; } value.textContent = count; + // animate value change + value.classList.remove("pop"); + // reflow to restart animation + void value.offsetWidth; + value.classList.add("pop"); }); }); + +// theme toggle: toggles alternate palette on body[data-theme="alt"] +if (themeToggle) { + themeToggle.addEventListener("click", function () { + const isAlt = body.getAttribute("data-theme") === "alt"; + if (isAlt) { + body.removeAttribute("data-theme"); + themeToggle.setAttribute("aria-pressed", "false"); + } else { + body.setAttribute("data-theme", "alt"); + themeToggle.setAttribute("aria-pressed", "true"); + } + }); +} diff --git a/02-counter/final/index.html b/02-counter/final/index.html index 82f7ea2ba..89f4f5930 100644 --- a/02-counter/final/index.html +++ b/02-counter/final/index.html @@ -11,12 +11,16 @@
-

counter

- 0 -
- - - +

Counter

+
+

A small, delightful counter with accessible controls

+ +
+ 0 +
+ + +
diff --git a/02-counter/final/styles.css b/02-counter/final/styles.css index f7ef04d4a..35ac4ddf1 100644 --- a/02-counter/final/styles.css +++ b/02-counter/final/styles.css @@ -68,10 +68,11 @@ Global Styles } body { font-family: var(--ff-secondary); - background: var(--clr-grey-10); + background: linear-gradient(135deg, var(--clr-primary-9), var(--clr-primary-10)); color: var(--clr-grey-1); line-height: 1.5; - font-size: 0.875rem; + font-size: 0.95rem; + -webkit-font-smoothing:antialiased; } ul { list-style-type: none; @@ -164,27 +165,138 @@ main { } .container { text-align: center; + background: var(--clr-white); + padding: 2.25rem 2rem; + border-radius: 12px; + box-shadow: var(--light-shadow); + width: min(90%, 420px); + transform: translateY(-1%); +} + +/* top row: subtitle + theme toggle */ +.top-row { + display: flex; + gap: 0.75rem; + align-items: center; + justify-content: center; + margin-bottom: 0.25rem; } +.theme-toggle { + background: transparent; + border: 1px solid var(--clr-grey-8); + color: var(--clr-grey-2); + padding: 0.35rem 0.6rem; + border-radius: 999px; + cursor: pointer; + font-size: 0.825rem; +} +.theme-toggle[aria-pressed="true"] { + background: var(--clr-primary-9); + color: var(--clr-white); + border-color: transparent; +} +/* value */ #value { - font-size: 6rem; - font-weight: bold; + display: block; + font-size: 4.5rem; + font-weight: 800; + color: var(--clr-primary-2); + margin: 0.5rem 0 1rem; + letter-spacing: -0.02em; + text-shadow: 0 4px 18px rgba(32, 45, 70, 0.08); +} + +/* animation when value changes */ +.pop { + animation: pop 360ms cubic-bezier(0.22, 1, 0.36, 1); +} +@keyframes pop { + 0% { transform: scale(1); } + 40% { transform: scale(1.18); } + 100% { transform: scale(1); } +} + +/* subtitle */ +.subtitle { + color: var(--clr-grey-5); + margin-top: 0.25rem; + margin-bottom: 0.5rem; + font-size: 0.95rem; } + +/* buttons */ .btn { - text-transform: uppercase; - background: transparent; - color: var(--clr-black); - padding: 0.375rem 0.75rem; - letter-spacing: var(--spacing); + text-transform: none; + color: var(--clr-white); + padding: 0.5rem 1rem; + letter-spacing: 0.02em; display: inline-block; transition: var(--transition); - font-size: 0.875rem; - border: 2px solid var(--clr-black); + font-size: 1rem; + border: 0; cursor: pointer; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); - border-radius: var(--radius); - margin: 0.5rem; + border-radius: 8px; + margin: 0.25rem; + min-width: 84px; +} +.btn:focus { + outline: 3px solid rgba(32, 124, 229, 0.18); + outline-offset: 2px; +} +.btn.increase { + background: linear-gradient(180deg, var(--clr-green-light), var(--clr-green-dark)); + box-shadow: 0 8px 20px rgba(46, 125, 50, 0.18); +} +.btn.decrease { + background: linear-gradient(180deg, var(--clr-red-light), var(--clr-red-dark)); + box-shadow: 0 8px 20px rgba(211, 47, 47, 0.18); +} +.btn.reset { + background: linear-gradient(180deg, var(--clr-primary-8), var(--clr-primary-5)); + color: var(--clr-grey-1); + box-shadow: 0 6px 16px rgba(10, 102, 194, 0.12); } .btn:hover { - color: var(--clr-white); - background: var(--clr-black); + transform: translateY(-3px); + filter: brightness(1.02); +} + +/* button container spacing */ +.button-container { + display: flex; + gap: 0.5rem; + justify-content: center; + align-items: center; + flex-wrap: wrap; +} + +@media screen and (min-width: 800px) { + .container { + padding: 3rem 3.5rem; + } + #value { + font-size: 6rem; + } + .btn { + min-width: 110px; + font-size: 1.05rem; + } +} + +/* alternate palette applied when body[data-theme="alt"] */ +body[data-theme="alt"] { + --clr-primary-1: hsl(285, 40%, 12%); + --clr-primary-2: hsl(285, 55%, 28%); + --clr-primary-3: hsl(285, 55%, 38%); + --clr-primary-4: hsl(285, 60%, 48%); + --clr-primary-5: hsl(285, 65%, 58%); + --clr-primary-6: hsl(285, 75%, 68%); + --clr-primary-7: hsl(285, 80%, 76%); + --clr-primary-8: hsl(285, 85%, 83%); + --clr-primary-9: hsl(285, 90%, 90%); + --clr-primary-10: hsl(285, 95%, 96%); + --clr-green-dark: hsl(190, 70%, 30%); + --clr-green-light: hsl(190, 70%, 60%); + --clr-red-dark: hsl(10, 70%, 35%); + --clr-red-light: hsl(10, 70%, 62%); } diff --git a/04-navbar/final/app.js b/04-navbar/final/app.js index 128fc4f64..4c84a8590 100644 --- a/04-navbar/final/app.js +++ b/04-navbar/final/app.js @@ -1,20 +1,120 @@ -// classList - shows/gets all classes -// contains - checks classList for specific class -// add - add class -// remove - remove class -// toggle - toggles class +// Enhanced Navbar Functionality const navToggle = document.querySelector(".nav-toggle"); const links = document.querySelector(".links"); +const mobileOverlay = document.querySelector(".mobile-overlay"); +const navbar = document.querySelector(".navbar"); +const navLinks = document.querySelectorAll(".nav-link"); +const body = document.body; -navToggle.addEventListener("click", function () { - // console.log(links.classList); - // console.log(links.classList.contains("random")); - // console.log(links.classList.contains("links")); - // if (links.classList.contains("show-links")) { - // links.classList.remove("show-links"); - // } else { - // links.classList.add("show-links"); - // } +// Toggle mobile menu +function toggleMenu() { + const isOpen = links.classList.contains("show-links"); + links.classList.toggle("show-links"); + mobileOverlay.classList.toggle("show"); + navToggle.classList.toggle("active"); + + // Update aria attributes for accessibility + navToggle.setAttribute("aria-expanded", !isOpen); + + // Prevent body scroll when menu is open (mobile) + if (!isOpen) { + body.style.overflow = "hidden"; + } else { + body.style.overflow = ""; + } +} + +// Close mobile menu +function closeMenu() { + links.classList.remove("show-links"); + mobileOverlay.classList.remove("show"); + navToggle.classList.remove("active"); + navToggle.setAttribute("aria-expanded", "false"); + body.style.overflow = ""; +} + +// Event Listeners +navToggle.addEventListener("click", toggleMenu); + +// Close menu when clicking overlay +mobileOverlay.addEventListener("click", closeMenu); + +// Close menu when clicking a link (mobile) +navLinks.forEach((link) => { + link.addEventListener("click", () => { + if (window.innerWidth < 800) { + closeMenu(); + } + }); +}); + +// Handle window resize +let resizeTimer; +window.addEventListener("resize", () => { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(() => { + if (window.innerWidth >= 800) { + closeMenu(); + } + }, 250); +}); + +// Add scroll effect to navbar +let lastScroll = 0; +window.addEventListener("scroll", () => { + const currentScroll = window.pageYOffset; + + if (currentScroll > 50) { + navbar.classList.add("scrolled"); + } else { + navbar.classList.remove("scrolled"); + } + + lastScroll = currentScroll; +}); + +// Set active link based on current page +function setActiveLink() { + const currentPath = window.location.pathname; + const currentPage = currentPath.split("/").pop() || "index.html"; + + navLinks.forEach((link) => { + link.classList.remove("active"); + const linkPath = link.getAttribute("href"); + + if (linkPath === currentPage || + (currentPage === "" && linkPath === "index.html")) { + link.classList.add("active"); + } + }); +} + +// Initialize active link on load +setActiveLink(); + +// Close menu on Escape key press +document.addEventListener("keydown", (e) => { + if (e.key === "Escape" && links.classList.contains("show-links")) { + closeMenu(); + navToggle.focus(); // Return focus to toggle button + } +}); + +// Smooth scroll for anchor links +document.querySelectorAll('a[href^="#"]').forEach((anchor) => { + anchor.addEventListener("click", function (e) { + const href = this.getAttribute("href"); + if (href !== "#" && href.startsWith("#")) { + e.preventDefault(); + const target = document.querySelector(href); + if (target) { + target.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + } + } + }); }); diff --git a/04-navbar/final/index.html b/04-navbar/final/index.html index 21758f36a..0c1e53ba0 100644 --- a/04-navbar/final/index.html +++ b/04-navbar/final/index.html @@ -3,71 +3,137 @@ - Navbar + Modern Navbar -