From a621bce692619c815ef0331b8c57826eaaaa5edf Mon Sep 17 00:00:00 2001 From: shayla-develops-webs Date: Tue, 25 Aug 2026 16:42:49 -0500 Subject: [PATCH 01/12] Add Secondary color CSS vars for What We Offer pills/dialog --- styles/globals.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/styles/globals.scss b/styles/globals.scss index b8be84c9..718b9de7 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -12,6 +12,8 @@ --color-light-bg: #8cd5e8; --color-primary-accent: #ffcc4c; --color-primary-content: #292929; + --color-secondary-accent: #19aad1; + --color-secondary-surface: #75cce3; --color-transparent: transparent; --color-box-shadow: rgba(0, 0, 0, 0.08); --dark-bg: #023047; From ab37b1d4825f3da4c9aa5510607ced254c4d6f5e Mon Sep 17 00:00:00 2001 From: shayla-develops-webs Date: Tue, 25 Aug 2026 16:54:20 -0500 Subject: [PATCH 02/12] Add static Pill component and services test harness page --- components/services/Pill/Pill.module.scss | 29 +++++++++++++++++++++++ components/services/Pill/index.js | 12 ++++++++++ pages/services.js | 11 +++++++++ 3 files changed, 52 insertions(+) create mode 100644 components/services/Pill/Pill.module.scss create mode 100644 components/services/Pill/index.js create mode 100644 pages/services.js diff --git a/components/services/Pill/Pill.module.scss b/components/services/Pill/Pill.module.scss new file mode 100644 index 00000000..2c76a29f --- /dev/null +++ b/components/services/Pill/Pill.module.scss @@ -0,0 +1,29 @@ +@use '@/styles/index' as *; + +.pill { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 999px; + border: 2px solid var(--color-primary-accent); + background-color: var(--color-transparent); + color: var(--color-black); + font-family: $font-family-primary; + font-weight: $font-weight-regular; + white-space: nowrap; + cursor: pointer; + padding: 0.5rem 1rem; + font-size: 1rem; + line-height: 1.5rem; + @include transition(all 0.3s ease); + + @include desktop { + padding: 0.75rem 2rem; + font-size: 1.5rem; + line-height: 2rem; + } + + &.secondary { + border-color: var(--color-secondary-accent); + } +} diff --git a/components/services/Pill/index.js b/components/services/Pill/index.js new file mode 100644 index 00000000..e16b4816 --- /dev/null +++ b/components/services/Pill/index.js @@ -0,0 +1,12 @@ +import styles from './Pill.module.scss'; +import { combineClasses } from '@/utils/classnames'; + +export default function Pill({ label, variant = 'primary', onClick }) { + const pillClass = combineClasses(styles.pill, variant, styles); + + return ( + + ); +} diff --git a/pages/services.js b/pages/services.js new file mode 100644 index 00000000..f5738db7 --- /dev/null +++ b/pages/services.js @@ -0,0 +1,11 @@ +import Pill from '@/components/services/Pill'; + +export default function Services() { + return ( +
+ + + +
+ ); +} From 6fda0d52c39720dddbddb3d2949d579d3b085f89 Mon Sep 17 00:00:00 2001 From: shayla-develops-webs Date: Tue, 25 Aug 2026 17:55:11 -0500 Subject: [PATCH 03/12] Add static What We Offer section layout --- .../WhatWeOffer/WhatWeOffer.module.scss | 54 +++++++++++++++++++ components/services/WhatWeOffer/index.js | 48 +++++++++++++++++ pages/services.js | 10 +--- 3 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 components/services/WhatWeOffer/WhatWeOffer.module.scss create mode 100644 components/services/WhatWeOffer/index.js diff --git a/components/services/WhatWeOffer/WhatWeOffer.module.scss b/components/services/WhatWeOffer/WhatWeOffer.module.scss new file mode 100644 index 00000000..fc7bad8d --- /dev/null +++ b/components/services/WhatWeOffer/WhatWeOffer.module.scss @@ -0,0 +1,54 @@ +@use '@/styles/index' as *; + +.whatWeOffer { + width: 100%; + background-color: var(--color-white); + padding: 4rem 0; + + @include desktop { + padding: 6rem 0; + } +} + +.heading { + font-family: $font-family-secondary; + font-weight: $font-weight-bold; + font-size: 2.25rem; + line-height: 2.75rem; + text-align: center; + margin: 0 0 1.5rem; + + @include desktop { + font-size: 4rem; + line-height: 4.5rem; + } +} + +.body { + font-family: $font-family-primary; + font-size: 1rem; + line-height: 1.5rem; + text-align: center; + max-width: 60rem; + margin: 0 auto 2.5rem; + + @include desktop { + font-size: 1.5rem; + line-height: 2rem; + } +} + +.pillRows { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +.pillRow { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 1rem; + width: 100%; +} diff --git a/components/services/WhatWeOffer/index.js b/components/services/WhatWeOffer/index.js new file mode 100644 index 00000000..93c26573 --- /dev/null +++ b/components/services/WhatWeOffer/index.js @@ -0,0 +1,48 @@ +import Pill from '@/components/services/Pill'; +import Container from '@/components/containers/Container'; +import styles from './WhatWeOffer.module.scss'; + +const offerPills = [ + { id: 'web-development', label: 'Web Development', variant: 'primary' }, + { + id: 'product-project-management', + label: 'Product & Project Management', + variant: 'secondary', + }, + { id: 'design', label: 'Design', variant: 'primary' }, + { + id: 'startup-nonprofit-tech-support', + label: 'Startup & Nonprofit Tech Support', + variant: 'secondary', + }, + { + id: 'optimization-growth', + label: 'Optimization & Growth', + variant: 'primary', + }, +]; + +export default function WhatWeOffer() { + return ( +
+ +

What We Offer

+

+ We deliver high-end digital solutions — from{' '} + design and development to{' '} + project management and strategy — tailored to your + goals and budget, with no compromise on quality. +

+
+ {[0, 1, 2].map(rowIndex => ( +
+ {offerPills.map(pill => ( + + ))} +
+ ))} +
+
+
+ ); +} diff --git a/pages/services.js b/pages/services.js index f5738db7..56c1509c 100644 --- a/pages/services.js +++ b/pages/services.js @@ -1,11 +1,5 @@ -import Pill from '@/components/services/Pill'; +import WhatWeOffer from '@/components/services/WhatWeOffer'; export default function Services() { - return ( -
- - - -
- ); + return ; } From fb69ad954f2b3d906a9eba182559763ad3b5c878 Mon Sep 17 00:00:00 2001 From: shayla-develops-webs Date: Tue, 25 Aug 2026 18:35:43 -0500 Subject: [PATCH 04/12] Add auto-scrolling marquee rows with keyboard-accessible focus handling --- components/services/Pill/index.js | 14 ++++- .../WhatWeOffer/WhatWeOffer.module.scss | 24 +++++++-- components/services/WhatWeOffer/index.js | 46 +++++++++++++--- .../services/WhatWeOffer/useAutoScroll.js | 52 +++++++++++++++++++ 4 files changed, 123 insertions(+), 13 deletions(-) create mode 100644 components/services/WhatWeOffer/useAutoScroll.js diff --git a/components/services/Pill/index.js b/components/services/Pill/index.js index e16b4816..d4a94e6d 100644 --- a/components/services/Pill/index.js +++ b/components/services/Pill/index.js @@ -1,11 +1,21 @@ import styles from './Pill.module.scss'; import { combineClasses } from '@/utils/classnames'; -export default function Pill({ label, variant = 'primary', onClick }) { +export default function Pill({ + label, + variant = 'primary', + onClick, + tabIndex, +}) { const pillClass = combineClasses(styles.pill, variant, styles); return ( - ); diff --git a/components/services/WhatWeOffer/WhatWeOffer.module.scss b/components/services/WhatWeOffer/WhatWeOffer.module.scss index fc7bad8d..dae0b52f 100644 --- a/components/services/WhatWeOffer/WhatWeOffer.module.scss +++ b/components/services/WhatWeOffer/WhatWeOffer.module.scss @@ -41,14 +41,28 @@ .pillRows { display: flex; flex-direction: column; - align-items: center; gap: 1rem; } -.pillRow { +.pillRowViewport { + width: 100%; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; + -ms-overflow-style: none; + + &::-webkit-scrollbar { + display: none; + } +} + +.pillTrack { + display: flex; + gap: 1rem; + width: max-content; +} + +.pillGroup { display: flex; - flex-wrap: wrap; - justify-content: center; gap: 1rem; - width: 100%; } diff --git a/components/services/WhatWeOffer/index.js b/components/services/WhatWeOffer/index.js index 93c26573..609581b5 100644 --- a/components/services/WhatWeOffer/index.js +++ b/components/services/WhatWeOffer/index.js @@ -1,7 +1,45 @@ import Pill from '@/components/services/Pill'; import Container from '@/components/containers/Container'; +import { useAutoScroll } from './useAutoScroll'; import styles from './WhatWeOffer.module.scss'; +const rowDirections = ['left', 'right', 'left']; + +function PillRow({ pills, direction }) { + const { viewportRef, groupRef, pause, resume } = useAutoScroll({ + direction, + }); + + return ( +
+
+
+ {pills.map(pill => ( + + ))} +
+ +
+
+ ); +} + const offerPills = [ { id: 'web-development', label: 'Web Development', variant: 'primary' }, { @@ -34,12 +72,8 @@ export default function WhatWeOffer() { goals and budget, with no compromise on quality.

- {[0, 1, 2].map(rowIndex => ( -
- {offerPills.map(pill => ( - - ))} -
+ {rowDirections.map((direction, rowIndex) => ( + ))}
diff --git a/components/services/WhatWeOffer/useAutoScroll.js b/components/services/WhatWeOffer/useAutoScroll.js new file mode 100644 index 00000000..f6b085e1 --- /dev/null +++ b/components/services/WhatWeOffer/useAutoScroll.js @@ -0,0 +1,52 @@ +import { useEffect, useRef } from 'react'; + +export function useAutoScroll({ direction = 'left', speed = 60 } = {}) { + const viewportRef = useRef(null); + const groupRef = useRef(null); + const pausedRef = useRef(false); + + useEffect(() => { + const viewport = viewportRef.current; + const group = groupRef.current; + if (!viewport || !group) return undefined; + + const prefersReducedMotion = window.matchMedia( + '(prefers-reduced-motion: reduce)', + ).matches; + if (prefersReducedMotion) return undefined; + + const dir = direction === 'right' ? -1 : 1; + const groupWidth = group.offsetWidth; + viewport.scrollLeft = dir === -1 ? groupWidth : 0; + + let frameId; + let lastTimestamp; + + const step = timestamp => { + if (lastTimestamp !== undefined && !pausedRef.current) { + const delta = ((timestamp - lastTimestamp) / 1000) * speed * dir; + viewport.scrollLeft += delta; + + if (viewport.scrollLeft >= groupWidth) { + viewport.scrollLeft -= groupWidth; + } else if (viewport.scrollLeft <= 0) { + viewport.scrollLeft += groupWidth; + } + } + lastTimestamp = timestamp; + frameId = requestAnimationFrame(step); + }; + + frameId = requestAnimationFrame(step); + return () => cancelAnimationFrame(frameId); + }, [direction, speed]); + + const pause = () => { + pausedRef.current = true; + }; + const resume = () => { + pausedRef.current = false; + }; + + return { viewportRef, groupRef, pause, resume }; +} From cfd69cec74f11477d095f087905b3575b14818a4 Mon Sep 17 00:00:00 2001 From: shayla-develops-webs Date: Tue, 25 Aug 2026 18:41:49 -0500 Subject: [PATCH 05/12] Add accessible Dialog primitive with focus trap and escape/backdrop close --- components/overlays/Dialog/Dialog.module.scss | 20 +++++ components/overlays/Dialog/index.js | 74 +++++++++++++++++++ pages/services.js | 21 +++++- 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 components/overlays/Dialog/Dialog.module.scss create mode 100644 components/overlays/Dialog/index.js diff --git a/components/overlays/Dialog/Dialog.module.scss b/components/overlays/Dialog/Dialog.module.scss new file mode 100644 index 00000000..2c674dcd --- /dev/null +++ b/components/overlays/Dialog/Dialog.module.scss @@ -0,0 +1,20 @@ +.backdrop { + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + padding: 1.5rem; + z-index: 100; +} + +.panel { + background-color: var(--color-white); + border-radius: 1rem; + max-width: 40rem; + width: 100%; + max-height: 90vh; + overflow-y: auto; + padding: 2rem; +} diff --git a/components/overlays/Dialog/index.js b/components/overlays/Dialog/index.js new file mode 100644 index 00000000..21d89c8c --- /dev/null +++ b/components/overlays/Dialog/index.js @@ -0,0 +1,74 @@ +import { useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; +import styles from './Dialog.module.scss'; + +const FOCUSABLE_SELECTOR = + 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'; + +export default function Dialog({ open, onClose, titleId, children }) { + const [mounted, setMounted] = useState(false); + const panelRef = useRef(null); + const previouslyFocusedRef = useRef(null); + + useEffect(() => { + setMounted(true); + }, []); + + useEffect(() => { + if (!open) return undefined; + + previouslyFocusedRef.current = document.activeElement; + const panel = panelRef.current; + const focusable = panel + ? Array.from(panel.querySelectorAll(FOCUSABLE_SELECTOR)) + : []; + (focusable[0] || panel)?.focus(); + + const handleKeyDown = event => { + if (event.key === 'Escape') { + event.preventDefault(); + onClose(); + return; + } + + if (event.key !== 'Tab' || focusable.length === 0) return; + + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + + if (event.shiftKey && document.activeElement === first) { + event.preventDefault(); + last.focus(); + } else if (!event.shiftKey && document.activeElement === last) { + event.preventDefault(); + first.focus(); + } + }; + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + previouslyFocusedRef.current?.focus(); + }; + }, [open, onClose]); + + if (!mounted || !open) return null; + + return createPortal( +
+
event.stopPropagation()} + > + {children} +
+
, + document.body, + ); +} diff --git a/pages/services.js b/pages/services.js index 56c1509c..881c028e 100644 --- a/pages/services.js +++ b/pages/services.js @@ -1,5 +1,24 @@ +import { useId, useState } from 'react'; import WhatWeOffer from '@/components/services/WhatWeOffer'; +import Dialog from '@/components/overlays/Dialog'; export default function Services() { - return ; + const [open, setOpen] = useState(false); + const titleId = useId(); + + return ( + <> + + + setOpen(false)} titleId={titleId}> +

Test dialog

+

This is a throwaway dialog to verify the Dialog primitive.

+ +
+ + ); } From 21e0f95ac18019cdf02e7011d2e47402f8913b2b Mon Sep 17 00:00:00 2001 From: shayla-develops-webs Date: Tue, 25 Aug 2026 18:52:29 -0500 Subject: [PATCH 06/12] Add ServiceDialog with hand-built CloseIcon and Primary/Secondary variants --- components/icons/CloseIcon/index.js | 18 +++++ components/overlays/Dialog/Dialog.module.scss | 1 - components/overlays/Dialog/index.js | 10 ++- .../ServiceDialog/ServiceDialog.module.scss | 68 +++++++++++++++++++ components/services/ServiceDialog/index.js | 39 +++++++++++ pages/services.js | 38 +++++++---- 6 files changed, 159 insertions(+), 15 deletions(-) create mode 100644 components/icons/CloseIcon/index.js create mode 100644 components/services/ServiceDialog/ServiceDialog.module.scss create mode 100644 components/services/ServiceDialog/index.js diff --git a/components/icons/CloseIcon/index.js b/components/icons/CloseIcon/index.js new file mode 100644 index 00000000..e83521de --- /dev/null +++ b/components/icons/CloseIcon/index.js @@ -0,0 +1,18 @@ +export default function CloseIcon(props) { + const { className, fill = 'var(--color-black)' } = props; + + return ( + + + + ); +} diff --git a/components/overlays/Dialog/Dialog.module.scss b/components/overlays/Dialog/Dialog.module.scss index 2c674dcd..1b90108a 100644 --- a/components/overlays/Dialog/Dialog.module.scss +++ b/components/overlays/Dialog/Dialog.module.scss @@ -10,7 +10,6 @@ } .panel { - background-color: var(--color-white); border-radius: 1rem; max-width: 40rem; width: 100%; diff --git a/components/overlays/Dialog/index.js b/components/overlays/Dialog/index.js index 21d89c8c..5152891d 100644 --- a/components/overlays/Dialog/index.js +++ b/components/overlays/Dialog/index.js @@ -5,7 +5,13 @@ import styles from './Dialog.module.scss'; const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'; -export default function Dialog({ open, onClose, titleId, children }) { +export default function Dialog({ + open, + onClose, + titleId, + className, + children, +}) { const [mounted, setMounted] = useState(false); const panelRef = useRef(null); const previouslyFocusedRef = useRef(null); @@ -58,7 +64,7 @@ export default function Dialog({ open, onClose, titleId, children }) { return createPortal(
+
+ + {label} + + +
+

{description}

+ + ); +} diff --git a/pages/services.js b/pages/services.js index 881c028e..4a8d0179 100644 --- a/pages/services.js +++ b/pages/services.js @@ -1,24 +1,38 @@ import { useId, useState } from 'react'; import WhatWeOffer from '@/components/services/WhatWeOffer'; -import Dialog from '@/components/overlays/Dialog'; +import ServiceDialog from '@/components/services/ServiceDialog'; export default function Services() { - const [open, setOpen] = useState(false); - const titleId = useId(); + const [primaryOpen, setPrimaryOpen] = useState(false); + const [secondaryOpen, setSecondaryOpen] = useState(false); + const primaryTitleId = useId(); + const secondaryTitleId = useId(); return ( <> - - setOpen(false)} titleId={titleId}> -

Test dialog

-

This is a throwaway dialog to verify the Dialog primitive.

- -
+ + setPrimaryOpen(false)} + titleId={primaryTitleId} + variant='primary' + label='Web Development' + description='This is placeholder dialog content for testing the primary variant.' + /> + setSecondaryOpen(false)} + titleId={secondaryTitleId} + variant='secondary' + label='Product & Project Management' + description='This is placeholder dialog content for testing the secondary variant.' + /> ); } From d5d6c08111ec0e86bc01d2ceaf4803ac039980ea Mon Sep 17 00:00:00 2001 From: shayla-develops-webs Date: Tue, 25 Aug 2026 19:12:31 -0500 Subject: [PATCH 07/12] Add services data file and wire pill clicks to open ServiceDialog --- components/services/WhatWeOffer/index.js | 51 ++++++++++++---------- pages/services.js | 35 +-------------- utils/services.js | 54 ++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 57 deletions(-) create mode 100644 utils/services.js diff --git a/components/services/WhatWeOffer/index.js b/components/services/WhatWeOffer/index.js index 609581b5..e4261c24 100644 --- a/components/services/WhatWeOffer/index.js +++ b/components/services/WhatWeOffer/index.js @@ -1,11 +1,14 @@ +import { useId, useState } from 'react'; import Pill from '@/components/services/Pill'; import Container from '@/components/containers/Container'; +import ServiceDialog from '@/components/services/ServiceDialog'; +import { services } from '@/utils/services'; import { useAutoScroll } from './useAutoScroll'; import styles from './WhatWeOffer.module.scss'; const rowDirections = ['left', 'right', 'left']; -function PillRow({ pills, direction }) { +function PillRow({ pills, direction, onSelect }) { const { viewportRef, groupRef, pause, resume } = useAutoScroll({ direction, }); @@ -22,7 +25,12 @@ function PillRow({ pills, direction }) {
{pills.map(pill => ( - + onSelect(pill)} + /> ))}
@@ -40,27 +49,10 @@ function PillRow({ pills, direction }) { ); } -const offerPills = [ - { id: 'web-development', label: 'Web Development', variant: 'primary' }, - { - id: 'product-project-management', - label: 'Product & Project Management', - variant: 'secondary', - }, - { id: 'design', label: 'Design', variant: 'primary' }, - { - id: 'startup-nonprofit-tech-support', - label: 'Startup & Nonprofit Tech Support', - variant: 'secondary', - }, - { - id: 'optimization-growth', - label: 'Optimization & Growth', - variant: 'primary', - }, -]; - export default function WhatWeOffer() { + const [activeService, setActiveService] = useState(null); + const titleId = useId(); + return (
@@ -73,10 +65,23 @@ export default function WhatWeOffer() {

{rowDirections.map((direction, rowIndex) => ( - + ))}
+ setActiveService(null)} + titleId={titleId} + label={activeService?.label} + description={activeService?.description} + variant={activeService?.variant} + />
); } diff --git a/pages/services.js b/pages/services.js index 4a8d0179..56c1509c 100644 --- a/pages/services.js +++ b/pages/services.js @@ -1,38 +1,5 @@ -import { useId, useState } from 'react'; import WhatWeOffer from '@/components/services/WhatWeOffer'; -import ServiceDialog from '@/components/services/ServiceDialog'; export default function Services() { - const [primaryOpen, setPrimaryOpen] = useState(false); - const [secondaryOpen, setSecondaryOpen] = useState(false); - const primaryTitleId = useId(); - const secondaryTitleId = useId(); - - return ( - <> - - - - setPrimaryOpen(false)} - titleId={primaryTitleId} - variant='primary' - label='Web Development' - description='This is placeholder dialog content for testing the primary variant.' - /> - setSecondaryOpen(false)} - titleId={secondaryTitleId} - variant='secondary' - label='Product & Project Management' - description='This is placeholder dialog content for testing the secondary variant.' - /> - - ); + return ; } diff --git a/utils/services.js b/utils/services.js new file mode 100644 index 00000000..3dd38929 --- /dev/null +++ b/utils/services.js @@ -0,0 +1,54 @@ +export const services = [ + { + id: 'web-development', + label: 'Web Development', + variant: 'primary', + subServices: ['Custom Websites', 'CMS Development (WordPress)'], + description: + 'We create modern, responsive websites that are fast, secure, and tailored to your business—designed to engage users, drive results, and grow your online presence.', + }, + { + id: 'product-project-management', + label: 'Product & Project Management', + variant: 'secondary', + subServices: [ + 'Agile Project Management', + 'Product Discovery', + 'Technical Roadmapping', + ], + description: + 'Our service ensures every project is strategically planned, efficiently executed, and successfully delivered—on time, within budget, and aligned with your business goals.', + }, + { + id: 'design', + label: 'Design', + variant: 'primary', + subServices: ['UI/UX Design', 'Product Design', 'UX Audits'], + description: + 'We craft intuitive, user-centered designs — from UI/UX and product design to UX audits — that make every digital experience clear, engaging, and easy to use.', + }, + { + id: 'startup-nonprofit-tech-support', + label: 'Startup & Nonprofit Tech Support', + variant: 'secondary', + subServices: [ + 'MVP Development', + 'Technical Advisory', + 'Digital Transformation for Nonprofits', + ], + description: + 'We support startups and nonprofits with MVP development, technical advisory, and digital transformation — helping mission-driven teams build smart and move fast, without compromising on quality.', + }, + { + id: 'optimization-growth', + label: 'Optimization & Growth', + variant: 'primary', + subServices: [ + 'SEO Optimization', + 'Website Performance Optimization', + 'Accessibility Improvements', + ], + description: + 'We help your website perform at its best through SEO optimization, performance tuning, and accessibility improvements — keeping you visible, fast, and inclusive for every user.', + }, +]; From a0c45d939ed4c7de5ed616cc62df82c2dacba840 Mon Sep 17 00:00:00 2001 From: shayla-develops-webs Date: Thu, 3 Sep 2026 16:31:01 -0500 Subject: [PATCH 08/12] Fix ServiceDialog mobile/desktop sizing, decoration assets, and overflow bugs --- components/overlays/Dialog/Dialog.module.scss | 11 +++- components/overlays/Dialog/index.js | 34 +++++++++++- components/services/Pill/Pill.module.scss | 4 +- .../ServiceDialog/ServiceDialog.module.scss | 47 ++++++++++++++-- components/services/ServiceDialog/index.js | 1 + .../WhatWeOffer/WhatWeOffer.module.scss | 53 +++++++++++++++++-- components/services/WhatWeOffer/index.js | 47 ++++++++++------ styles/globals.scss | 1 + 8 files changed, 169 insertions(+), 29 deletions(-) diff --git a/components/overlays/Dialog/Dialog.module.scss b/components/overlays/Dialog/Dialog.module.scss index 1b90108a..15bb251a 100644 --- a/components/overlays/Dialog/Dialog.module.scss +++ b/components/overlays/Dialog/Dialog.module.scss @@ -1,7 +1,9 @@ +@use '@/styles/index' as *; + .backdrop { position: fixed; inset: 0; - background-color: rgba(0, 0, 0, 0.5); + background-color: var(--overlay-backdrop); display: flex; align-items: center; justify-content: center; @@ -13,7 +15,14 @@ border-radius: 1rem; max-width: 40rem; width: 100%; + min-width: 0; max-height: 90vh; overflow-y: auto; padding: 2rem; } + +.containedPanel { + border-radius: 1rem; + min-width: 0; + padding: 2rem; +} diff --git a/components/overlays/Dialog/index.js b/components/overlays/Dialog/index.js index 5152891d..bd29196a 100644 --- a/components/overlays/Dialog/index.js +++ b/components/overlays/Dialog/index.js @@ -10,6 +10,7 @@ export default function Dialog({ onClose, titleId, className, + contained = false, children, }) { const [mounted, setMounted] = useState(false); @@ -51,15 +52,44 @@ export default function Dialog({ } }; + const handleOutsideClick = event => { + if (panel && !panel.contains(event.target)) { + onClose(); + } + }; + document.addEventListener('keydown', handleKeyDown); + if (contained) { + document.addEventListener('mousedown', handleOutsideClick); + } return () => { document.removeEventListener('keydown', handleKeyDown); + if (contained) { + document.removeEventListener('mousedown', handleOutsideClick); + } previouslyFocusedRef.current?.focus(); }; - }, [open, onClose]); + }, [open, onClose, contained]); + + if (!open) return null; + + if (contained) { + return ( +
+ {children} +
+ ); + } - if (!mounted || !open) return null; + if (!mounted) return null; return createPortal(
diff --git a/components/services/Pill/Pill.module.scss b/components/services/Pill/Pill.module.scss index 2c76a29f..7c295e97 100644 --- a/components/services/Pill/Pill.module.scss +++ b/components/services/Pill/Pill.module.scss @@ -13,13 +13,13 @@ white-space: nowrap; cursor: pointer; padding: 0.5rem 1rem; - font-size: 1rem; + font-size: $font-size-2xs; line-height: 1.5rem; @include transition(all 0.3s ease); @include desktop { padding: 0.75rem 2rem; - font-size: 1.5rem; + font-size: $font-size-sm; line-height: 2rem; } diff --git a/components/services/ServiceDialog/ServiceDialog.module.scss b/components/services/ServiceDialog/ServiceDialog.module.scss index 5fded2a9..3d3d59f4 100644 --- a/components/services/ServiceDialog/ServiceDialog.module.scss +++ b/components/services/ServiceDialog/ServiceDialog.module.scss @@ -1,10 +1,33 @@ @use '@/styles/index' as *; .panel { + position: absolute; + min-height: 100%; + z-index: 1; display: flex; flex-direction: column; gap: 1.5rem; + @include mobile { + top: -0.5rem; + right: -2rem; + bottom: -4rem; + left: -2rem; + } + + @include tablet { + top: -1rem; + right: -2rem; + left: -2rem; + } + + @include desktop { + top: -1rem; + right: -2rem; + bottom: -2rem; + left: -2rem; + } + &.primary { background-color: var(--color-primary-accent); } @@ -19,6 +42,7 @@ align-items: center; justify-content: space-between; gap: 1rem; + min-width: 0; } .echoPill { @@ -30,10 +54,17 @@ color: var(--color-white); font-family: $font-family-primary; font-weight: $font-weight-regular; - padding: 0.75rem 2rem; - font-size: 1.5rem; - line-height: 2rem; + padding: 0.25rem 0.75rem; + font-size: 0.75rem; + line-height: 1rem; white-space: nowrap; + flex-shrink: 0; + + @include desktop { + padding: 0.5rem 1.25rem; + font-size: 1rem; + line-height: 1.5rem; + } } .closeButton { @@ -61,8 +92,14 @@ .description { font-family: $font-family-primary; - font-size: 1.5rem; - line-height: 2rem; + font-size: $font-size-2xs; + line-height: 1.5rem; color: var(--color-primary-content); + word-break: break-word; margin: 0; + + @include desktop { + font-size: $font-size-sm; + line-height: 2rem; + } } diff --git a/components/services/ServiceDialog/index.js b/components/services/ServiceDialog/index.js index a8f118c0..cae3d60c 100644 --- a/components/services/ServiceDialog/index.js +++ b/components/services/ServiceDialog/index.js @@ -19,6 +19,7 @@ export default function ServiceDialog({ onClose={onClose} titleId={titleId} className={panelClass} + contained >
diff --git a/components/services/WhatWeOffer/WhatWeOffer.module.scss b/components/services/WhatWeOffer/WhatWeOffer.module.scss index dae0b52f..0b1ad963 100644 --- a/components/services/WhatWeOffer/WhatWeOffer.module.scss +++ b/components/services/WhatWeOffer/WhatWeOffer.module.scss @@ -1,7 +1,10 @@ @use '@/styles/index' as *; .whatWeOffer { + position: relative; width: 100%; + overflow-x: hidden; + overflow-y: visible; background-color: var(--color-white); padding: 4rem 0; @@ -10,10 +13,50 @@ } } +.leftDecoration { + display: none; + pointer-events: none; + + @include desktop { + display: block; + position: absolute; + top: 50%; + left: -17rem; + transform: translateY(-50%); + height: 14rem; + width: auto; + } +} + +.rightDecoration { + display: none; + pointer-events: none; + + @include desktop { + display: flex; + align-items: center; + gap: 0.5rem; + position: absolute; + top: 50%; + right: -24rem; + transform: translateY(-50%); + } +} + +.slash { + height: 19rem; + width: auto; +} + +.angleBracket { + height: 14rem; + width: auto; +} + .heading { font-family: $font-family-secondary; font-weight: $font-weight-bold; - font-size: 2.25rem; + font-size: $font-size-md; line-height: 2.75rem; text-align: center; margin: 0 0 1.5rem; @@ -26,18 +69,22 @@ .body { font-family: $font-family-primary; - font-size: 1rem; + font-size: $font-size-2xs; line-height: 1.5rem; text-align: center; max-width: 60rem; margin: 0 auto 2.5rem; @include desktop { - font-size: 1.5rem; + font-size: $font-size-sm; line-height: 2rem; } } +.pillRowsWrapper { + position: relative; +} + .pillRows { display: flex; flex-direction: column; diff --git a/components/services/WhatWeOffer/index.js b/components/services/WhatWeOffer/index.js index e4261c24..87c3667d 100644 --- a/components/services/WhatWeOffer/index.js +++ b/components/services/WhatWeOffer/index.js @@ -63,25 +63,40 @@ export default function WhatWeOffer() { project management and strategy — tailored to your goals and budget, with no compromise on quality.

-
- {rowDirections.map((direction, rowIndex) => ( - + + +
+ {rowDirections.map((direction, rowIndex) => ( + + ))} +
+ setActiveService(null)} + titleId={titleId} + label={activeService?.label} + description={activeService?.description} + variant={activeService?.variant} + />
- setActiveService(null)} - titleId={titleId} - label={activeService?.label} - description={activeService?.description} - variant={activeService?.variant} - /> ); } diff --git a/styles/globals.scss b/styles/globals.scss index 718b9de7..34745cba 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -16,6 +16,7 @@ --color-secondary-surface: #75cce3; --color-transparent: transparent; --color-box-shadow: rgba(0, 0, 0, 0.08); + --overlay-backdrop: rgba(0, 0, 0, 0.5); --dark-bg: #023047; --error: #be1313; --shadow-default: 0px 8px 24px rgba(0, 0, 0, 0.15); From 5aad9c943abbf5825d5df7af466a57d9aab02cb6 Mon Sep 17 00:00:00 2001 From: shayla-develops-webs Date: Thu, 3 Sep 2026 16:39:14 -0500 Subject: [PATCH 09/12] move useAutoScroll to hooks --- components/services/WhatWeOffer/index.js | 2 +- {components/services/WhatWeOffer => hooks}/useAutoScroll.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {components/services/WhatWeOffer => hooks}/useAutoScroll.js (100%) diff --git a/components/services/WhatWeOffer/index.js b/components/services/WhatWeOffer/index.js index 87c3667d..6b9b7a5d 100644 --- a/components/services/WhatWeOffer/index.js +++ b/components/services/WhatWeOffer/index.js @@ -3,7 +3,7 @@ import Pill from '@/components/services/Pill'; import Container from '@/components/containers/Container'; import ServiceDialog from '@/components/services/ServiceDialog'; import { services } from '@/utils/services'; -import { useAutoScroll } from './useAutoScroll'; +import { useAutoScroll } from '@/hooks/useAutoScroll'; import styles from './WhatWeOffer.module.scss'; const rowDirections = ['left', 'right', 'left']; diff --git a/components/services/WhatWeOffer/useAutoScroll.js b/hooks/useAutoScroll.js similarity index 100% rename from components/services/WhatWeOffer/useAutoScroll.js rename to hooks/useAutoScroll.js From 90a1b5c966023394d62fe112d954f5419b9e76de Mon Sep 17 00:00:00 2001 From: shayla-develops-webs Date: Thu, 3 Sep 2026 17:08:30 -0500 Subject: [PATCH 10/12] changelog updated --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f27fb910..8f196699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -234,6 +234,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Introduced SDD structure for Services Page - Updated SDD docs to include new Service page issues +- Added "What We Offer" section to the Services page ### Fixed From 1c6c53eeb197884de6691b813c55314a68673560 Mon Sep 17 00:00:00 2001 From: shayla-develops-webs Date: Thu, 3 Sep 2026 18:04:32 -0500 Subject: [PATCH 11/12] refactor pill movement based on row index --- components/services/WhatWeOffer/index.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/services/WhatWeOffer/index.js b/components/services/WhatWeOffer/index.js index 6b9b7a5d..d4ac8df2 100644 --- a/components/services/WhatWeOffer/index.js +++ b/components/services/WhatWeOffer/index.js @@ -6,7 +6,12 @@ import { services } from '@/utils/services'; import { useAutoScroll } from '@/hooks/useAutoScroll'; import styles from './WhatWeOffer.module.scss'; -const rowDirections = ['left', 'right', 'left']; +const ROW_COUNT = 3; +const rowIndexes = Array.from({ length: ROW_COUNT }, (_, index) => index); + +function getRowDirection(rowIndex) { + return rowIndex % 2 === 0 ? 'left' : 'right'; +} function PillRow({ pills, direction, onSelect }) { const { viewportRef, groupRef, pause, resume } = useAutoScroll({ @@ -78,11 +83,11 @@ export default function WhatWeOffer() { />
- {rowDirections.map((direction, rowIndex) => ( + {rowIndexes.map(rowIndex => ( ))} From 4d33c9809d8dc3a4860bf8d92b6abf34060744e2 Mon Sep 17 00:00:00 2001 From: shayla-develops-webs Date: Thu, 3 Sep 2026 19:58:27 -0500 Subject: [PATCH 12/12] Add staged entrance animation --- .../WhatWeOffer/WhatWeOffer.module.scss | 29 +++++++- components/services/WhatWeOffer/index.js | 66 +++++++++++++++---- hooks/useAutoScroll.js | 10 ++- 3 files changed, 88 insertions(+), 17 deletions(-) diff --git a/components/services/WhatWeOffer/WhatWeOffer.module.scss b/components/services/WhatWeOffer/WhatWeOffer.module.scss index 0b1ad963..3d32c6fb 100644 --- a/components/services/WhatWeOffer/WhatWeOffer.module.scss +++ b/components/services/WhatWeOffer/WhatWeOffer.module.scss @@ -13,6 +13,17 @@ } } +.content { + opacity: 0; + transform: translateY(-16rem); + @include transition(transform 1s, opacity 1s); + + &.revealed { + opacity: 1; + transform: translateY(0); + } +} + .leftDecoration { display: none; pointer-events: none; @@ -22,9 +33,16 @@ position: absolute; top: 50%; left: -17rem; - transform: translateY(-50%); height: 14rem; width: auto; + opacity: 0; + transform: translateY(-50%) translateX(-13rem); + @include transition(transform 1s, opacity 1s); + + &.revealed { + opacity: 1; + transform: translateY(-50%) translateX(0); + } } } @@ -39,7 +57,14 @@ position: absolute; top: 50%; right: -24rem; - transform: translateY(-50%); + opacity: 0; + transform: translateY(-50%) translateX(13rem); + @include transition(transform 1s, opacity 1s); + + &.revealed { + opacity: 1; + transform: translateY(-50%) translateX(0); + } } } diff --git a/components/services/WhatWeOffer/index.js b/components/services/WhatWeOffer/index.js index d4ac8df2..45835c9b 100644 --- a/components/services/WhatWeOffer/index.js +++ b/components/services/WhatWeOffer/index.js @@ -1,21 +1,25 @@ -import { useId, useState } from 'react'; +import { useEffect, useId, useState } from 'react'; import Pill from '@/components/services/Pill'; import Container from '@/components/containers/Container'; import ServiceDialog from '@/components/services/ServiceDialog'; import { services } from '@/utils/services'; import { useAutoScroll } from '@/hooks/useAutoScroll'; +import { useIntersect } from '@/hooks/useIntersect'; +import { combineClasses } from '@/utils/classnames'; import styles from './WhatWeOffer.module.scss'; const ROW_COUNT = 3; const rowIndexes = Array.from({ length: ROW_COUNT }, (_, index) => index); +const REVEAL_TRANSITION_MS = 1000; function getRowDirection(rowIndex) { return rowIndex % 2 === 0 ? 'left' : 'right'; } -function PillRow({ pills, direction, onSelect }) { +function PillRow({ pills, direction, enabled, onSelect }) { const { viewportRef, groupRef, pause, resume } = useAutoScroll({ direction, + enabled, }); return ( @@ -57,24 +61,61 @@ function PillRow({ pills, direction, onSelect }) { export default function WhatWeOffer() { const [activeService, setActiveService] = useState(null); const titleId = useId(); + const [setSectionNode, entry] = useIntersect({}); + const [revealed, setRevealed] = useState(false); + const [marqueeEnabled, setMarqueeEnabled] = useState(false); + + useEffect(() => { + if (entry.isIntersecting && !revealed) { + setRevealed(true); + } + }, [entry.isIntersecting, revealed]); + + useEffect(() => { + if (!revealed) return undefined; + + const timeoutId = setTimeout( + () => setMarqueeEnabled(true), + REVEAL_TRANSITION_MS, + ); + return () => clearTimeout(timeoutId); + }, [revealed]); + + const contentClass = combineClasses( + styles.content, + revealed ? 'revealed' : null, + styles, + ); + const leftDecorationClass = combineClasses( + styles.leftDecoration, + revealed ? 'revealed' : null, + styles, + ); + const rightDecorationClass = combineClasses( + styles.rightDecoration, + revealed ? 'revealed' : null, + styles, + ); return ( -
+
-

What We Offer

-

- We deliver high-end digital solutions — from{' '} - design and development to{' '} - project management and strategy — tailored to your - goals and budget, with no compromise on quality. -

+
+

What We Offer

+

+ We deliver high-end digital solutions — from{' '} + design and development to{' '} + project management and strategy — tailored to your + goals and budget, with no compromise on quality. +

+
-