feat: iPad portrait responsive layout — min-[820px] breakpoints across all blocks + tablet slide-in nav
This commit is contained in:
parent
7010e6519b
commit
313cfd31fb
@ -56,7 +56,6 @@ function groupChildren(children: NavChild[]): { heading: string | null; links: N
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Priority #4: Focus trap hook for mobile menu ──────────────────── */
|
|
||||||
function useFocusTrap(containerRef: React.RefObject<HTMLElement | null>, active: boolean) {
|
function useFocusTrap(containerRef: React.RefObject<HTMLElement | null>, active: boolean) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!active || !containerRef.current) return
|
if (!active || !containerRef.current) return
|
||||||
@ -68,10 +67,8 @@ function useFocusTrap(containerRef: React.RefObject<HTMLElement | null>, active:
|
|||||||
const getFocusableElements = () =>
|
const getFocusableElements = () =>
|
||||||
Array.from(container.querySelectorAll<HTMLElement>(focusableSelector))
|
Array.from(container.querySelectorAll<HTMLElement>(focusableSelector))
|
||||||
|
|
||||||
// Focus the first focusable element when trap activates
|
|
||||||
const elements = getFocusableElements()
|
const elements = getFocusableElements()
|
||||||
if (elements.length > 0) {
|
if (elements.length > 0) {
|
||||||
// Small delay to ensure the menu transition has started
|
|
||||||
setTimeout(() => elements[0]?.focus(), 100)
|
setTimeout(() => elements[0]?.focus(), 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,16 +99,13 @@ function useFocusTrap(containerRef: React.RefObject<HTMLElement | null>, active:
|
|||||||
}, [active, containerRef])
|
}, [active, containerRef])
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Priority #10: Check if a nav item matches current path ────────── */
|
|
||||||
function isActiveSection(item: NavItem, pathname: string | null): boolean {
|
function isActiveSection(item: NavItem, pathname: string | null): boolean {
|
||||||
if (!pathname || !item.children?.length) {
|
if (!pathname || !item.children?.length) {
|
||||||
// For top-level links without children, check direct href match
|
|
||||||
const href = resolveHref(item)
|
const href = resolveHref(item)
|
||||||
if (href === '/') return pathname === '/'
|
if (href === '/') return pathname === '/'
|
||||||
return pathname?.startsWith(href) || false
|
return pathname?.startsWith(href) || false
|
||||||
}
|
}
|
||||||
|
|
||||||
// For dropdown parents, check if any child href matches
|
|
||||||
return item.children.some((child) => {
|
return item.children.some((child) => {
|
||||||
const childHref = resolveHref(child)
|
const childHref = resolveHref(child)
|
||||||
if (childHref === '/' || childHref === '#') return false
|
if (childHref === '/' || childHref === '#') return false
|
||||||
@ -119,7 +113,6 @@ function isActiveSection(item: NavItem, pathname: string | null): boolean {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const MegaMenuPanel: React.FC<{
|
const MegaMenuPanel: React.FC<{
|
||||||
item: NavItem
|
item: NavItem
|
||||||
headerRef: React.RefObject<HTMLElement | null>
|
headerRef: React.RefObject<HTMLElement | null>
|
||||||
@ -155,7 +148,7 @@ const MegaMenuPanel: React.FC<{
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Panel — white in light, navy in dark */}
|
{/* Panel */}
|
||||||
<div
|
<div
|
||||||
ref={panelRef}
|
ref={panelRef}
|
||||||
className="fixed left-0 right-0 z-40 bg-white dark:bg-fd-navy border-b border-fd-navy/10 dark:border-white/10"
|
className="fixed left-0 right-0 z-40 bg-white dark:bg-fd-navy border-b border-fd-navy/10 dark:border-white/10"
|
||||||
@ -222,7 +215,6 @@ export const HeaderNav: React.FC<{ data: HeaderType; socialLinks?: SocialLinkDat
|
|||||||
const megaMenuRef = useRef<HTMLDivElement | null>(null)
|
const megaMenuRef = useRef<HTMLDivElement | null>(null)
|
||||||
const mobileMenuRef = useRef<HTMLDivElement | null>(null)
|
const mobileMenuRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
// Priority #4: Focus trap
|
|
||||||
useFocusTrap(mobileMenuRef, mobileOpen)
|
useFocusTrap(mobileMenuRef, mobileOpen)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -275,13 +267,12 @@ export const HeaderNav: React.FC<{ data: HeaderType; socialLinks?: SocialLinkDat
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* ── Desktop nav ── */}
|
{/* ── Desktop nav — shows at lg (1024px+) ── */}
|
||||||
<nav ref={navRef} className="hidden md:flex gap-6 items-center">
|
<nav ref={navRef} className="hidden lg:flex gap-6 items-center">
|
||||||
{navItems.map((item, i) => {
|
{navItems.map((item, i) => {
|
||||||
const hasChildren = item.children && item.children.length > 0
|
const hasChildren = item.children && item.children.length > 0
|
||||||
const isOpen = openDropdown === item.label
|
const isOpen = openDropdown === item.label
|
||||||
const isMega = item.megaMenu && hasChildren
|
const isMega = item.megaMenu && hasChildren
|
||||||
// Priority #10: Active section detection
|
|
||||||
const isActive = isActiveSection(item, pathname)
|
const isActive = isActiveSection(item, pathname)
|
||||||
|
|
||||||
return hasChildren ? (
|
return hasChildren ? (
|
||||||
@ -302,7 +293,6 @@ export const HeaderNav: React.FC<{ data: HeaderType; socialLinks?: SocialLinkDat
|
|||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className={`w-4 h-4 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
|
className={`w-4 h-4 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
|
||||||
/>
|
/>
|
||||||
{/* Active indicator dot */}
|
|
||||||
{isActive && !isOpen && (
|
{isActive && !isOpen && (
|
||||||
<span className="absolute -bottom-1 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-fd-yellow" />
|
<span className="absolute -bottom-1 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-fd-yellow" />
|
||||||
)}
|
)}
|
||||||
@ -361,27 +351,42 @@ export const HeaderNav: React.FC<{ data: HeaderType; socialLinks?: SocialLinkDat
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ── Mobile hamburger ── */}
|
{/* ── Hamburger — shows below lg (mobile + tablet) ── */}
|
||||||
<button
|
<button
|
||||||
className="md:hidden text-fd-navy dark:text-white p-2"
|
className="lg:hidden text-fd-navy dark:text-white p-2"
|
||||||
onClick={() => setMobileOpen(!mobileOpen)}
|
onClick={() => setMobileOpen(!mobileOpen)}
|
||||||
aria-label={mobileOpen ? 'Stäng meny' : 'Öppna meny'}
|
aria-label={mobileOpen ? 'Stäng meny' : 'Öppna meny'}
|
||||||
>
|
>
|
||||||
{mobileOpen ? <XIcon className="w-6 h-6" /> : <MenuIcon className="w-6 h-6" />}
|
{mobileOpen ? <XIcon className="w-6 h-6" /> : <MenuIcon className="w-6 h-6" />}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* ── Mobile full-screen overlay (Priority #4: has focus trap via ref) ── */}
|
{/* ── Tablet backdrop — blur overlay left of panel on md+ ── */}
|
||||||
|
<div
|
||||||
|
className={`fixed inset-0 z-40 lg:hidden transition-opacity duration-300 ${
|
||||||
|
mobileOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'
|
||||||
|
}`}
|
||||||
|
style={{ backdropFilter: 'blur(4px)', WebkitBackdropFilter: 'blur(4px)', backgroundColor: 'rgba(14, 35, 56, 0.45)' }}
|
||||||
|
onClick={closeMobile}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* ── Mobile/tablet slide-in panel ──
|
||||||
|
Mobile (<768px): full-screen overlay
|
||||||
|
Tablet (768-1023px): right-side panel, 420px wide, with backdrop above
|
||||||
|
── */}
|
||||||
<div
|
<div
|
||||||
ref={mobileMenuRef}
|
ref={mobileMenuRef}
|
||||||
className={`fixed inset-0 z-50 bg-fd-navy flex flex-col transition-transform duration-300 md:hidden ${
|
className={`fixed inset-y-0 right-0 z-50 flex flex-col bg-fd-navy lg:hidden
|
||||||
mobileOpen ? 'translate-x-0' : 'translate-x-full'
|
w-full md:w-[420px]
|
||||||
}`}
|
transition-transform duration-300
|
||||||
|
${mobileOpen ? 'translate-x-0 md:shadow-[-24px_0_60px_rgba(0,0,0,0.25)]' : 'translate-x-full'}
|
||||||
|
`}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-label="Navigeringsmeny"
|
aria-label="Navigeringsmeny"
|
||||||
>
|
>
|
||||||
{/* Header bar */}
|
{/* Header bar */}
|
||||||
<div className="flex items-center justify-between px-6 py-5 border-b border-white/10">
|
<div className="flex items-center justify-between px-6 md:px-8 py-5 border-b border-white/10">
|
||||||
<Link href="/" onClick={closeMobile}>
|
<Link href="/" onClick={closeMobile}>
|
||||||
<span className="font-joey-heavy text-fd-yellow text-xl">Fiber Direkt</span>
|
<span className="font-joey-heavy text-fd-yellow text-xl">Fiber Direkt</span>
|
||||||
</Link>
|
</Link>
|
||||||
@ -395,7 +400,7 @@ export const HeaderNav: React.FC<{ data: HeaderType; socialLinks?: SocialLinkDat
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Nav links */}
|
{/* Nav links */}
|
||||||
<nav className="flex-1 overflow-y-auto px-6 py-6 flex flex-col">
|
<nav className="flex-1 overflow-y-auto px-6 md:px-8 py-6 flex flex-col">
|
||||||
{navItems.map((item, i) => {
|
{navItems.map((item, i) => {
|
||||||
const hasChildren = item.children && item.children.length > 0
|
const hasChildren = item.children && item.children.length > 0
|
||||||
const isExpanded = mobileOpenSection === item.label
|
const isExpanded = mobileOpenSection === item.label
|
||||||
@ -404,7 +409,7 @@ export const HeaderNav: React.FC<{ data: HeaderType; socialLinks?: SocialLinkDat
|
|||||||
<div key={i} className="border-b border-white/10">
|
<div key={i} className="border-b border-white/10">
|
||||||
<button
|
<button
|
||||||
onClick={() => setMobileOpenSection(isExpanded ? null : item.label ?? null)}
|
onClick={() => setMobileOpenSection(isExpanded ? null : item.label ?? null)}
|
||||||
className="w-full flex justify-between items-center py-4 text-white font-joey-medium text-xl hover:text-fd-yellow transition-colors"
|
className="w-full flex justify-between items-center py-4 md:py-5 text-white font-joey-medium text-xl md:text-2xl hover:text-fd-yellow transition-colors"
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
@ -421,7 +426,7 @@ export const HeaderNav: React.FC<{ data: HeaderType; socialLinks?: SocialLinkDat
|
|||||||
key={j}
|
key={j}
|
||||||
href={resolveHref(child)}
|
href={resolveHref(child)}
|
||||||
onClick={closeMobile}
|
onClick={closeMobile}
|
||||||
className="block pl-4 py-3 text-fd-yellow font-joey text-lg border-b border-white/5 hover:text-white transition-colors"
|
className="block pl-4 py-3 md:py-3.5 text-fd-yellow font-joey text-lg md:text-xl border-b border-white/5 hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
{child.label}
|
{child.label}
|
||||||
</Link>
|
</Link>
|
||||||
@ -433,7 +438,7 @@ export const HeaderNav: React.FC<{ data: HeaderType; socialLinks?: SocialLinkDat
|
|||||||
key={i}
|
key={i}
|
||||||
href={resolveHref(item)}
|
href={resolveHref(item)}
|
||||||
onClick={closeMobile}
|
onClick={closeMobile}
|
||||||
className="py-4 text-white font-joey-medium text-xl border-b border-white/10 hover:text-fd-yellow transition-colors block"
|
className="py-4 md:py-5 text-white font-joey-medium text-xl md:text-2xl border-b border-white/10 hover:text-fd-yellow transition-colors block"
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</Link>
|
</Link>
|
||||||
@ -441,8 +446,8 @@ export const HeaderNav: React.FC<{ data: HeaderType; socialLinks?: SocialLinkDat
|
|||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* ── Mobile menu footer: flag left, social icons right ── */}
|
{/* Footer: Swedish flag left, social icons right */}
|
||||||
<div className="px-6 py-5 border-t border-white/10 flex items-center justify-between">
|
<div className="px-6 md:px-8 py-5 border-t border-white/10 flex items-center justify-between">
|
||||||
<SwedishFlag />
|
<SwedishFlag />
|
||||||
{socialLinks.length > 0 && (
|
{socialLinks.length > 0 && (
|
||||||
<SocialIconsRow
|
<SocialIconsRow
|
||||||
|
|||||||
@ -50,10 +50,10 @@ const sectionBgMap: Record<string, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const layoutGridMap: Record<string, string> = {
|
const layoutGridMap: Record<string, string> = {
|
||||||
'1-2': 'lg:grid-cols-[1fr_2fr]',
|
'1-2': 'min-[820px]:grid-cols-[1fr_2fr]',
|
||||||
'2-1': 'lg:grid-cols-[2fr_1fr]',
|
'2-1': 'min-[820px]:grid-cols-[2fr_1fr]',
|
||||||
'1-1-1': 'lg:grid-cols-3',
|
'1-1-1': 'min-[820px]:grid-cols-3',
|
||||||
'1-1': 'lg:grid-cols-2',
|
'1-1': 'min-[820px]:grid-cols-2',
|
||||||
}
|
}
|
||||||
|
|
||||||
const styleClassMap: Record<string, string> = {
|
const styleClassMap: Record<string, string> = {
|
||||||
@ -80,7 +80,7 @@ export const FDCardGridBlockComponent: React.FC<FDCardGridBlockProps> = ({
|
|||||||
return (
|
return (
|
||||||
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
|
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
|
||||||
<div className="max-w-[1200px] mx-auto px-6 md:px-8">
|
<div className="max-w-[1200px] mx-auto px-6 md:px-8">
|
||||||
<div className={`grid grid-cols-1 md:grid-cols-2 ${gridCols} gap-4 md:gap-6`}>
|
<div className={`grid grid-cols-1 ${gridCols} gap-4 md:gap-6`}>
|
||||||
{cards?.map((card, index) => {
|
{cards?.map((card, index) => {
|
||||||
const mode = card.displayMode || 'content'
|
const mode = card.displayMode || 'content'
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export const FDCtaSideImageBlockComponent: React.FC<FDCtaSideImageBlockProps> =
|
|||||||
: 'text-fd-navy dark:text-white'
|
: 'text-fd-navy dark:text-white'
|
||||||
|
|
||||||
const textContent = (
|
const textContent = (
|
||||||
<div className={`flex flex-col flex-1 items-start gap-8 lg:gap-[41px] ${!hasImage ? 'max-w-2xl' : ''}`}>
|
<div className={`flex flex-col flex-1 items-start gap-8 min-[820px]:gap-[41px] ${!hasImage ? 'max-w-2xl' : ''}`}>
|
||||||
<div className="flex flex-col items-start gap-4 w-full">
|
<div className="flex flex-col items-start gap-4 w-full">
|
||||||
<h2 className={`w-full font-joey-heavy text-fd-h1 leading-tight ${headingClass}`}>
|
<h2 className={`w-full font-joey-heavy text-fd-h1 leading-tight ${headingClass}`}>
|
||||||
{heading}
|
{heading}
|
||||||
@ -67,7 +67,7 @@ export const FDCtaSideImageBlockComponent: React.FC<FDCtaSideImageBlockProps> =
|
|||||||
)
|
)
|
||||||
|
|
||||||
const imageContent = hasImage ? (
|
const imageContent = hasImage ? (
|
||||||
<div className={`relative w-full lg:w-[575px] lg:h-[479px] flex-shrink-0 overflow-hidden ${imageRadius}`}>
|
<div className={`relative w-full min-[820px]:w-[45%] lg:w-[575px] lg:h-[479px] flex-shrink-0 overflow-hidden ${imageRadius}`}>
|
||||||
<FDImage
|
<FDImage
|
||||||
media={image as Media}
|
media={image as Media}
|
||||||
size="large"
|
size="large"
|
||||||
@ -81,7 +81,7 @@ export const FDCtaSideImageBlockComponent: React.FC<FDCtaSideImageBlockProps> =
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
|
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
|
||||||
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col lg:flex-row items-center gap-10 lg:gap-16">
|
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col min-[820px]:flex-row items-center gap-10 min-[820px]:gap-16">
|
||||||
{imagePosition === 'left' ? (
|
{imagePosition === 'left' ? (
|
||||||
<>{imageContent}{textContent}</>
|
<>{imageContent}{textContent}</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export const FDLocationsGridBlockComponent: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 min-[820px]:grid-cols-3 gap-4 md:gap-6">
|
||||||
{(cards ?? []).map((card, i) => {
|
{(cards ?? []).map((card, i) => {
|
||||||
const media = card.image as Media | undefined
|
const media = card.image as Media | undefined
|
||||||
const isLink = Boolean(card.link)
|
const isLink = Boolean(card.link)
|
||||||
|
|||||||
@ -64,9 +64,9 @@ const buttonVariantMap: Record<string, { variant: 'primary' | 'outline' }> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const gridColsMap: Record<number, string> = {
|
const gridColsMap: Record<number, string> = {
|
||||||
1: 'lg:grid-cols-1 max-w-[500px] mx-auto',
|
1: 'min-[820px]:grid-cols-1 max-w-[500px] mx-auto',
|
||||||
2: 'lg:grid-cols-2',
|
2: 'min-[820px]:grid-cols-2',
|
||||||
3: 'lg:grid-cols-3',
|
3: 'min-[820px]:grid-cols-3',
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
|
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
|
||||||
@ -96,11 +96,11 @@ export const FDPricingCardBlockComponent: React.FC<FDPricingCardBlockProps> = ({
|
|||||||
{sectionTitle}
|
{sectionTitle}
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
<div className={`grid grid-cols-1 ${gridCols} gap-6 md:gap-8`}>
|
<div className={`grid grid-cols-1 ${gridCols} gap-4 min-[820px]:gap-5 lg:gap-8`}>
|
||||||
{cards?.map((card, index) => (
|
{cards?.map((card, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`${style.bg} ${style.border} ${cardRadius} px-8 md:px-12 py-10 md:py-14 flex flex-col gap-5`}
|
className={`${style.bg} ${style.border} ${cardRadius} px-6 min-[820px]:px-8 lg:px-12 py-8 min-[820px]:py-10 lg:py-14 flex flex-col gap-5`}
|
||||||
>
|
>
|
||||||
<h3 className={`font-joey-heavy text-fd-h2 leading-tight ${style.title}`}>
|
<h3 className={`font-joey-heavy text-fd-h2 leading-tight ${style.title}`}>
|
||||||
{card.title}
|
{card.title}
|
||||||
|
|||||||
@ -219,10 +219,10 @@ export const FDServiceCalculatorBlockComponent: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6 lg:gap-8">
|
<div className="grid grid-cols-1 min-[820px]:grid-cols-5 gap-6 min-[820px]:gap-8">
|
||||||
|
|
||||||
{/* ── Left: Configuration ─────────────────────────────────── */}
|
{/* ── Left: Configuration ─────────────────────────────────── */}
|
||||||
<div className={`lg:col-span-3 p-7 md:p-9 ${cardClass}`}>
|
<div className={`min-[820px]:col-span-3 p-7 md:p-9 ${cardClass}`}>
|
||||||
|
|
||||||
{/* Option groups */}
|
{/* Option groups */}
|
||||||
{(optionGroups ?? []).map((group, gi) => (
|
{(optionGroups ?? []).map((group, gi) => (
|
||||||
@ -312,7 +312,7 @@ export const FDServiceCalculatorBlockComponent: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── Right: Summary ──────────────────────────────────────── */}
|
{/* ── Right: Summary ──────────────────────────────────────── */}
|
||||||
<div className={`lg:col-span-2 p-7 md:p-9 flex flex-col ${cardClass}`}>
|
<div className={`min-[820px]:col-span-2 p-7 md:p-9 flex flex-col ${cardClass}`}>
|
||||||
<h3 className={`font-joey-heavy text-fd-h2 mb-6 ${headingColor}`}>{summaryHeading}</h3>
|
<h3 className={`font-joey-heavy text-fd-h2 mb-6 ${headingColor}`}>{summaryHeading}</h3>
|
||||||
|
|
||||||
<div className="flex-1 space-y-0">
|
<div className="flex-1 space-y-0">
|
||||||
|
|||||||
@ -100,7 +100,7 @@ export const FDServiceChooserBlockComponent: React.FC<Props> = ({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 min-[820px]:grid-cols-3 gap-4 md:gap-6">
|
||||||
{activeCategory?.services?.map((service, i) => (
|
{activeCategory?.services?.map((service, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import { FDImage } from '@/components/FDImage'
|
|||||||
|
|
||||||
const columnClasses: Record<string, string> = {
|
const columnClasses: Record<string, string> = {
|
||||||
'2': 'grid-cols-1 md:grid-cols-2',
|
'2': 'grid-cols-1 md:grid-cols-2',
|
||||||
'3': 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
'3': 'grid-cols-1 md:grid-cols-2 min-[820px]:grid-cols-3',
|
||||||
'4': 'grid-cols-2 lg:grid-cols-4',
|
'4': 'grid-cols-2 min-[820px]:grid-cols-4',
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Priority #5: Responsive radius for service images */
|
/* Priority #5: Responsive radius for service images */
|
||||||
|
|||||||
@ -34,8 +34,8 @@ const cardMap: Record<string, { bg: string; name: string; role: string; bio: str
|
|||||||
|
|
||||||
const colsMap: Record<string, string> = {
|
const colsMap: Record<string, string> = {
|
||||||
'2': 'sm:grid-cols-2',
|
'2': 'sm:grid-cols-2',
|
||||||
'3': 'sm:grid-cols-2 lg:grid-cols-3',
|
'3': 'sm:grid-cols-2 min-[820px]:grid-cols-3',
|
||||||
'4': 'sm:grid-cols-2 lg:grid-cols-4',
|
'4': 'sm:grid-cols-2 min-[820px]:grid-cols-4',
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Priority #5: Responsive radius for team member cards */
|
/* Priority #5: Responsive radius for team member cards */
|
||||||
|
|||||||
@ -121,7 +121,7 @@ export const FDTestimonialBlockComponent: React.FC<FDTestimonialBlockProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 min-[820px]:grid-cols-3 gap-6">
|
||||||
{testimonials?.map((t, i) => {
|
{testimonials?.map((t, i) => {
|
||||||
const avatar = t.avatar as Media | undefined
|
const avatar = t.avatar as Media | undefined
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -78,7 +78,7 @@ export const FDUspChecklistBlockComponent: React.FC<FDUspChecklistBlockProps> =
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
|
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
|
||||||
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col lg:flex-row items-center gap-10 lg:gap-16">
|
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col min-[820px]:flex-row items-center gap-10 min-[820px]:gap-16">
|
||||||
{imagePosition === 'left' ? <>{imageContent}{textContent}</> : <>{textContent}{imageContent}</>}
|
{imagePosition === 'left' ? <>{imageContent}{textContent}</> : <>{textContent}{imageContent}</>}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -169,10 +169,10 @@ export const FDVpsCalculatorBlockComponent: React.FC<FDVpsCalculatorBlockProps>
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6 lg:gap-8">
|
<div className="grid grid-cols-1 min-[820px]:grid-cols-5 gap-6 min-[820px]:gap-8">
|
||||||
|
|
||||||
{/* Left: Config */}
|
{/* Left: Config */}
|
||||||
<div className={`lg:col-span-3 p-7 md:p-9 ${cardClass}`}>
|
<div className={`min-[820px]:col-span-3 p-7 md:p-9 ${cardClass}`}>
|
||||||
|
|
||||||
<p className={`font-joey-medium text-fd-body tracking-widest uppercase mb-3 ${sectionLabel}`}>
|
<p className={`font-joey-medium text-fd-body tracking-widest uppercase mb-3 ${sectionLabel}`}>
|
||||||
Operativsystem
|
Operativsystem
|
||||||
@ -225,7 +225,7 @@ export const FDVpsCalculatorBlockComponent: React.FC<FDVpsCalculatorBlockProps>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: Summary */}
|
{/* Right: Summary */}
|
||||||
<div className={`lg:col-span-2 p-7 md:p-9 flex flex-col ${cardClass}`}>
|
<div className={`min-[820px]:col-span-2 p-7 md:p-9 flex flex-col ${cardClass}`}>
|
||||||
<h3 className={`font-joey-heavy text-fd-h2 mb-6 ${headingColor}`}>Kostnadsöversikt</h3>
|
<h3 className={`font-joey-heavy text-fd-h2 mb-6 ${headingColor}`}>Kostnadsöversikt</h3>
|
||||||
|
|
||||||
<div className="flex-1 space-y-0">
|
<div className="flex-1 space-y-0">
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export const FDWideCardBlockComponent: React.FC<FDWideCardBlockProps> = ({
|
|||||||
return (
|
return (
|
||||||
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
|
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
|
||||||
<div className="max-w-[1200px] mx-auto px-6 md:px-8">
|
<div className="max-w-[1200px] mx-auto px-6 md:px-8">
|
||||||
<div className={`${card.bg} ${cardRadius} overflow-hidden flex flex-col lg:flex-row`}>
|
<div className={`${card.bg} ${cardRadius} overflow-hidden flex flex-col min-[820px]:flex-row`}>
|
||||||
<div className="flex-1 flex flex-col justify-center gap-5 md:gap-6 px-8 md:px-14 lg:px-16 py-12 md:py-16">
|
<div className="flex-1 flex flex-col justify-center gap-5 md:gap-6 px-8 md:px-14 lg:px-16 py-12 md:py-16">
|
||||||
<h2 className={`font-joey-heavy text-fd-h1 leading-tight ${card.heading}`}>
|
<h2 className={`font-joey-heavy text-fd-h1 leading-tight ${card.heading}`}>
|
||||||
{heading}
|
{heading}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user