@prosophia/lab-minimal 0.0.1

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.
package/dist/index.js ADDED
@@ -0,0 +1,556 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AnimatedHero: () => AnimatedHero,
34
+ ClientLayout: () => ClientLayout,
35
+ ContactCTA: () => ContactCTA,
36
+ Footer: () => Footer,
37
+ Header: () => Header,
38
+ ThemeToggle: () => ThemeToggle
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/components/AnimatedCard.tsx
43
+ var import_framer_motion = require("framer-motion");
44
+
45
+ // src/lib/animations.ts
46
+ var fadeInUp = {
47
+ hidden: { opacity: 0, y: 30 },
48
+ visible: {
49
+ opacity: 1,
50
+ y: 0,
51
+ transition: {
52
+ duration: 0.8,
53
+ ease: [0.65, 0, 0.35, 1]
54
+ }
55
+ }
56
+ };
57
+ var scaleIn = {
58
+ hidden: { opacity: 0, scale: 0.9 },
59
+ visible: {
60
+ opacity: 1,
61
+ scale: 1,
62
+ transition: {
63
+ duration: 0.8,
64
+ ease: [0.65, 0, 0.35, 1]
65
+ }
66
+ }
67
+ };
68
+
69
+ // src/components/AnimatedCard.tsx
70
+ var import_jsx_runtime = require("react/jsx-runtime");
71
+
72
+ // src/components/AnimatedGallery.tsx
73
+ var import_framer_motion2 = require("framer-motion");
74
+ var import_image = __toESM(require("next/image"));
75
+ var import_react = require("react");
76
+ var import_PicturesPage = __toESM(require("./PicturesPage.module.css"));
77
+ var import_jsx_runtime2 = require("react/jsx-runtime");
78
+
79
+ // src/components/AnimatedHero.tsx
80
+ var import_framer_motion3 = require("framer-motion");
81
+ var import_react2 = require("react");
82
+ var import_image2 = __toESM(require("next/image"));
83
+ var import_link = __toESM(require("next/link"));
84
+ var import_HomePage = __toESM(require("./HomePage.module.css"));
85
+ var import_jsx_runtime3 = require("react/jsx-runtime");
86
+ function AnimatedHero({ heroData, ArrowRightIcon }) {
87
+ const ref = (0, import_react2.useRef)(null);
88
+ const { scrollY } = (0, import_framer_motion3.useScroll)();
89
+ const y = (0, import_framer_motion3.useTransform)(scrollY, [0, 500], [0, 150]);
90
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("section", { className: import_HomePage.default.heroSection, ref, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: import_HomePage.default.heroContainer, children: [
91
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
92
+ import_framer_motion3.motion.div,
93
+ {
94
+ className: import_HomePage.default.heroContent,
95
+ initial: "hidden",
96
+ animate: "visible",
97
+ variants: fadeInUp,
98
+ children: [
99
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
100
+ import_framer_motion3.motion.span,
101
+ {
102
+ className: import_HomePage.default.heroTagline,
103
+ initial: { opacity: 0 },
104
+ animate: { opacity: 1 },
105
+ transition: { duration: 0.6, delay: 0.2 },
106
+ children: heroData.tagline
107
+ }
108
+ ),
109
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
110
+ import_framer_motion3.motion.h1,
111
+ {
112
+ className: import_HomePage.default.heroHeading,
113
+ initial: { opacity: 0, y: 20 },
114
+ animate: { opacity: 1, y: 0 },
115
+ transition: { duration: 0.8, delay: 0.3 },
116
+ children: [
117
+ heroData.heading,
118
+ " ",
119
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: import_HomePage.default.heroHeadingAccent, children: heroData.headingAccent })
120
+ ]
121
+ }
122
+ ),
123
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
124
+ import_framer_motion3.motion.p,
125
+ {
126
+ className: import_HomePage.default.heroDescription,
127
+ initial: { opacity: 0, y: 20 },
128
+ animate: { opacity: 1, y: 0 },
129
+ transition: { duration: 0.8, delay: 0.4 },
130
+ children: heroData.description
131
+ }
132
+ ),
133
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
134
+ import_framer_motion3.motion.div,
135
+ {
136
+ className: import_HomePage.default.heroCtas,
137
+ initial: { opacity: 0, y: 20 },
138
+ animate: { opacity: 1, y: 0 },
139
+ transition: { duration: 0.8, delay: 0.5 },
140
+ children: [
141
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_link.default, { href: heroData.ctaLink, className: import_HomePage.default.heroPrimaryCta, children: [
142
+ heroData.ctaText,
143
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArrowRightIcon, {})
144
+ ] }),
145
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_link.default, { href: "/publications", className: import_HomePage.default.heroSecondaryCta, children: "View Publications" })
146
+ ]
147
+ }
148
+ )
149
+ ]
150
+ }
151
+ ),
152
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
153
+ import_framer_motion3.motion.div,
154
+ {
155
+ className: import_HomePage.default.heroImageWrapper,
156
+ style: { y },
157
+ initial: "hidden",
158
+ animate: "visible",
159
+ variants: scaleIn,
160
+ transition: { duration: 0.8, delay: 0.6 },
161
+ children: heroData.image ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
162
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
163
+ import_image2.default,
164
+ {
165
+ src: heroData.image,
166
+ alt: "Research lab",
167
+ width: 800,
168
+ height: 600,
169
+ className: import_HomePage.default.heroImage,
170
+ priority: true
171
+ }
172
+ ),
173
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: import_HomePage.default.heroImageOverlay })
174
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: import_HomePage.default.heroImagePlaceholder, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
175
+ "svg",
176
+ {
177
+ width: "120",
178
+ height: "120",
179
+ viewBox: "0 0 24 24",
180
+ fill: "none",
181
+ stroke: "currentColor",
182
+ strokeWidth: "1",
183
+ children: [
184
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
185
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
186
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 15l-5-5L5 21" })
187
+ ]
188
+ }
189
+ ) })
190
+ }
191
+ )
192
+ ] }) });
193
+ }
194
+
195
+ // src/components/AnimatedPage.tsx
196
+ var import_framer_motion4 = require("framer-motion");
197
+ var import_react3 = require("react");
198
+ var import_jsx_runtime4 = require("react/jsx-runtime");
199
+
200
+ // src/components/AnimatedSections.tsx
201
+ var import_framer_motion5 = require("framer-motion");
202
+ var import_jsx_runtime5 = require("react/jsx-runtime");
203
+
204
+ // src/components/ClientLayout.tsx
205
+ var import_react6 = require("react");
206
+ var import_next_themes2 = require("next-themes");
207
+
208
+ // src/components/Header.tsx
209
+ var import_link2 = __toESM(require("next/link"));
210
+ var import_react5 = require("react");
211
+ var import_navigation = require("next/navigation");
212
+ var import_Layout = __toESM(require("./Layout.module.css"));
213
+
214
+ // src/components/ThemeToggle.tsx
215
+ var import_next_themes = require("next-themes");
216
+ var import_react4 = require("react");
217
+ var import_framer_motion6 = require("framer-motion");
218
+ var import_ThemeToggle = __toESM(require("./ThemeToggle.module.css"));
219
+ var import_jsx_runtime6 = require("react/jsx-runtime");
220
+ function ThemeToggle() {
221
+ const { theme, setTheme, resolvedTheme } = (0, import_next_themes.useTheme)();
222
+ const [mounted, setMounted] = (0, import_react4.useState)(false);
223
+ (0, import_react4.useEffect)(() => {
224
+ setMounted(true);
225
+ }, []);
226
+ if (!mounted) {
227
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: import_ThemeToggle.default.togglePlaceholder });
228
+ }
229
+ const isDark = resolvedTheme === "dark";
230
+ const toggleTheme = () => {
231
+ setTheme(isDark ? "light" : "dark");
232
+ };
233
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
234
+ import_framer_motion6.motion.button,
235
+ {
236
+ className: import_ThemeToggle.default.toggle,
237
+ onClick: toggleTheme,
238
+ "aria-label": `Switch to ${isDark ? "light" : "dark"} mode`,
239
+ whileTap: { scale: 0.95 },
240
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: import_ThemeToggle.default.track, children: [
241
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: `${import_ThemeToggle.default.icon} ${import_ThemeToggle.default.sunIcon} ${!isDark ? import_ThemeToggle.default.iconActive : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
242
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "5" }),
243
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "12", y1: "1", x2: "12", y2: "3" }),
244
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "12", y1: "21", x2: "12", y2: "23" }),
245
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "4.22", y1: "4.22", x2: "5.64", y2: "5.64" }),
246
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "18.36", y1: "18.36", x2: "19.78", y2: "19.78" }),
247
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "1", y1: "12", x2: "3", y2: "12" }),
248
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "21", y1: "12", x2: "23", y2: "12" }),
249
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "4.22", y1: "19.78", x2: "5.64", y2: "18.36" }),
250
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "18.36", y1: "5.64", x2: "19.78", y2: "4.22" })
251
+ ] }) }),
252
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
253
+ import_framer_motion6.motion.span,
254
+ {
255
+ className: import_ThemeToggle.default.thumb,
256
+ animate: { x: isDark ? 26 : 0 },
257
+ transition: { type: "spring", stiffness: 500, damping: 30 }
258
+ }
259
+ ),
260
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: `${import_ThemeToggle.default.icon} ${import_ThemeToggle.default.moonIcon} ${isDark ? import_ThemeToggle.default.iconActive : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" }) }) })
261
+ ] })
262
+ }
263
+ );
264
+ }
265
+
266
+ // src/components/Header.tsx
267
+ var import_jsx_runtime7 = require("react/jsx-runtime");
268
+ var navLinks = [
269
+ { href: "/research", label: "Research" },
270
+ { href: "/publications", label: "Publications" },
271
+ { href: "/people", label: "People" },
272
+ { href: "/news", label: "News" },
273
+ { href: "/pictures", label: "Gallery" }
274
+ ];
275
+ function Header({ settings }) {
276
+ const [menuOpen, setMenuOpen] = (0, import_react5.useState)(false);
277
+ const [scrolled, setScrolled] = (0, import_react5.useState)(false);
278
+ const pathname = (0, import_navigation.usePathname)();
279
+ (0, import_react5.useEffect)(() => {
280
+ const handleScroll = () => {
281
+ setScrolled(window.scrollY > 20);
282
+ };
283
+ window.addEventListener("scroll", handleScroll, { passive: true });
284
+ return () => window.removeEventListener("scroll", handleScroll);
285
+ }, []);
286
+ (0, import_react5.useEffect)(() => {
287
+ if (menuOpen) {
288
+ document.body.style.overflow = "hidden";
289
+ } else {
290
+ document.body.style.overflow = "";
291
+ }
292
+ return () => {
293
+ document.body.style.overflow = "";
294
+ };
295
+ }, [menuOpen]);
296
+ (0, import_react5.useEffect)(() => {
297
+ setMenuOpen(false);
298
+ }, [pathname]);
299
+ const handleKeyDown = (0, import_react5.useCallback)((e) => {
300
+ if (e.key === "Escape" && menuOpen) {
301
+ setMenuOpen(false);
302
+ }
303
+ }, [menuOpen]);
304
+ (0, import_react5.useEffect)(() => {
305
+ document.addEventListener("keydown", handleKeyDown);
306
+ return () => document.removeEventListener("keydown", handleKeyDown);
307
+ }, [handleKeyDown]);
308
+ const isActive = (href) => {
309
+ if (href === "/") return pathname === "/";
310
+ return pathname.startsWith(href);
311
+ };
312
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("header", { className: `${import_Layout.default.header} ${scrolled ? import_Layout.default.headerScrolled : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: import_Layout.default.navContainer, children: [
313
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_link2.default, { href: "/", className: import_Layout.default.navLogo, "aria-label": "Go to homepage", children: [
314
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
315
+ "svg",
316
+ {
317
+ width: "32",
318
+ height: "32",
319
+ viewBox: "0 0 24 24",
320
+ fill: "none",
321
+ xmlns: "http://www.w3.org/2000/svg",
322
+ "aria-hidden": "true",
323
+ children: [
324
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
325
+ "path",
326
+ {
327
+ d: "M12 2L2 7L12 12L22 7L12 2Z",
328
+ stroke: "currentColor",
329
+ strokeWidth: "2",
330
+ strokeLinecap: "round",
331
+ strokeLinejoin: "round"
332
+ }
333
+ ),
334
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
335
+ "path",
336
+ {
337
+ d: "M2 17L12 22L22 17",
338
+ stroke: "currentColor",
339
+ strokeWidth: "2",
340
+ strokeLinecap: "round",
341
+ strokeLinejoin: "round"
342
+ }
343
+ ),
344
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
345
+ "path",
346
+ {
347
+ d: "M2 12L12 17L22 12",
348
+ stroke: "currentColor",
349
+ strokeWidth: "2",
350
+ strokeLinecap: "round",
351
+ strokeLinejoin: "round"
352
+ }
353
+ )
354
+ ]
355
+ }
356
+ ),
357
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
358
+ settings.labName || "Research",
359
+ settings.labNameAccent && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: import_Layout.default.navLogoAccent, children: [
360
+ " ",
361
+ settings.labNameAccent
362
+ ] })
363
+ ] })
364
+ ] }),
365
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("nav", { className: import_Layout.default.desktopNav, "aria-label": "Main navigation", children: navLinks.map((link) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
366
+ import_link2.default,
367
+ {
368
+ href: link.href,
369
+ className: `${import_Layout.default.navLink} ${isActive(link.href) ? import_Layout.default.navLinkActive : ""}`,
370
+ children: link.label
371
+ },
372
+ link.href
373
+ )) }),
374
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: import_Layout.default.navActions, children: [
375
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ThemeToggle, {}),
376
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_link2.default, { href: "/contact", className: import_Layout.default.navCta, children: "Contact Us" })
377
+ ] }),
378
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
379
+ "button",
380
+ {
381
+ className: import_Layout.default.menuButton,
382
+ onClick: () => setMenuOpen(!menuOpen),
383
+ "aria-label": menuOpen ? "Close menu" : "Open menu",
384
+ "aria-expanded": menuOpen,
385
+ "aria-controls": "mobile-menu",
386
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: import_Layout.default.menuButtonLines, children: [
387
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: `${import_Layout.default.menuLine} ${menuOpen ? import_Layout.default.menuLineOpen1 : ""}` }),
388
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: `${import_Layout.default.menuLine} ${menuOpen ? import_Layout.default.menuLineOpen2 : ""}` }),
389
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: `${import_Layout.default.menuLine} ${menuOpen ? import_Layout.default.menuLineOpen3 : ""}` })
390
+ ] })
391
+ }
392
+ ),
393
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
394
+ "div",
395
+ {
396
+ id: "mobile-menu",
397
+ className: `${import_Layout.default.mobileMenu} ${menuOpen ? import_Layout.default.mobileMenuOpen : ""}`,
398
+ "aria-hidden": !menuOpen,
399
+ children: [
400
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
401
+ "div",
402
+ {
403
+ className: import_Layout.default.mobileMenuOverlay,
404
+ onClick: () => setMenuOpen(false),
405
+ "aria-hidden": "true"
406
+ }
407
+ ),
408
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
409
+ "nav",
410
+ {
411
+ className: import_Layout.default.mobileMenuContent,
412
+ "aria-label": "Mobile navigation",
413
+ children: [
414
+ navLinks.map((link, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
415
+ import_link2.default,
416
+ {
417
+ href: link.href,
418
+ className: `${import_Layout.default.mobileNavLink} ${isActive(link.href) ? import_Layout.default.mobileNavLinkActive : ""}`,
419
+ style: { animationDelay: `${index * 50}ms` },
420
+ tabIndex: menuOpen ? 0 : -1,
421
+ children: link.label
422
+ },
423
+ link.href
424
+ )),
425
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
426
+ import_link2.default,
427
+ {
428
+ href: "/contact",
429
+ className: import_Layout.default.mobileNavCta,
430
+ style: { animationDelay: `${navLinks.length * 50}ms` },
431
+ tabIndex: menuOpen ? 0 : -1,
432
+ children: "Contact Us"
433
+ }
434
+ )
435
+ ]
436
+ }
437
+ )
438
+ ]
439
+ }
440
+ )
441
+ ] }) });
442
+ }
443
+
444
+ // src/components/Footer.tsx
445
+ var import_link3 = __toESM(require("next/link"));
446
+ var import_Footer = __toESM(require("./Footer.module.css"));
447
+
448
+ // src/lib/utils.ts
449
+ function isValidExternalUrl(url) {
450
+ if (!url || typeof url !== "string") return false;
451
+ try {
452
+ const parsed = new URL(url);
453
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
454
+ } catch {
455
+ return false;
456
+ }
457
+ }
458
+
459
+ // src/components/Footer.tsx
460
+ var import_jsx_runtime8 = require("react/jsx-runtime");
461
+ var SocialIcon = ({ href, children }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: import_Footer.default.socialIcon, children });
462
+ function Footer({ settings }) {
463
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
464
+ const showPrivacy = settings?.showPrivacyPolicy !== false;
465
+ const showTerms = settings?.showTerms !== false;
466
+ const hasLegalLinks = showPrivacy || showTerms;
467
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("footer", { className: import_Footer.default.footerWrapper, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: import_Footer.default.footerContainer, children: [
468
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: import_Footer.default.mainFooter, children: [
469
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: import_Footer.default.footerAbout, children: [
470
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_link3.default, { href: "/", className: import_Footer.default.footerLogo, children: [
471
+ settings?.labName || "Cavendish",
472
+ " ",
473
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: import_Footer.default.logoAccent, children: settings?.labNameAccent || "Lab" })
474
+ ] }),
475
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: import_Footer.default.footerDescription, children: settings?.labNameDescription || "Advancing the frontiers of physics and our understanding of the universe." }),
476
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: import_Footer.default.socials, children: [
477
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SocialIcon, { href: "#", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z" }) }) }),
478
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SocialIcon, { href: "#", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24h-6.617l-5.21-6.817-6.044 6.817h-3.308l7.73-8.835-7.73-10.668h6.78l4.522 6.312 5.59-6.312z" }) }) })
479
+ ] })
480
+ ] }),
481
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: import_Footer.default.footerLinksGrid, children: [
482
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: import_Footer.default.footerColumn, children: [
483
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { children: "Quick Links" }),
484
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_link3.default, { href: "/research", children: "Research" }),
485
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_link3.default, { href: "/publications", children: "Publications" }),
486
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_link3.default, { href: "/pictures", children: "Gallery" })
487
+ ] }),
488
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: import_Footer.default.footerColumn, children: [
489
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { children: "About" }),
490
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_link3.default, { href: "/news", children: "News" }),
491
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_link3.default, { href: "/people", children: "Our Team" }),
492
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_link3.default, { href: "/contact", children: "Contact" })
493
+ ] }),
494
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: import_Footer.default.footerColumn, children: [
495
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { children: "Resources" }),
496
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_link3.default, { href: "https://www.cam.ac.uk/", children: "University" }),
497
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_link3.default, { href: "#", children: "Careers" })
498
+ ] })
499
+ ] })
500
+ ] }),
501
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: import_Footer.default.footerBottomBar, children: [
502
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: settings?.footerText || `\xA9 ${currentYear} The Research Group. All Rights Reserved.` }),
503
+ hasLegalLinks && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: import_Footer.default.legalLinks, children: [
504
+ showPrivacy && (settings?.privacyPolicyUrl && isValidExternalUrl(settings.privacyPolicyUrl) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("a", { href: settings.privacyPolicyUrl, target: "_blank", rel: "noopener noreferrer", children: "Privacy Policy" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_link3.default, { href: "/privacy", children: "Privacy Policy" })),
505
+ showTerms && (settings?.termsUrl && isValidExternalUrl(settings.termsUrl) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("a", { href: settings.termsUrl, target: "_blank", rel: "noopener noreferrer", children: "Terms & Conditions" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_link3.default, { href: "/terms", children: "Terms & Conditions" }))
506
+ ] })
507
+ ] })
508
+ ] }) });
509
+ }
510
+
511
+ // src/components/ClientLayout.tsx
512
+ var import_jsx_runtime9 = require("react/jsx-runtime");
513
+ function ThemeBodySync() {
514
+ const { resolvedTheme } = (0, import_next_themes2.useTheme)();
515
+ (0, import_react6.useEffect)(() => {
516
+ if (resolvedTheme === "dark") {
517
+ document.body.classList.add("dark-mode");
518
+ document.body.classList.remove("light-mode");
519
+ } else {
520
+ document.body.classList.add("light-mode");
521
+ document.body.classList.remove("dark-mode");
522
+ }
523
+ }, [resolvedTheme]);
524
+ return null;
525
+ }
526
+ function ClientLayout({ children, settings }) {
527
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_next_themes2.ThemeProvider, { attribute: "class", defaultTheme: "light", enableSystem: false, children: [
528
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ThemeBodySync, {}),
529
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "pageWrapper", children: [
530
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Header, { settings }),
531
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("main", { children }),
532
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Footer, { settings })
533
+ ] })
534
+ ] });
535
+ }
536
+
537
+ // src/components/ContactCTA.tsx
538
+ var import_link4 = __toESM(require("next/link"));
539
+ var import_ContactCTA = __toESM(require("./ContactCTA.module.css"));
540
+ var import_jsx_runtime10 = require("react/jsx-runtime");
541
+ function ContactCTA() {
542
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("section", { className: import_ContactCTA.default.ctaContainer, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: import_ContactCTA.default.ctaContent, children: [
543
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { className: import_ContactCTA.default.ctaHeading, children: "Interested in Our Research?" }),
544
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: import_ContactCTA.default.ctaText, children: "Follow our work, explore our publications, or get in touch to discuss potential collaborations." }),
545
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_link4.default, { href: "/contact", className: import_ContactCTA.default.ctaButton, children: "Contact Us" })
546
+ ] }) });
547
+ }
548
+ // Annotate the CommonJS export names for ESM import in node:
549
+ 0 && (module.exports = {
550
+ AnimatedHero,
551
+ ClientLayout,
552
+ ContactCTA,
553
+ Footer,
554
+ Header,
555
+ ThemeToggle
556
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,514 @@
1
+ // src/components/AnimatedCard.tsx
2
+ import { motion } from "framer-motion";
3
+
4
+ // src/lib/animations.ts
5
+ var fadeInUp = {
6
+ hidden: { opacity: 0, y: 30 },
7
+ visible: {
8
+ opacity: 1,
9
+ y: 0,
10
+ transition: {
11
+ duration: 0.8,
12
+ ease: [0.65, 0, 0.35, 1]
13
+ }
14
+ }
15
+ };
16
+ var scaleIn = {
17
+ hidden: { opacity: 0, scale: 0.9 },
18
+ visible: {
19
+ opacity: 1,
20
+ scale: 1,
21
+ transition: {
22
+ duration: 0.8,
23
+ ease: [0.65, 0, 0.35, 1]
24
+ }
25
+ }
26
+ };
27
+
28
+ // src/components/AnimatedCard.tsx
29
+ import { jsx } from "react/jsx-runtime";
30
+
31
+ // src/components/AnimatedGallery.tsx
32
+ import { motion as motion2 } from "framer-motion";
33
+ import Image from "next/image";
34
+ import { useState, useEffect } from "react";
35
+ import styles from "./PicturesPage.module.css";
36
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
37
+
38
+ // src/components/AnimatedHero.tsx
39
+ import { motion as motion3, useScroll, useTransform } from "framer-motion";
40
+ import { useRef } from "react";
41
+ import Image2 from "next/image";
42
+ import Link from "next/link";
43
+ import styles2 from "./HomePage.module.css";
44
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
45
+ function AnimatedHero({ heroData, ArrowRightIcon }) {
46
+ const ref = useRef(null);
47
+ const { scrollY } = useScroll();
48
+ const y = useTransform(scrollY, [0, 500], [0, 150]);
49
+ return /* @__PURE__ */ jsx3("section", { className: styles2.heroSection, ref, children: /* @__PURE__ */ jsxs2("div", { className: styles2.heroContainer, children: [
50
+ /* @__PURE__ */ jsxs2(
51
+ motion3.div,
52
+ {
53
+ className: styles2.heroContent,
54
+ initial: "hidden",
55
+ animate: "visible",
56
+ variants: fadeInUp,
57
+ children: [
58
+ /* @__PURE__ */ jsx3(
59
+ motion3.span,
60
+ {
61
+ className: styles2.heroTagline,
62
+ initial: { opacity: 0 },
63
+ animate: { opacity: 1 },
64
+ transition: { duration: 0.6, delay: 0.2 },
65
+ children: heroData.tagline
66
+ }
67
+ ),
68
+ /* @__PURE__ */ jsxs2(
69
+ motion3.h1,
70
+ {
71
+ className: styles2.heroHeading,
72
+ initial: { opacity: 0, y: 20 },
73
+ animate: { opacity: 1, y: 0 },
74
+ transition: { duration: 0.8, delay: 0.3 },
75
+ children: [
76
+ heroData.heading,
77
+ " ",
78
+ /* @__PURE__ */ jsx3("span", { className: styles2.heroHeadingAccent, children: heroData.headingAccent })
79
+ ]
80
+ }
81
+ ),
82
+ /* @__PURE__ */ jsx3(
83
+ motion3.p,
84
+ {
85
+ className: styles2.heroDescription,
86
+ initial: { opacity: 0, y: 20 },
87
+ animate: { opacity: 1, y: 0 },
88
+ transition: { duration: 0.8, delay: 0.4 },
89
+ children: heroData.description
90
+ }
91
+ ),
92
+ /* @__PURE__ */ jsxs2(
93
+ motion3.div,
94
+ {
95
+ className: styles2.heroCtas,
96
+ initial: { opacity: 0, y: 20 },
97
+ animate: { opacity: 1, y: 0 },
98
+ transition: { duration: 0.8, delay: 0.5 },
99
+ children: [
100
+ /* @__PURE__ */ jsxs2(Link, { href: heroData.ctaLink, className: styles2.heroPrimaryCta, children: [
101
+ heroData.ctaText,
102
+ /* @__PURE__ */ jsx3(ArrowRightIcon, {})
103
+ ] }),
104
+ /* @__PURE__ */ jsx3(Link, { href: "/publications", className: styles2.heroSecondaryCta, children: "View Publications" })
105
+ ]
106
+ }
107
+ )
108
+ ]
109
+ }
110
+ ),
111
+ /* @__PURE__ */ jsx3(
112
+ motion3.div,
113
+ {
114
+ className: styles2.heroImageWrapper,
115
+ style: { y },
116
+ initial: "hidden",
117
+ animate: "visible",
118
+ variants: scaleIn,
119
+ transition: { duration: 0.8, delay: 0.6 },
120
+ children: heroData.image ? /* @__PURE__ */ jsxs2(Fragment, { children: [
121
+ /* @__PURE__ */ jsx3(
122
+ Image2,
123
+ {
124
+ src: heroData.image,
125
+ alt: "Research lab",
126
+ width: 800,
127
+ height: 600,
128
+ className: styles2.heroImage,
129
+ priority: true
130
+ }
131
+ ),
132
+ /* @__PURE__ */ jsx3("div", { className: styles2.heroImageOverlay })
133
+ ] }) : /* @__PURE__ */ jsx3("div", { className: styles2.heroImagePlaceholder, children: /* @__PURE__ */ jsxs2(
134
+ "svg",
135
+ {
136
+ width: "120",
137
+ height: "120",
138
+ viewBox: "0 0 24 24",
139
+ fill: "none",
140
+ stroke: "currentColor",
141
+ strokeWidth: "1",
142
+ children: [
143
+ /* @__PURE__ */ jsx3("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
144
+ /* @__PURE__ */ jsx3("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
145
+ /* @__PURE__ */ jsx3("path", { d: "M21 15l-5-5L5 21" })
146
+ ]
147
+ }
148
+ ) })
149
+ }
150
+ )
151
+ ] }) });
152
+ }
153
+
154
+ // src/components/AnimatedPage.tsx
155
+ import { motion as motion4 } from "framer-motion";
156
+ import { useState as useState2, useEffect as useEffect2 } from "react";
157
+ import { jsx as jsx4 } from "react/jsx-runtime";
158
+
159
+ // src/components/AnimatedSections.tsx
160
+ import { motion as motion5 } from "framer-motion";
161
+ import { jsx as jsx5 } from "react/jsx-runtime";
162
+
163
+ // src/components/ClientLayout.tsx
164
+ import { useEffect as useEffect5 } from "react";
165
+ import { ThemeProvider, useTheme as useTheme2 } from "next-themes";
166
+
167
+ // src/components/Header.tsx
168
+ import Link2 from "next/link";
169
+ import { useState as useState4, useEffect as useEffect4, useCallback } from "react";
170
+ import { usePathname } from "next/navigation";
171
+ import styles4 from "./Layout.module.css";
172
+
173
+ // src/components/ThemeToggle.tsx
174
+ import { useTheme } from "next-themes";
175
+ import { useEffect as useEffect3, useState as useState3 } from "react";
176
+ import { motion as motion6 } from "framer-motion";
177
+ import styles3 from "./ThemeToggle.module.css";
178
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
179
+ function ThemeToggle() {
180
+ const { theme, setTheme, resolvedTheme } = useTheme();
181
+ const [mounted, setMounted] = useState3(false);
182
+ useEffect3(() => {
183
+ setMounted(true);
184
+ }, []);
185
+ if (!mounted) {
186
+ return /* @__PURE__ */ jsx6("div", { className: styles3.togglePlaceholder });
187
+ }
188
+ const isDark = resolvedTheme === "dark";
189
+ const toggleTheme = () => {
190
+ setTheme(isDark ? "light" : "dark");
191
+ };
192
+ return /* @__PURE__ */ jsx6(
193
+ motion6.button,
194
+ {
195
+ className: styles3.toggle,
196
+ onClick: toggleTheme,
197
+ "aria-label": `Switch to ${isDark ? "light" : "dark"} mode`,
198
+ whileTap: { scale: 0.95 },
199
+ children: /* @__PURE__ */ jsxs3("span", { className: styles3.track, children: [
200
+ /* @__PURE__ */ jsx6("span", { className: `${styles3.icon} ${styles3.sunIcon} ${!isDark ? styles3.iconActive : ""}`, children: /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
201
+ /* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "5" }),
202
+ /* @__PURE__ */ jsx6("line", { x1: "12", y1: "1", x2: "12", y2: "3" }),
203
+ /* @__PURE__ */ jsx6("line", { x1: "12", y1: "21", x2: "12", y2: "23" }),
204
+ /* @__PURE__ */ jsx6("line", { x1: "4.22", y1: "4.22", x2: "5.64", y2: "5.64" }),
205
+ /* @__PURE__ */ jsx6("line", { x1: "18.36", y1: "18.36", x2: "19.78", y2: "19.78" }),
206
+ /* @__PURE__ */ jsx6("line", { x1: "1", y1: "12", x2: "3", y2: "12" }),
207
+ /* @__PURE__ */ jsx6("line", { x1: "21", y1: "12", x2: "23", y2: "12" }),
208
+ /* @__PURE__ */ jsx6("line", { x1: "4.22", y1: "19.78", x2: "5.64", y2: "18.36" }),
209
+ /* @__PURE__ */ jsx6("line", { x1: "18.36", y1: "5.64", x2: "19.78", y2: "4.22" })
210
+ ] }) }),
211
+ /* @__PURE__ */ jsx6(
212
+ motion6.span,
213
+ {
214
+ className: styles3.thumb,
215
+ animate: { x: isDark ? 26 : 0 },
216
+ transition: { type: "spring", stiffness: 500, damping: 30 }
217
+ }
218
+ ),
219
+ /* @__PURE__ */ jsx6("span", { className: `${styles3.icon} ${styles3.moonIcon} ${isDark ? styles3.iconActive : ""}`, children: /* @__PURE__ */ jsx6("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx6("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" }) }) })
220
+ ] })
221
+ }
222
+ );
223
+ }
224
+
225
+ // src/components/Header.tsx
226
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
227
+ var navLinks = [
228
+ { href: "/research", label: "Research" },
229
+ { href: "/publications", label: "Publications" },
230
+ { href: "/people", label: "People" },
231
+ { href: "/news", label: "News" },
232
+ { href: "/pictures", label: "Gallery" }
233
+ ];
234
+ function Header({ settings }) {
235
+ const [menuOpen, setMenuOpen] = useState4(false);
236
+ const [scrolled, setScrolled] = useState4(false);
237
+ const pathname = usePathname();
238
+ useEffect4(() => {
239
+ const handleScroll = () => {
240
+ setScrolled(window.scrollY > 20);
241
+ };
242
+ window.addEventListener("scroll", handleScroll, { passive: true });
243
+ return () => window.removeEventListener("scroll", handleScroll);
244
+ }, []);
245
+ useEffect4(() => {
246
+ if (menuOpen) {
247
+ document.body.style.overflow = "hidden";
248
+ } else {
249
+ document.body.style.overflow = "";
250
+ }
251
+ return () => {
252
+ document.body.style.overflow = "";
253
+ };
254
+ }, [menuOpen]);
255
+ useEffect4(() => {
256
+ setMenuOpen(false);
257
+ }, [pathname]);
258
+ const handleKeyDown = useCallback((e) => {
259
+ if (e.key === "Escape" && menuOpen) {
260
+ setMenuOpen(false);
261
+ }
262
+ }, [menuOpen]);
263
+ useEffect4(() => {
264
+ document.addEventListener("keydown", handleKeyDown);
265
+ return () => document.removeEventListener("keydown", handleKeyDown);
266
+ }, [handleKeyDown]);
267
+ const isActive = (href) => {
268
+ if (href === "/") return pathname === "/";
269
+ return pathname.startsWith(href);
270
+ };
271
+ return /* @__PURE__ */ jsx7("header", { className: `${styles4.header} ${scrolled ? styles4.headerScrolled : ""}`, children: /* @__PURE__ */ jsxs4("div", { className: styles4.navContainer, children: [
272
+ /* @__PURE__ */ jsxs4(Link2, { href: "/", className: styles4.navLogo, "aria-label": "Go to homepage", children: [
273
+ /* @__PURE__ */ jsxs4(
274
+ "svg",
275
+ {
276
+ width: "32",
277
+ height: "32",
278
+ viewBox: "0 0 24 24",
279
+ fill: "none",
280
+ xmlns: "http://www.w3.org/2000/svg",
281
+ "aria-hidden": "true",
282
+ children: [
283
+ /* @__PURE__ */ jsx7(
284
+ "path",
285
+ {
286
+ d: "M12 2L2 7L12 12L22 7L12 2Z",
287
+ stroke: "currentColor",
288
+ strokeWidth: "2",
289
+ strokeLinecap: "round",
290
+ strokeLinejoin: "round"
291
+ }
292
+ ),
293
+ /* @__PURE__ */ jsx7(
294
+ "path",
295
+ {
296
+ d: "M2 17L12 22L22 17",
297
+ stroke: "currentColor",
298
+ strokeWidth: "2",
299
+ strokeLinecap: "round",
300
+ strokeLinejoin: "round"
301
+ }
302
+ ),
303
+ /* @__PURE__ */ jsx7(
304
+ "path",
305
+ {
306
+ d: "M2 12L12 17L22 12",
307
+ stroke: "currentColor",
308
+ strokeWidth: "2",
309
+ strokeLinecap: "round",
310
+ strokeLinejoin: "round"
311
+ }
312
+ )
313
+ ]
314
+ }
315
+ ),
316
+ /* @__PURE__ */ jsxs4("span", { children: [
317
+ settings.labName || "Research",
318
+ settings.labNameAccent && /* @__PURE__ */ jsxs4("span", { className: styles4.navLogoAccent, children: [
319
+ " ",
320
+ settings.labNameAccent
321
+ ] })
322
+ ] })
323
+ ] }),
324
+ /* @__PURE__ */ jsx7("nav", { className: styles4.desktopNav, "aria-label": "Main navigation", children: navLinks.map((link) => /* @__PURE__ */ jsx7(
325
+ Link2,
326
+ {
327
+ href: link.href,
328
+ className: `${styles4.navLink} ${isActive(link.href) ? styles4.navLinkActive : ""}`,
329
+ children: link.label
330
+ },
331
+ link.href
332
+ )) }),
333
+ /* @__PURE__ */ jsxs4("div", { className: styles4.navActions, children: [
334
+ /* @__PURE__ */ jsx7(ThemeToggle, {}),
335
+ /* @__PURE__ */ jsx7(Link2, { href: "/contact", className: styles4.navCta, children: "Contact Us" })
336
+ ] }),
337
+ /* @__PURE__ */ jsx7(
338
+ "button",
339
+ {
340
+ className: styles4.menuButton,
341
+ onClick: () => setMenuOpen(!menuOpen),
342
+ "aria-label": menuOpen ? "Close menu" : "Open menu",
343
+ "aria-expanded": menuOpen,
344
+ "aria-controls": "mobile-menu",
345
+ children: /* @__PURE__ */ jsxs4("span", { className: styles4.menuButtonLines, children: [
346
+ /* @__PURE__ */ jsx7("span", { className: `${styles4.menuLine} ${menuOpen ? styles4.menuLineOpen1 : ""}` }),
347
+ /* @__PURE__ */ jsx7("span", { className: `${styles4.menuLine} ${menuOpen ? styles4.menuLineOpen2 : ""}` }),
348
+ /* @__PURE__ */ jsx7("span", { className: `${styles4.menuLine} ${menuOpen ? styles4.menuLineOpen3 : ""}` })
349
+ ] })
350
+ }
351
+ ),
352
+ /* @__PURE__ */ jsxs4(
353
+ "div",
354
+ {
355
+ id: "mobile-menu",
356
+ className: `${styles4.mobileMenu} ${menuOpen ? styles4.mobileMenuOpen : ""}`,
357
+ "aria-hidden": !menuOpen,
358
+ children: [
359
+ /* @__PURE__ */ jsx7(
360
+ "div",
361
+ {
362
+ className: styles4.mobileMenuOverlay,
363
+ onClick: () => setMenuOpen(false),
364
+ "aria-hidden": "true"
365
+ }
366
+ ),
367
+ /* @__PURE__ */ jsxs4(
368
+ "nav",
369
+ {
370
+ className: styles4.mobileMenuContent,
371
+ "aria-label": "Mobile navigation",
372
+ children: [
373
+ navLinks.map((link, index) => /* @__PURE__ */ jsx7(
374
+ Link2,
375
+ {
376
+ href: link.href,
377
+ className: `${styles4.mobileNavLink} ${isActive(link.href) ? styles4.mobileNavLinkActive : ""}`,
378
+ style: { animationDelay: `${index * 50}ms` },
379
+ tabIndex: menuOpen ? 0 : -1,
380
+ children: link.label
381
+ },
382
+ link.href
383
+ )),
384
+ /* @__PURE__ */ jsx7(
385
+ Link2,
386
+ {
387
+ href: "/contact",
388
+ className: styles4.mobileNavCta,
389
+ style: { animationDelay: `${navLinks.length * 50}ms` },
390
+ tabIndex: menuOpen ? 0 : -1,
391
+ children: "Contact Us"
392
+ }
393
+ )
394
+ ]
395
+ }
396
+ )
397
+ ]
398
+ }
399
+ )
400
+ ] }) });
401
+ }
402
+
403
+ // src/components/Footer.tsx
404
+ import Link3 from "next/link";
405
+ import styles5 from "./Footer.module.css";
406
+
407
+ // src/lib/utils.ts
408
+ function isValidExternalUrl(url) {
409
+ if (!url || typeof url !== "string") return false;
410
+ try {
411
+ const parsed = new URL(url);
412
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
413
+ } catch {
414
+ return false;
415
+ }
416
+ }
417
+
418
+ // src/components/Footer.tsx
419
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
420
+ var SocialIcon = ({ href, children }) => /* @__PURE__ */ jsx8("a", { href, target: "_blank", rel: "noopener noreferrer", className: styles5.socialIcon, children });
421
+ function Footer({ settings }) {
422
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
423
+ const showPrivacy = settings?.showPrivacyPolicy !== false;
424
+ const showTerms = settings?.showTerms !== false;
425
+ const hasLegalLinks = showPrivacy || showTerms;
426
+ return /* @__PURE__ */ jsx8("footer", { className: styles5.footerWrapper, children: /* @__PURE__ */ jsxs5("div", { className: styles5.footerContainer, children: [
427
+ /* @__PURE__ */ jsxs5("div", { className: styles5.mainFooter, children: [
428
+ /* @__PURE__ */ jsxs5("div", { className: styles5.footerAbout, children: [
429
+ /* @__PURE__ */ jsxs5(Link3, { href: "/", className: styles5.footerLogo, children: [
430
+ settings?.labName || "Cavendish",
431
+ " ",
432
+ /* @__PURE__ */ jsx8("span", { className: styles5.logoAccent, children: settings?.labNameAccent || "Lab" })
433
+ ] }),
434
+ /* @__PURE__ */ jsx8("p", { className: styles5.footerDescription, children: settings?.labNameDescription || "Advancing the frontiers of physics and our understanding of the universe." }),
435
+ /* @__PURE__ */ jsxs5("div", { className: styles5.socials, children: [
436
+ /* @__PURE__ */ jsx8(SocialIcon, { href: "#", children: /* @__PURE__ */ jsx8("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx8("path", { d: "M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z" }) }) }),
437
+ /* @__PURE__ */ jsx8(SocialIcon, { href: "#", children: /* @__PURE__ */ jsx8("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx8("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24h-6.617l-5.21-6.817-6.044 6.817h-3.308l7.73-8.835-7.73-10.668h6.78l4.522 6.312 5.59-6.312z" }) }) })
438
+ ] })
439
+ ] }),
440
+ /* @__PURE__ */ jsxs5("div", { className: styles5.footerLinksGrid, children: [
441
+ /* @__PURE__ */ jsxs5("div", { className: styles5.footerColumn, children: [
442
+ /* @__PURE__ */ jsx8("h4", { children: "Quick Links" }),
443
+ /* @__PURE__ */ jsx8(Link3, { href: "/research", children: "Research" }),
444
+ /* @__PURE__ */ jsx8(Link3, { href: "/publications", children: "Publications" }),
445
+ /* @__PURE__ */ jsx8(Link3, { href: "/pictures", children: "Gallery" })
446
+ ] }),
447
+ /* @__PURE__ */ jsxs5("div", { className: styles5.footerColumn, children: [
448
+ /* @__PURE__ */ jsx8("h4", { children: "About" }),
449
+ /* @__PURE__ */ jsx8(Link3, { href: "/news", children: "News" }),
450
+ /* @__PURE__ */ jsx8(Link3, { href: "/people", children: "Our Team" }),
451
+ /* @__PURE__ */ jsx8(Link3, { href: "/contact", children: "Contact" })
452
+ ] }),
453
+ /* @__PURE__ */ jsxs5("div", { className: styles5.footerColumn, children: [
454
+ /* @__PURE__ */ jsx8("h4", { children: "Resources" }),
455
+ /* @__PURE__ */ jsx8(Link3, { href: "https://www.cam.ac.uk/", children: "University" }),
456
+ /* @__PURE__ */ jsx8(Link3, { href: "#", children: "Careers" })
457
+ ] })
458
+ ] })
459
+ ] }),
460
+ /* @__PURE__ */ jsxs5("div", { className: styles5.footerBottomBar, children: [
461
+ /* @__PURE__ */ jsx8("p", { children: settings?.footerText || `\xA9 ${currentYear} The Research Group. All Rights Reserved.` }),
462
+ hasLegalLinks && /* @__PURE__ */ jsxs5("div", { className: styles5.legalLinks, children: [
463
+ showPrivacy && (settings?.privacyPolicyUrl && isValidExternalUrl(settings.privacyPolicyUrl) ? /* @__PURE__ */ jsx8("a", { href: settings.privacyPolicyUrl, target: "_blank", rel: "noopener noreferrer", children: "Privacy Policy" }) : /* @__PURE__ */ jsx8(Link3, { href: "/privacy", children: "Privacy Policy" })),
464
+ showTerms && (settings?.termsUrl && isValidExternalUrl(settings.termsUrl) ? /* @__PURE__ */ jsx8("a", { href: settings.termsUrl, target: "_blank", rel: "noopener noreferrer", children: "Terms & Conditions" }) : /* @__PURE__ */ jsx8(Link3, { href: "/terms", children: "Terms & Conditions" }))
465
+ ] })
466
+ ] })
467
+ ] }) });
468
+ }
469
+
470
+ // src/components/ClientLayout.tsx
471
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
472
+ function ThemeBodySync() {
473
+ const { resolvedTheme } = useTheme2();
474
+ useEffect5(() => {
475
+ if (resolvedTheme === "dark") {
476
+ document.body.classList.add("dark-mode");
477
+ document.body.classList.remove("light-mode");
478
+ } else {
479
+ document.body.classList.add("light-mode");
480
+ document.body.classList.remove("dark-mode");
481
+ }
482
+ }, [resolvedTheme]);
483
+ return null;
484
+ }
485
+ function ClientLayout({ children, settings }) {
486
+ return /* @__PURE__ */ jsxs6(ThemeProvider, { attribute: "class", defaultTheme: "light", enableSystem: false, children: [
487
+ /* @__PURE__ */ jsx9(ThemeBodySync, {}),
488
+ /* @__PURE__ */ jsxs6("div", { className: "pageWrapper", children: [
489
+ /* @__PURE__ */ jsx9(Header, { settings }),
490
+ /* @__PURE__ */ jsx9("main", { children }),
491
+ /* @__PURE__ */ jsx9(Footer, { settings })
492
+ ] })
493
+ ] });
494
+ }
495
+
496
+ // src/components/ContactCTA.tsx
497
+ import Link4 from "next/link";
498
+ import styles6 from "./ContactCTA.module.css";
499
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
500
+ function ContactCTA() {
501
+ return /* @__PURE__ */ jsx10("section", { className: styles6.ctaContainer, children: /* @__PURE__ */ jsxs7("div", { className: styles6.ctaContent, children: [
502
+ /* @__PURE__ */ jsx10("h2", { className: styles6.ctaHeading, children: "Interested in Our Research?" }),
503
+ /* @__PURE__ */ jsx10("p", { className: styles6.ctaText, children: "Follow our work, explore our publications, or get in touch to discuss potential collaborations." }),
504
+ /* @__PURE__ */ jsx10(Link4, { href: "/contact", className: styles6.ctaButton, children: "Contact Us" })
505
+ ] }) });
506
+ }
507
+ export {
508
+ AnimatedHero,
509
+ ClientLayout,
510
+ ContactCTA,
511
+ Footer,
512
+ Header,
513
+ ThemeToggle
514
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@prosophia/lab-minimal",
3
+ "version": "0.0.1",
4
+ "description": "Minimal lab website template for Prosophia",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./styles": "./dist/styles/index.css"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "lint": "eslint src/"
23
+ },
24
+ "peerDependencies": {
25
+ "framer-motion": "^11.0.0",
26
+ "next": "^14.0.0",
27
+ "react": "^18.0.0",
28
+ "react-dom": "^18.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/react": "^18.0.0",
32
+ "@types/react-dom": "^18.0.0",
33
+ "tsup": "^8.0.0",
34
+ "typescript": "^5.0.0"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/prosophia/prosophia-templates.git",
42
+ "directory": "packages/lab-minimal"
43
+ },
44
+ "license": "MIT"
45
+ }