@notionhive/menus 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/bin/menus.js +16 -0
  2. package/category.config.json +7 -0
  3. package/package.json +24 -0
  4. package/registry/index.json +74 -0
  5. package/registry/menu-01.json +14 -0
  6. package/registry/menu-02.json +12 -0
  7. package/registry/menu-03.json +12 -0
  8. package/registry/menu-04.json +10 -0
  9. package/registry/menu-05.json +10 -0
  10. package/registry/menu-06.json +10 -0
  11. package/templates/components/atoms/SafeImage/SafeImage.jsx +101 -0
  12. package/templates/components/atoms/SafeImage/index.js +1 -0
  13. package/templates/components/hooks/useCarousel.js +73 -0
  14. package/templates/components/molecules/CorporateHeader/CorporateHeader.jsx +174 -0
  15. package/templates/components/molecules/CorporateHeader/CorporateHeader.propTypes.js +22 -0
  16. package/templates/components/molecules/CorporateHeader/index.js +1 -0
  17. package/templates/components/molecules/EquipmentMegaMenu/EquipmentMegaMenu.jsx +202 -0
  18. package/templates/components/molecules/EquipmentMegaMenu/EquipmentMegaMenu.propTypes.js +104 -0
  19. package/templates/components/molecules/EquipmentMegaMenu/index.js +5 -0
  20. package/templates/components/molecules/FullScreenNavOverlay/FullScreenNavOverlay.jsx +117 -0
  21. package/templates/components/molecules/FullScreenNavOverlay/FullScreenNavOverlay.propTypes.js +34 -0
  22. package/templates/components/molecules/FullScreenNavOverlay/index.js +2 -0
  23. package/templates/components/molecules/MobileNavDrawer/MobileNavDrawer.jsx +139 -0
  24. package/templates/components/molecules/MobileNavDrawer/index.js +2 -0
  25. package/templates/components/molecules/SiteHeader/SiteHeader.jsx +177 -0
  26. package/templates/components/molecules/SiteHeader/SiteHeader.propTypes.js +20 -0
  27. package/templates/components/molecules/SiteHeader/index.js +1 -0
  28. package/templates/components/molecules/SlideIndicators/SlideIndicators.jsx +75 -0
  29. package/templates/components/molecules/SlideIndicators/SlideIndicators.propTypes.js +10 -0
  30. package/templates/components/molecules/SlideIndicators/index.js +1 -0
  31. package/templates/components/organisms/Menu01/Menu01.jsx +211 -0
  32. package/templates/components/organisms/Menu01/Menu01.propTypes.js +63 -0
  33. package/templates/components/organisms/Menu01/index.js +1 -0
  34. package/templates/components/organisms/Menu02/Menu02.jsx +133 -0
  35. package/templates/components/organisms/Menu02/Menu02.propTypes.js +71 -0
  36. package/templates/components/organisms/Menu02/index.js +1 -0
  37. package/templates/components/organisms/Menu03/Menu03.jsx +411 -0
  38. package/templates/components/organisms/Menu03/Menu03.propTypes.js +113 -0
  39. package/templates/components/organisms/Menu03/index.js +1 -0
  40. package/templates/components/organisms/Menu04/Menu04.jsx +232 -0
  41. package/templates/components/organisms/Menu04/Menu04.propTypes.js +53 -0
  42. package/templates/components/organisms/Menu04/index.js +2 -0
  43. package/templates/components/organisms/Menu05/Menu05.jsx +318 -0
  44. package/templates/components/organisms/Menu05/Menu05.propTypes.js +79 -0
  45. package/templates/components/organisms/Menu05/index.js +2 -0
  46. package/templates/components/organisms/Menu06/Menu06.jsx +241 -0
  47. package/templates/components/organisms/Menu06/Menu06.propTypes.js +55 -0
  48. package/templates/components/organisms/Menu06/index.js +2 -0
  49. package/templates/public/menus/menu01/agriculture.png +0 -0
  50. package/templates/public/menus/menu01/banner.jpg +0 -0
  51. package/templates/public/menus/menu01/build-your-own.png +0 -0
  52. package/templates/public/menus/menu01/construction.png +0 -0
  53. package/templates/public/menus/menu01/lawn-care.png +0 -0
  54. package/templates/public/menus/menu01/logo.png +0 -0
  55. package/templates/public/menus/menu01/promotions.png +0 -0
  56. package/templates/public/menus/menu03/logo.png +46 -0
  57. package/templates/public/menus/menu04/featured.jpg +0 -0
  58. package/templates/public/menus/menu04/logo.png +0 -0
  59. package/templates/public/menus/menu05/hero-bg.jpg +0 -0
  60. package/templates/public/menus/menu05/logo.png +0 -0
  61. package/templates/public/menus/menu06/pool.jpg +0 -0
@@ -0,0 +1,318 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import SafeImage from "../../atoms/SafeImage";
5
+ import { MobileNavDrawer } from "../../molecules/MobileNavDrawer";
6
+ import { menu05DefaultProps, menu05PropTypes } from "./Menu05.propTypes";
7
+
8
+ function ChevronDownIcon({ className = "" }) {
9
+ return (
10
+ <svg
11
+ width="20"
12
+ height="20"
13
+ viewBox="0 0 20 20"
14
+ fill="none"
15
+ aria-hidden="true"
16
+ className={className}
17
+ >
18
+ <path
19
+ d="M5 7.5L10 12.5L15 7.5"
20
+ stroke="currentColor"
21
+ strokeWidth="1.5"
22
+ strokeLinecap="round"
23
+ strokeLinejoin="round"
24
+ />
25
+ </svg>
26
+ );
27
+ }
28
+
29
+ function ChevronRightIcon({ className = "" }) {
30
+ return (
31
+ <svg
32
+ width="20"
33
+ height="20"
34
+ viewBox="0 0 20 20"
35
+ fill="none"
36
+ aria-hidden="true"
37
+ className={className}
38
+ >
39
+ <path
40
+ d="M7.5 5L12.5 10L7.5 15"
41
+ stroke="currentColor"
42
+ strokeWidth="1.5"
43
+ strokeLinecap="round"
44
+ strokeLinejoin="round"
45
+ />
46
+ </svg>
47
+ );
48
+ }
49
+
50
+ function PhoneIcon() {
51
+ return (
52
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
53
+ <path
54
+ d="M12.5 9.8V11.3C12.5 11.74 12.16 12.1 11.73 12.1C6.55 12.1 2.4 7.95 2.4 2.77C2.4 2.34 2.76 2 3.2 2H4.7C5.05 2 5.35 2.24 5.42 2.58L5.78 4.48C5.84 4.78 5.74 5.09 5.52 5.29L4.62 6.19C5.48 7.78 6.72 9.02 8.31 9.88L9.21 8.98C9.41 8.76 9.72 8.66 10.02 8.72L11.92 9.08C12.26 9.15 12.5 9.45 12.5 9.8Z"
55
+ stroke="currentColor"
56
+ strokeWidth="1.2"
57
+ strokeLinecap="round"
58
+ strokeLinejoin="round"
59
+ />
60
+ </svg>
61
+ );
62
+ }
63
+
64
+ function HamburgerIcon() {
65
+ return (
66
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
67
+ <path d="M4 7H20M4 12H20M4 17H20" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
68
+ </svg>
69
+ );
70
+ }
71
+
72
+ /**
73
+ * Menu05 — Oxford College header with utility bar, hero section,
74
+ * and About Oxford dropdown panel.
75
+ *
76
+ * @param {object} props - See Menu05.propTypes.js
77
+ */
78
+ export function Menu05({
79
+ utilityLinks = menu05DefaultProps.utilityLinks,
80
+ phone = menu05DefaultProps.phone,
81
+ logoSrc = menu05DefaultProps.logoSrc,
82
+ logoAlt = menu05DefaultProps.logoAlt,
83
+ navItems = menu05DefaultProps.navItems,
84
+ ctaText = menu05DefaultProps.ctaText,
85
+ onCtaClick,
86
+ defaultDropdownOpen = menu05DefaultProps.defaultDropdownOpen,
87
+ dropdownLinks = menu05DefaultProps.dropdownLinks,
88
+ heroImage = menu05DefaultProps.heroImage,
89
+ heroImageAlt = menu05DefaultProps.heroImageAlt,
90
+ headline = menu05DefaultProps.headline,
91
+ description = menu05DefaultProps.description,
92
+ primaryCtaText = menu05DefaultProps.primaryCtaText,
93
+ secondaryCtaText = menu05DefaultProps.secondaryCtaText,
94
+ onPrimaryCtaClick,
95
+ onSecondaryCtaClick,
96
+ onMenuClick,
97
+ className = "",
98
+ }) {
99
+ const [dropdownOpen, setDropdownOpen] = useState(defaultDropdownOpen);
100
+ const [menuOpen, setMenuOpen] = useState(false);
101
+
102
+ const drawerItems = [
103
+ ...navItems.map((item) => ({ label: item.label, href: item.href, onClick: item.onClick })),
104
+ ...dropdownLinks.map((item) => ({ label: item.label, href: item.href, onClick: item.onClick })),
105
+ ];
106
+
107
+ return (
108
+ <section
109
+ className={["relative w-full overflow-hidden bg-[#f7f7f7]", className]
110
+ .filter(Boolean)
111
+ .join(" ")}
112
+ data-menu="menu05"
113
+ >
114
+ {/* Utility bar */}
115
+ <div className="relative z-30 bg-[#0f1e40] px-4 py-3 sm:px-6 md:px-10 lg:px-[120px]">
116
+ <div className="ml-auto flex max-w-full flex-wrap items-center justify-end gap-2 text-sm text-white sm:gap-2.5">
117
+ {utilityLinks.map((link, index) => (
118
+ <span key={link.label} className="flex items-center gap-2.5">
119
+ {index > 0 ? (
120
+ <span className="text-white/40" aria-hidden="true">
121
+ |
122
+ </span>
123
+ ) : null}
124
+ {link.href ? (
125
+ <a
126
+ href={link.href}
127
+ className="whitespace-nowrap transition-opacity duration-200 ease-out hover:opacity-80"
128
+ >
129
+ {link.label}
130
+ </a>
131
+ ) : (
132
+ <button
133
+ type="button"
134
+ onClick={link.onClick}
135
+ className="whitespace-nowrap transition-opacity duration-200 ease-out hover:opacity-80"
136
+ >
137
+ {link.label}
138
+ </button>
139
+ )}
140
+ </span>
141
+ ))}
142
+ <span className="hidden text-white/40 sm:inline" aria-hidden="true">
143
+ |
144
+ </span>
145
+ <a
146
+ href={`tel:${phone.replace(/[^0-9+]/g, "")}`}
147
+ className="inline-flex items-center gap-1 transition-opacity duration-200 ease-out hover:opacity-80"
148
+ >
149
+ <PhoneIcon />
150
+ <span className="whitespace-nowrap">{phone}</span>
151
+ </a>
152
+ </div>
153
+ </div>
154
+
155
+ {/* Main header */}
156
+ <header className="relative z-30 bg-white px-4 py-3 sm:px-6 md:px-10 lg:px-[120px] lg:py-2.5">
157
+ <div className="mx-auto flex w-full max-w-[1680px] items-center justify-between gap-4">
158
+ <div className="relative h-10 w-[200px] shrink-0 sm:h-[53px] sm:w-[280px] lg:w-[337px]">
159
+ <SafeImage
160
+ src={logoSrc}
161
+ alt={logoAlt}
162
+ fill
163
+ className="object-contain object-left"
164
+ sizes="337px"
165
+ priority
166
+ />
167
+ </div>
168
+
169
+ <div className="hidden items-center gap-6 lg:flex">
170
+ <nav className="flex items-center gap-[34px]">
171
+ {navItems.map((item) => {
172
+ const isAbout = item.label === "About Oxford";
173
+
174
+ return (
175
+ <div key={item.label} className="relative">
176
+ <button
177
+ type="button"
178
+ onClick={() => {
179
+ if (isAbout) setDropdownOpen((open) => !open);
180
+ item.onClick?.();
181
+ }}
182
+ className={[
183
+ "inline-flex items-center gap-1 text-lg font-medium text-[#0f1e40] transition-colors duration-200 ease-out hover:text-[#0f1e40]/70",
184
+ isAbout && dropdownOpen ? "text-[#0f1e40]" : "",
185
+ ].join(" ")}
186
+ aria-expanded={isAbout ? dropdownOpen : undefined}
187
+ >
188
+ {item.label}
189
+ {item.hasDropdown ? <ChevronDownIcon /> : null}
190
+ </button>
191
+
192
+ {isAbout && dropdownOpen ? (
193
+ <div className="absolute left-0 top-full z-40 mt-3 w-[min(395px,calc(100vw-2rem))] border-t-[3px] border-[#eaaf51] bg-white p-5 shadow-lg animate-fade-in">
194
+ <ul className="flex flex-col gap-5">
195
+ {dropdownLinks.map((link, index) => (
196
+ <li key={link.label}>
197
+ {index > 0 ? (
198
+ <div className="mb-5 h-px bg-neutral-200" aria-hidden="true" />
199
+ ) : null}
200
+ <div className="flex items-center justify-between gap-3">
201
+ {link.href ? (
202
+ <a
203
+ href={link.href}
204
+ className="text-sm text-[#0f1e40] transition-colors duration-200 ease-out hover:text-[#0f1e40]/70"
205
+ >
206
+ {link.label}
207
+ </a>
208
+ ) : (
209
+ <button
210
+ type="button"
211
+ onClick={link.onClick}
212
+ className="text-left text-sm text-[#0f1e40] transition-colors duration-200 ease-out hover:text-[#0f1e40]/70"
213
+ >
214
+ {link.label}
215
+ </button>
216
+ )}
217
+ <ChevronRightIcon className="shrink-0 text-neutral-400" />
218
+ </div>
219
+ </li>
220
+ ))}
221
+ </ul>
222
+ </div>
223
+ ) : null}
224
+ </div>
225
+ );
226
+ })}
227
+ </nav>
228
+
229
+ <button
230
+ type="button"
231
+ onClick={onCtaClick}
232
+ className="shrink-0 rounded-[99px] bg-[#eaaf51] px-8 py-[18px] text-lg font-medium tracking-[-0.02em] text-[#0f1e40] transition-colors duration-200 ease-out hover:bg-[#e0a647] focus-visible:outline-2 focus-visible:outline-offset-2"
233
+ >
234
+ {ctaText}
235
+ </button>
236
+ </div>
237
+
238
+ <div className="flex items-center gap-3 lg:hidden">
239
+ <button
240
+ type="button"
241
+ onClick={onCtaClick}
242
+ className="rounded-[99px] bg-[#eaaf51] px-4 py-2.5 text-sm font-medium text-[#0f1e40] transition-colors duration-200 ease-out hover:bg-[#e0a647]"
243
+ >
244
+ {ctaText}
245
+ </button>
246
+ <button
247
+ type="button"
248
+ onClick={() => {
249
+ setMenuOpen(true);
250
+ onMenuClick?.();
251
+ }}
252
+ aria-label="Open menu"
253
+ className="rounded-lg p-2 text-[#0f1e40] transition-colors duration-200 ease-out hover:bg-neutral-100 focus-visible:outline-2 focus-visible:outline-offset-2"
254
+ >
255
+ <HamburgerIcon />
256
+ </button>
257
+ </div>
258
+ </div>
259
+ </header>
260
+
261
+ <MobileNavDrawer
262
+ isOpen={menuOpen}
263
+ onClose={() => setMenuOpen(false)}
264
+ navItems={drawerItems}
265
+ accentColor="#eaaf51"
266
+ />
267
+
268
+ {/* Hero */}
269
+ <div className="relative min-h-[60vh] w-full overflow-hidden md:min-h-[70vh] lg:min-h-[870px]">
270
+ <SafeImage
271
+ src={heroImage}
272
+ alt={heroImageAlt}
273
+ fill
274
+ className="object-cover object-center"
275
+ sizes="100vw"
276
+ priority
277
+ />
278
+ <div
279
+ className="absolute inset-0"
280
+ style={{
281
+ backgroundImage:
282
+ "linear-gradient(-74.6deg, rgb(15, 30, 64) 24.45%, rgba(15, 30, 64, 0) 98.7%)",
283
+ }}
284
+ aria-hidden="true"
285
+ />
286
+
287
+ <div className="relative z-10 mx-auto flex max-w-[711px] flex-col gap-6 px-4 py-16 sm:px-6 sm:py-20 md:px-10 lg:px-[120px] lg:py-24">
288
+ <h1 className="text-3xl leading-tight tracking-[-0.04em] text-white sm:text-4xl md:text-5xl lg:text-[56px] lg:tracking-[-2.24px]">
289
+ {headline}
290
+ </h1>
291
+ <p className="max-w-xl text-base leading-relaxed tracking-[0.01em] text-white/80 sm:text-lg">
292
+ {description}
293
+ </p>
294
+ <div className="flex flex-col gap-3 sm:flex-row sm:gap-2">
295
+ <button
296
+ type="button"
297
+ onClick={onPrimaryCtaClick}
298
+ className="rounded-full bg-[#eaaf51] px-8 py-[18px] text-lg font-medium tracking-[-0.02em] text-[#0f1e40] transition-colors duration-200 ease-out hover:bg-[#e0a647] focus-visible:outline-2 focus-visible:outline-offset-2"
299
+ >
300
+ {primaryCtaText}
301
+ </button>
302
+ <button
303
+ type="button"
304
+ onClick={onSecondaryCtaClick}
305
+ className="rounded-full bg-white px-8 py-[18px] text-lg font-medium tracking-[-0.02em] text-[#0f1e40] transition-colors duration-200 ease-out hover:bg-neutral-100 focus-visible:outline-2 focus-visible:outline-offset-2"
306
+ >
307
+ {secondaryCtaText}
308
+ </button>
309
+ </div>
310
+ </div>
311
+ </div>
312
+ </section>
313
+ );
314
+ }
315
+
316
+ Menu05.propTypes = menu05PropTypes;
317
+
318
+ export default Menu05;
@@ -0,0 +1,79 @@
1
+ import PropTypes from "prop-types";
2
+
3
+ export const menu05PropTypes = {
4
+ utilityLinks: PropTypes.arrayOf(
5
+ PropTypes.shape({
6
+ label: PropTypes.string.isRequired,
7
+ href: PropTypes.string,
8
+ onClick: PropTypes.func,
9
+ }),
10
+ ),
11
+ phone: PropTypes.string,
12
+ logoSrc: PropTypes.string,
13
+ logoAlt: PropTypes.string,
14
+ navItems: PropTypes.arrayOf(
15
+ PropTypes.shape({
16
+ label: PropTypes.string.isRequired,
17
+ href: PropTypes.string,
18
+ onClick: PropTypes.func,
19
+ hasDropdown: PropTypes.bool,
20
+ }),
21
+ ),
22
+ ctaText: PropTypes.string,
23
+ onCtaClick: PropTypes.func,
24
+ /** Whether the About Oxford dropdown starts open. */
25
+ defaultDropdownOpen: PropTypes.bool,
26
+ dropdownLinks: PropTypes.arrayOf(
27
+ PropTypes.shape({
28
+ label: PropTypes.string.isRequired,
29
+ href: PropTypes.string,
30
+ onClick: PropTypes.func,
31
+ }),
32
+ ),
33
+ heroImage: PropTypes.string,
34
+ heroImageAlt: PropTypes.string,
35
+ headline: PropTypes.string,
36
+ description: PropTypes.string,
37
+ primaryCtaText: PropTypes.string,
38
+ secondaryCtaText: PropTypes.string,
39
+ onPrimaryCtaClick: PropTypes.func,
40
+ onSecondaryCtaClick: PropTypes.func,
41
+ onMenuClick: PropTypes.func,
42
+ className: PropTypes.string,
43
+ };
44
+
45
+ export const menu05DefaultProps = {
46
+ utilityLinks: [
47
+ { label: "Take career quiz", href: "#" },
48
+ { label: "Student login", href: "#" },
49
+ ],
50
+ phone: "1-866-918-0657",
51
+ logoSrc: "/menus/menu05/logo.png",
52
+ logoAlt: "Oxford College of Arts, Business and Technology",
53
+ navItems: [
54
+ { label: "Programs", href: "#", hasDropdown: true },
55
+ { label: "Admissions", href: "#", hasDropdown: true },
56
+ { label: "About Oxford", href: "#", hasDropdown: true },
57
+ { label: "Student Experience", href: "#", hasDropdown: true },
58
+ { label: "Financial Aid", href: "#", hasDropdown: true },
59
+ ],
60
+ ctaText: "Request Info",
61
+ defaultDropdownOpen: false,
62
+ dropdownLinks: [
63
+ { label: "About Oxford", href: "#" },
64
+ { label: "Mission, Vision, Values", href: "#" },
65
+ { label: "President's Message", href: "#" },
66
+ { label: "Accreditations and Affiliations", href: "#" },
67
+ { label: "Join Our Team", href: "#" },
68
+ { label: "Agent", href: "#" },
69
+ { label: "Land Acknowledgement", href: "#" },
70
+ { label: "Policies", href: "#" },
71
+ ],
72
+ heroImage: "/menus/menu05/hero-bg.jpg",
73
+ heroImageAlt: "Students sitting together on campus",
74
+ headline: "Graduate job-ready. Start your career sooner.",
75
+ description:
76
+ "Hands-on diploma programs, expert instructors, financial aid options, and career-focused training across 6 campuses.",
77
+ primaryCtaText: "Explore Programs",
78
+ secondaryCtaText: "Speak to Admissions",
79
+ };
@@ -0,0 +1,2 @@
1
+ export { Menu05 } from "./Menu05";
2
+ export { default } from "./Menu05";
@@ -0,0 +1,241 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import SafeImage from "../../atoms/SafeImage";
5
+ import { FullScreenNavOverlay } from "../../molecules/FullScreenNavOverlay";
6
+ import { menu06DefaultProps, menu06PropTypes } from "./Menu06.propTypes";
7
+
8
+ function CloseIcon() {
9
+ return (
10
+ <svg width="36" height="36" viewBox="0 0 36 36" fill="none" aria-hidden="true">
11
+ <path
12
+ d="M11 11L25 25M25 11L11 25"
13
+ stroke="currentColor"
14
+ strokeWidth="1.5"
15
+ strokeLinecap="round"
16
+ />
17
+ </svg>
18
+ );
19
+ }
20
+
21
+ function MenuDashIcon() {
22
+ return (
23
+ <span className="inline-block h-[9px] w-3.5 rounded-sm bg-white" aria-hidden="true" />
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Menu06 — Sandbay resort split-panel menu overlay with pool image
29
+ * and burgundy navigation panel sliding in from the right.
30
+ *
31
+ * @param {object} props - See Menu06.propTypes.js
32
+ */
33
+ export function Menu06({
34
+ defaultOpen = menu06DefaultProps.defaultOpen,
35
+ showHeroPeek = menu06DefaultProps.showHeroPeek,
36
+ brandName = menu06DefaultProps.brandName,
37
+ peekHeadline = menu06DefaultProps.peekHeadline,
38
+ panelImage = menu06DefaultProps.panelImage,
39
+ panelImageAlt = menu06DefaultProps.panelImageAlt,
40
+ navItems = menu06DefaultProps.navItems,
41
+ email = menu06DefaultProps.email,
42
+ phone = menu06DefaultProps.phone,
43
+ socialLinks = menu06DefaultProps.socialLinks,
44
+ menuLabel = menu06DefaultProps.menuLabel,
45
+ onClose,
46
+ className = "",
47
+ }) {
48
+ const [isOpen, setIsOpen] = useState(defaultOpen);
49
+
50
+ const handleClose = () => {
51
+ setIsOpen(false);
52
+ onClose?.();
53
+ };
54
+
55
+ return (
56
+ <section
57
+ className={["relative min-h-[600px] w-full overflow-hidden bg-[#fff8f3] md:min-h-[700px]", className]
58
+ .filter(Boolean)
59
+ .join(" ")}
60
+ data-menu="menu06"
61
+ >
62
+ {showHeroPeek ? (
63
+ <div
64
+ className="relative flex min-h-[600px] flex-col px-4 pb-10 pt-6 sm:px-6 md:min-h-[700px] md:px-10 lg:px-[60px] lg:pt-9"
65
+ aria-hidden={isOpen}
66
+ >
67
+ <div className="mx-auto flex w-full max-w-[1800px] items-center justify-between">
68
+ <p className="font-serif text-3xl leading-none tracking-[-0.04em] text-[#680015] sm:text-4xl md:text-[48px]">
69
+ {brandName}
70
+ </p>
71
+ {!isOpen ? (
72
+ <button
73
+ type="button"
74
+ onClick={() => setIsOpen(true)}
75
+ className="rounded-[120px] border border-[#680015]/20 px-6 py-3 text-base text-[#641002] transition-colors duration-200 ease-out hover:bg-white focus-visible:outline-2 focus-visible:outline-offset-2"
76
+ >
77
+ Open Menu
78
+ </button>
79
+ ) : null}
80
+ </div>
81
+
82
+ <h1 className="mx-auto mt-10 max-w-[1282px] text-center font-serif text-[clamp(2rem,8vw,8rem)] leading-[0.75] tracking-[-0.04em] text-[#410000] sm:mt-16 lg:mt-[115px]">
83
+ {peekHeadline}
84
+ </h1>
85
+ </div>
86
+ ) : null}
87
+
88
+ {!showHeroPeek && !isOpen ? (
89
+ <div className="flex min-h-[600px] items-center justify-center px-4 md:min-h-[700px]">
90
+ <button
91
+ type="button"
92
+ onClick={() => setIsOpen(true)}
93
+ className="rounded-[120px] bg-[#680015] px-8 py-4 text-white transition-colors duration-200 ease-out hover:bg-[#5a0012] focus-visible:outline-2 focus-visible:outline-offset-2"
94
+ >
95
+ Open Menu
96
+ </button>
97
+ </div>
98
+ ) : null}
99
+
100
+ <FullScreenNavOverlay
101
+ isOpen={isOpen}
102
+ onClose={handleClose}
103
+ slideFrom="right"
104
+ showBackdrop={showHeroPeek}
105
+ backdropClassName="bg-black/30"
106
+ ariaLabel="Sandbay navigation"
107
+ panelClassName="shadow-2xl"
108
+ >
109
+ <div className="flex min-h-full w-full flex-col md:flex-row">
110
+ {/* Left image panel — 40% */}
111
+ <div className="relative h-56 w-full shrink-0 sm:h-72 md:h-auto md:min-h-full md:w-[40%]">
112
+ <SafeImage
113
+ src={panelImage}
114
+ alt={panelImageAlt}
115
+ fill
116
+ className="object-cover object-center"
117
+ sizes="(max-width: 768px) 100vw, 40vw"
118
+ priority
119
+ />
120
+ </div>
121
+
122
+ {/* Right burgundy panel — 60% */}
123
+ <div className="flex min-h-0 flex-1 flex-col bg-[#680015] px-6 py-8 sm:px-10 sm:py-10 md:w-[60%] md:px-[60px] md:py-[60px] lg:px-[100px]">
124
+ <div className="flex flex-1 flex-col gap-12 md:gap-[60px] lg:gap-[140px]">
125
+ <div className="flex flex-col gap-10 md:gap-[60px]">
126
+ <div className="flex items-center justify-between gap-4">
127
+ <div className="flex items-center gap-1.5 uppercase tracking-wide text-white">
128
+ <MenuDashIcon />
129
+ <span className="text-base">{menuLabel}</span>
130
+ </div>
131
+ <button
132
+ type="button"
133
+ onClick={handleClose}
134
+ aria-label="Close menu"
135
+ className="rounded-[3px] border border-white/20 p-1 text-white transition-colors duration-200 ease-out hover:bg-white/10 focus-visible:outline-2 focus-visible:outline-offset-2"
136
+ >
137
+ <CloseIcon />
138
+ </button>
139
+ </div>
140
+
141
+ <nav className="flex flex-col gap-6 md:gap-8">
142
+ {navItems.map((item, index) => {
143
+ const linkClass = [
144
+ "text-left font-normal leading-[0.75] tracking-[-0.04em] transition-opacity duration-200 ease-out",
145
+ "text-3xl sm:text-4xl md:text-[48px]",
146
+ item.isActive
147
+ ? "text-white"
148
+ : "text-white/80 hover:text-white",
149
+ ].join(" ");
150
+
151
+ const content = item.href ? (
152
+ <a
153
+ href={item.href}
154
+ className={linkClass}
155
+ aria-current={item.isActive ? "page" : undefined}
156
+ >
157
+ {item.label}
158
+ </a>
159
+ ) : (
160
+ <button
161
+ type="button"
162
+ onClick={item.onClick}
163
+ className={`${linkClass} focus-visible:outline-2 focus-visible:outline-offset-2`}
164
+ aria-current={item.isActive ? "page" : undefined}
165
+ >
166
+ {item.label}
167
+ </button>
168
+ );
169
+
170
+ return (
171
+ <div key={item.label} className="flex flex-col gap-6 md:gap-8">
172
+ {content}
173
+ {index < navItems.length - 1 ? (
174
+ <div className="h-px w-full bg-white/20" aria-hidden="true" />
175
+ ) : null}
176
+ </div>
177
+ );
178
+ })}
179
+ </nav>
180
+ </div>
181
+
182
+ <div className="mt-auto flex flex-col gap-10 md:gap-[60px]">
183
+ <div className="flex flex-col gap-6">
184
+ <p className="text-base uppercase text-white">Let&apos;s Talk</p>
185
+ <div className="flex flex-col gap-4">
186
+ {email ? (
187
+ <a
188
+ href={`mailto:${email}`}
189
+ className="text-2xl leading-tight tracking-[-0.02em] text-white/80 transition-opacity duration-200 ease-out hover:text-white sm:text-3xl md:text-[48px]"
190
+ >
191
+ {email}
192
+ </a>
193
+ ) : null}
194
+ {phone ? (
195
+ <a
196
+ href={`tel:${phone.replace(/\s/g, "")}`}
197
+ className="text-lg tracking-[-0.02em] text-white/70 transition-opacity duration-200 ease-out hover:text-white md:text-2xl"
198
+ >
199
+ {phone}
200
+ </a>
201
+ ) : null}
202
+ </div>
203
+ </div>
204
+
205
+ <div className="flex flex-col gap-4">
206
+ <p className="text-base uppercase text-white">Social</p>
207
+ <div className="flex flex-wrap gap-6 text-lg tracking-[-0.02em] text-white/70 md:gap-8 md:text-2xl">
208
+ {socialLinks.map((link) =>
209
+ link.href ? (
210
+ <a
211
+ key={link.label}
212
+ href={link.href}
213
+ className="transition-opacity duration-200 ease-out hover:text-white"
214
+ >
215
+ {link.label}
216
+ </a>
217
+ ) : (
218
+ <button
219
+ key={link.label}
220
+ type="button"
221
+ onClick={link.onClick}
222
+ className="transition-opacity duration-200 ease-out hover:text-white"
223
+ >
224
+ {link.label}
225
+ </button>
226
+ ),
227
+ )}
228
+ </div>
229
+ </div>
230
+ </div>
231
+ </div>
232
+ </div>
233
+ </div>
234
+ </FullScreenNavOverlay>
235
+ </section>
236
+ );
237
+ }
238
+
239
+ Menu06.propTypes = menu06PropTypes;
240
+
241
+ export default Menu06;