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
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
new file mode 100644
index 00000000..15bb251a
--- /dev/null
+++ b/components/overlays/Dialog/Dialog.module.scss
@@ -0,0 +1,28 @@
+@use '@/styles/index' as *;
+
+.backdrop {
+ position: fixed;
+ inset: 0;
+ background-color: var(--overlay-backdrop);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+ z-index: 100;
+}
+
+.panel {
+ 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
new file mode 100644
index 00000000..bd29196a
--- /dev/null
+++ b/components/overlays/Dialog/index.js
@@ -0,0 +1,110 @@
+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,
+ className,
+ contained = false,
+ 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();
+ }
+ };
+
+ 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, contained]);
+
+ if (!open) return null;
+
+ if (contained) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ if (!mounted) return null;
+
+ return createPortal(
+
+
event.stopPropagation()}
+ >
+ {children}
+
+
,
+ document.body,
+ );
+}
diff --git a/components/services/Pill/Pill.module.scss b/components/services/Pill/Pill.module.scss
new file mode 100644
index 00000000..7c295e97
--- /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: $font-size-2xs;
+ line-height: 1.5rem;
+ @include transition(all 0.3s ease);
+
+ @include desktop {
+ padding: 0.75rem 2rem;
+ font-size: $font-size-sm;
+ 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..d4a94e6d
--- /dev/null
+++ b/components/services/Pill/index.js
@@ -0,0 +1,22 @@
+import styles from './Pill.module.scss';
+import { combineClasses } from '@/utils/classnames';
+
+export default function Pill({
+ label,
+ variant = 'primary',
+ onClick,
+ tabIndex,
+}) {
+ const pillClass = combineClasses(styles.pill, variant, styles);
+
+ return (
+
+ {label}
+
+ );
+}
diff --git a/components/services/ServiceDialog/ServiceDialog.module.scss b/components/services/ServiceDialog/ServiceDialog.module.scss
new file mode 100644
index 00000000..3d3d59f4
--- /dev/null
+++ b/components/services/ServiceDialog/ServiceDialog.module.scss
@@ -0,0 +1,105 @@
+@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);
+ }
+
+ &.secondary {
+ background-color: var(--color-secondary-surface);
+ }
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ min-width: 0;
+}
+
+.echoPill {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 999px;
+ background-color: var(--color-black);
+ color: var(--color-white);
+ font-family: $font-family-primary;
+ font-weight: $font-weight-regular;
+ 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 {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 3.5rem;
+ height: 3.5rem;
+ border: none;
+ border-radius: 0.25rem;
+ background-color: var(--color-transparent);
+ cursor: pointer;
+ flex-shrink: 0;
+ @include transition(all 0.3s ease);
+
+ &:hover {
+ background-color: var(--color-box-shadow);
+ }
+}
+
+.closeIcon {
+ width: 2rem;
+ height: 2rem;
+}
+
+.description {
+ font-family: $font-family-primary;
+ 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
new file mode 100644
index 00000000..cae3d60c
--- /dev/null
+++ b/components/services/ServiceDialog/index.js
@@ -0,0 +1,40 @@
+import Dialog from '@/components/overlays/Dialog';
+import CloseIcon from '@/components/icons/CloseIcon';
+import { combineClasses } from '@/utils/classnames';
+import styles from './ServiceDialog.module.scss';
+
+export default function ServiceDialog({
+ open,
+ onClose,
+ titleId,
+ label,
+ description,
+ variant = 'primary',
+}) {
+ const panelClass = combineClasses(styles.panel, variant, styles);
+
+ return (
+
+
+
+ {label}
+
+
+
+
+
+ {description}
+
+ );
+}
diff --git a/components/services/WhatWeOffer/WhatWeOffer.module.scss b/components/services/WhatWeOffer/WhatWeOffer.module.scss
new file mode 100644
index 00000000..3d32c6fb
--- /dev/null
+++ b/components/services/WhatWeOffer/WhatWeOffer.module.scss
@@ -0,0 +1,140 @@
+@use '@/styles/index' as *;
+
+.whatWeOffer {
+ position: relative;
+ width: 100%;
+ overflow-x: hidden;
+ overflow-y: visible;
+ background-color: var(--color-white);
+ padding: 4rem 0;
+
+ @include desktop {
+ padding: 6rem 0;
+ }
+}
+
+.content {
+ opacity: 0;
+ transform: translateY(-16rem);
+ @include transition(transform 1s, opacity 1s);
+
+ &.revealed {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.leftDecoration {
+ display: none;
+ pointer-events: none;
+
+ @include desktop {
+ display: block;
+ position: absolute;
+ top: 50%;
+ left: -17rem;
+ 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);
+ }
+ }
+}
+
+.rightDecoration {
+ display: none;
+ pointer-events: none;
+
+ @include desktop {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ position: absolute;
+ top: 50%;
+ right: -24rem;
+ opacity: 0;
+ transform: translateY(-50%) translateX(13rem);
+ @include transition(transform 1s, opacity 1s);
+
+ &.revealed {
+ opacity: 1;
+ transform: translateY(-50%) translateX(0);
+ }
+ }
+}
+
+.slash {
+ height: 19rem;
+ width: auto;
+}
+
+.angleBracket {
+ height: 14rem;
+ width: auto;
+}
+
+.heading {
+ font-family: $font-family-secondary;
+ font-weight: $font-weight-bold;
+ font-size: $font-size-md;
+ 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: $font-size-2xs;
+ line-height: 1.5rem;
+ text-align: center;
+ max-width: 60rem;
+ margin: 0 auto 2.5rem;
+
+ @include desktop {
+ font-size: $font-size-sm;
+ line-height: 2rem;
+ }
+}
+
+.pillRowsWrapper {
+ position: relative;
+}
+
+.pillRows {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.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;
+ gap: 1rem;
+}
diff --git a/components/services/WhatWeOffer/index.js b/components/services/WhatWeOffer/index.js
new file mode 100644
index 00000000..45835c9b
--- /dev/null
+++ b/components/services/WhatWeOffer/index.js
@@ -0,0 +1,149 @@
+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, enabled, onSelect }) {
+ const { viewportRef, groupRef, pause, resume } = useAutoScroll({
+ direction,
+ enabled,
+ });
+
+ return (
+
+
+
+ {pills.map(pill => (
+
onSelect(pill)}
+ />
+ ))}
+
+
+ {pills.map(pill => (
+
onSelect(pill)}
+ />
+ ))}
+
+
+
+ );
+}
+
+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.
+
+
+
+
+
+
+
+
+
+ {rowIndexes.map(rowIndex => (
+
+ ))}
+
+
setActiveService(null)}
+ titleId={titleId}
+ label={activeService?.label}
+ description={activeService?.description}
+ variant={activeService?.variant}
+ />
+
+
+
+ );
+}
diff --git a/hooks/useAutoScroll.js b/hooks/useAutoScroll.js
new file mode 100644
index 00000000..3ba5b548
--- /dev/null
+++ b/hooks/useAutoScroll.js
@@ -0,0 +1,56 @@
+import { useEffect, useRef } from 'react';
+
+export function useAutoScroll({
+ direction = 'left',
+ speed = 60,
+ enabled = true,
+} = {}) {
+ const viewportRef = useRef(null);
+ const groupRef = useRef(null);
+ const pausedRef = useRef(false);
+
+ useEffect(() => {
+ const viewport = viewportRef.current;
+ const group = groupRef.current;
+ if (!viewport || !group || !enabled) 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, enabled]);
+
+ const pause = () => {
+ pausedRef.current = true;
+ };
+ const resume = () => {
+ pausedRef.current = false;
+ };
+
+ return { viewportRef, groupRef, pause, resume };
+}
diff --git a/pages/services.js b/pages/services.js
new file mode 100644
index 00000000..56c1509c
--- /dev/null
+++ b/pages/services.js
@@ -0,0 +1,5 @@
+import WhatWeOffer from '@/components/services/WhatWeOffer';
+
+export default function Services() {
+ return ;
+}
diff --git a/styles/globals.scss b/styles/globals.scss
index b8be84c9..34745cba 100644
--- a/styles/globals.scss
+++ b/styles/globals.scss
@@ -12,8 +12,11 @@
--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);
+ --overlay-backdrop: rgba(0, 0, 0, 0.5);
--dark-bg: #023047;
--error: #be1313;
--shadow-default: 0px 8px 24px rgba(0, 0, 0, 0.15);
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.',
+ },
+];