diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5ac35df..d496398 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,14 +1,17 @@ import type { Metadata } from "next"; -import { Halant } from "next/font/google"; +import { DM_Sans } from "next/font/google"; import { Inter } from "next/font/google"; -import { Nunito_Sans } from "next/font/google"; import "./globals.css"; import { ServiceWrapper } from "@/components/ServiceWrapper"; import Tag from "@/tag/Tag"; -import { Open_Sans } from "next/font/google"; - +const dmSans = DM_Sans({ + variable: "--font-dm-sans", subsets: ["latin"], +}); +const inter = Inter({ + variable: "--font-inter", subsets: ["latin"], +}); export const metadata: Metadata = { title: "UNBRKABLE Pre-Order Waitlist - March 15, 2026", description: "Join the UNBRKABLE waitlist and be the first to know. Exclusive pre-order access launching March 15, 2026. Limited availability.", keywords: "UNBRKABLE, pre-order, waitlist, exclusive, launch, 2026", metadataBase: new URL("https://unbrkable.com"), @@ -24,15 +27,6 @@ export const metadata: Metadata = { }, }; -const inter = Inter({ - variable: "--font-inter", - subsets: ["latin"], -}); -const openSans = Open_Sans({ - variable: "--font-open-sans", - subsets: ["latin"], -}); - export default function RootLayout({ children, }: Readonly<{ @@ -41,7 +35,9 @@ export default function RootLayout({ return ( - + {children} @@ -286,7 +282,9 @@ export default function RootLayout({ const getElementInfo = (element, assignId = false) => { const rect = element.getBoundingClientRect(); const tagName = element.tagName.toLowerCase(); - + const selector = getUniqueSelector(element, assignId); + const sectionId = getSectionId(element); + let className = undefined; try { if (element.className) { @@ -314,7 +312,8 @@ export default function RootLayout({ }; if (tagName === 'img') { - info.imageData = { + const originalSrc = extractOriginalUrl(element.src); + info.imageData = { src: originalSrc, alt: element.alt || undefined, naturalWidth: element.naturalWidth, @@ -325,7 +324,8 @@ export default function RootLayout({ if (tagName === 'video') { const rawSrc = element.src || element.currentSrc || (element.querySelector('source') && element.querySelector('source').src) || ''; - info.imageData = { + const resolvedSrc = extractOriginalUrl(rawSrc); + info.imageData = { src: resolvedSrc, alt: element.getAttribute('aria-label') || undefined, isBackground: false, @@ -338,7 +338,8 @@ export default function RootLayout({ if (backgroundImage && backgroundImage !== 'none') { const urlMatch = backgroundImage.match(/url(['"]?([^'")]+)['"]?)/); if (urlMatch) { - if (tagName !== 'img') { + const originalBgSrc = extractOriginalUrl(urlMatch[1]); + if (tagName !== 'img') { info.imageData = { src: originalBgSrc, isBackground: true @@ -350,7 +351,8 @@ export default function RootLayout({ } } - info.elementType = elementType; + const elementType = getElementType(element); + info.elementType = elementType; if (elementType === 'Button') { const buttonText = element.textContent?.trim() || element.value || element.getAttribute('aria-label') || ''; @@ -443,11 +445,13 @@ export default function RootLayout({ }; const isTextElement = (element) => { - return elementType === 'Text'; + const elementType = getElementType(element); + return elementType === 'Text'; }; const isButtonElement = (element) => { - return elementType === 'Button'; + const elementType = getElementType(element); + return elementType === 'Button'; }; const updateButtonText = (element, newText) => { @@ -522,7 +526,8 @@ export default function RootLayout({ }; const handleInput = () => { - let currentText = element.textContent; + const elementInfo = getElementInfo(element); + let currentText = element.textContent; // Ensure there's always at least a space to keep the element editable if (currentText === '' || currentText === null || currentText.length === 0) { @@ -635,7 +640,8 @@ export default function RootLayout({ }, '*'); if (save && originalContent !== element.textContent) { - let finalText = element.textContent; + const elementInfo = getElementInfo(element); + let finalText = element.textContent; // Trim the final text and convert space-only to empty string for saving if (finalText === ' ' || finalText.trim() === '') { @@ -764,7 +770,7 @@ export default function RootLayout({ lastMouseX = e.clientX; lastMouseY = e.clientY; - || e.target; + const target = getMostSpecificElement(e.clientX, e.clientY) || e.target; if (!isValidElement(target) || target === hoveredElement || target === selectedElement) { return; @@ -796,7 +802,8 @@ export default function RootLayout({ hoverOverlay = createHoverOverlay(target); } - showElementTypeLabel(target, elementType); + const elementType = getElementType(target); + showElementTypeLabel(target, elementType); window.parent.postMessage({ type: 'webild-element-hover', @@ -838,7 +845,7 @@ export default function RootLayout({ e.preventDefault(); e.stopPropagation(); - || e.target; + const target = getMostSpecificElement(e.clientX, e.clientY) || e.target; if (!isValidElement(target)) return; if (selectedElement && selectedElement !== target) { @@ -883,7 +890,8 @@ export default function RootLayout({ hoveredElement = null; } - selectedElement.dataset.webildSelector = elementInfo.selector; + const elementInfo = getElementInfo(target, true); + selectedElement.dataset.webildSelector = elementInfo.selector; showElementTypeLabel(target, elementInfo.elementType); window.parent.postMessage({ @@ -966,7 +974,8 @@ export default function RootLayout({ isScrolling = false; if (lastMouseX > 0 && lastMouseY > 0) { - if (target && isValidElement(target) && target !== selectedElement) { + const target = getMostSpecificElement(lastMouseX, lastMouseY); + if (target && isValidElement(target) && target !== selectedElement) { hoveredElement = target; const computedStyle = window.getComputedStyle(target); @@ -980,7 +989,8 @@ export default function RootLayout({ hoveredElement.classList.add(hoverClass); hoverOverlay = createHoverOverlay(target); - showElementTypeLabel(target, elementType); + const elementType = getElementType(target); + showElementTypeLabel(target, elementType); window.parent.postMessage({ type: 'webild-element-hover', @@ -1003,7 +1013,8 @@ export default function RootLayout({ const saveChangeToStorage = (change) => { try { - const existingChanges = JSON.parse(localStorage.getItem(storageKey) || '[]'); + const storageKey = getStorageKey(); + const existingChanges = JSON.parse(localStorage.getItem(storageKey) || '[]'); const filteredChanges = existingChanges.filter(c => { return !(c.oldValue === change.oldValue && c.sectionId === change.sectionId); @@ -1023,7 +1034,8 @@ export default function RootLayout({ const clearLocalChanges = () => { try { - localStorage.removeItem(storageKey); + const storageKey = getStorageKey(); + localStorage.removeItem(storageKey); window.parent.postMessage({ type: 'webild-local-changes-cleared', data: {} @@ -1072,7 +1084,8 @@ export default function RootLayout({ if (e.data.type === 'webild-cancel-changes') { try { - const savedChanges = localStorage.getItem(storageKey); + const storageKey = getStorageKey(); + const savedChanges = localStorage.getItem(storageKey); if (savedChanges) { const changes = JSON.parse(savedChanges); changes.forEach(change => { @@ -1094,7 +1107,8 @@ export default function RootLayout({ if (isBackground) { element.style.backgroundImage = change.oldValue ? 'url(' + change.oldValue + ')' : ''; } else { - if (revertTag === 'video' && oldMediaType === 'image') { + const oldMediaType = getMediaTypeFromUrl(change.oldValue); + if (revertTag === 'video' && oldMediaType === 'image') { swapMediaElement(element, 'img', change.oldValue); } else if (revertTag === 'img' && oldMediaType === 'video') { swapMediaElement(element, 'video', change.oldValue); @@ -1142,7 +1156,8 @@ export default function RootLayout({ const el = textElements[i]; if (isTextElement(el) && el.textContent.trim() === (oldValue || '').trim()) { element = el; - if (newSelector) { + const newSelector = getUniqueSelector(element, true); + if (newSelector) { element.dataset.webildSelector = newSelector; } break; @@ -1233,8 +1248,10 @@ export default function RootLayout({ replaced = true; } else if (element.tagName.toLowerCase() === 'img') { oldValue = element.src; - if (newMediaType === 'video' && allowMediaTypeSwap) { - if (selectedElement === element) selectedElement = swapped; + const newMediaType = getMediaTypeFromUrl(newSrc); + if (newMediaType === 'video' && allowMediaTypeSwap) { + const swapped = swapMediaElement(element, 'video', newSrc); + if (selectedElement === element) selectedElement = swapped; element = swapped; } else { element.src = newSrc; @@ -1242,9 +1259,11 @@ export default function RootLayout({ replaced = true; } else if (element.tagName.toLowerCase() === 'video') { oldValue = element.src || element.currentSrc || ''; - const sources = element.querySelectorAll('source'); + const newMediaType = getMediaTypeFromUrl(newSrc); + const sources = element.querySelectorAll('source'); if (newMediaType === 'image' && allowMediaTypeSwap) { - if (selectedElement === element) selectedElement = swapped; + const swapped = swapMediaElement(element, 'img', newSrc); + if (selectedElement === element) selectedElement = swapped; element = swapped; } else { if (sources.length > 0) { @@ -1266,7 +1285,8 @@ export default function RootLayout({ } if (replaced) { - + const elementInfo = getElementInfo(element); + let cleanOldValue = oldValue; if (oldValue.includes('url(')) { const urlMatch = oldValue.match(/url(['"]?([^'")]+)['"]?)/); @@ -1337,7 +1357,13 @@ export default function RootLayout({ } }, true); - + const urlCheckInterval = setInterval(() => { + if (lastPathname !== window.location.pathname) { + lastPathname = window.location.pathname; + notifyPageChange(); + } + }, 500); + notifyPageChange(); window.webildCleanup = () => { diff --git a/src/app/page.tsx b/src/app/page.tsx index 5ae0147..da510dd 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,20 +6,49 @@ import HeroBillboardCarousel from "@/components/sections/hero/HeroBillboardCarou import ProductCardThree from "@/components/sections/product/ProductCardThree"; import SocialProofOne from "@/components/sections/socialProof/SocialProofOne"; import FooterCard from "@/components/sections/footer/FooterCard"; -import { Instagram } from "lucide-react"; +import { Instagram, Clock } from "lucide-react"; +import { useEffect, useState } from "react"; export default function LandingPage() { + const [timeLeft, setTimeLeft] = useState({ + days: 0, + hours: 0, + minutes: 0, + seconds: 0, + }); + + useEffect(() => { + const calculateTimeLeft = () => { + const targetDate = new Date("2026-03-15T15:00:00+01:00").getTime(); + const now = new Date().getTime(); + const difference = targetDate - now; + + if (difference > 0) { + setTimeLeft({ + days: Math.floor(difference / (1000 * 60 * 60 * 24)), + hours: Math.floor((difference / (1000 * 60 * 60)) % 24), + minutes: Math.floor((difference / 1000 / 60) % 60), + seconds: Math.floor((difference / 1000) % 60), + }); + } + }; + + calculateTimeLeft(); + const timer = setInterval(calculateTimeLeft, 1000); + return () => clearInterval(timer); + }, []); + return ( +
+
+
+ +

LAUNCHING MARCH 15, 2026 AT 3:00 PM CET

+
+
+ {[ + { label: "DAYS", value: timeLeft.days }, + { label: "HOURS", value: timeLeft.hours }, + { label: "MINUTES", value: timeLeft.minutes }, + { label: "SECONDS", value: timeLeft.seconds }, + ].map((unit, idx) => ( +
+
+
+
+ {String(unit.value).padStart(2, "0")} +
+
+ {unit.label} +
+
+
+ ))} +
+
+
+ +
+
+
+

+ GET EXCLUSIVE ACCESS +

+

+ Join our waitlist and be among the first to experience UNBRKABLE +

+
{ + e.preventDefault(); + console.log("Email captured"); + }} + > + + +
+

+ ✓ We respect your privacy. Unsubscribe at any time. +

+
+
+
+ +
+
+
+

+ UNBREAKABLE RESILIENCE +

+

+ Designed for those who never back down. Premium streetwear built to last through anything. +

+
+
+
+
+ + + +

TEASER VIDEO

+
+
+
+
+
+
diff --git a/src/app/styles/base.css b/src/app/styles/base.css index 52de42f..c480bac 100644 --- a/src/app/styles/base.css +++ b/src/app/styles/base.css @@ -11,7 +11,7 @@ html { body { background-color: var(--background); color: var(--foreground); - font-family: var(--font-open-sans), sans-serif; + font-family: var(--font-dm-sans), sans-serif; position: relative; min-height: 100vh; overscroll-behavior: none; @@ -24,5 +24,5 @@ h3, h4, h5, h6 { - font-family: var(--font-inter), sans-serif; + font-family: var(--font-dm-sans), sans-serif; } diff --git a/src/app/styles/variables.css b/src/app/styles/variables.css index ddbbc1e..0641793 100644 --- a/src/app/styles/variables.css +++ b/src/app/styles/variables.css @@ -2,23 +2,23 @@ /* Base units */ /* --vw is set by ThemeProvider */ - /* --background: #f5f4ef;; - --card: #dad6cd;; - --foreground: #2a2928;; - --primary-cta: #2a2928;; - --secondary-cta: #ecebea;; - --accent: #ffffff;; - --background-accent: #c6b180;; */ + /* --background: #000000;; + --card: #0c0c0c;; + --foreground: #ffffff;; + --primary-cta: #ff6b6b;; + --secondary-cta: #000000;; + --accent: #ff6b6b;; + --background-accent: #ff6b6b;; */ - --background: #f5f4ef;; - --card: #dad6cd;; - --foreground: #2a2928;; - --primary-cta: #2a2928;; + --background: #000000;; + --card: #0c0c0c;; + --foreground: #ffffff;; + --primary-cta: #ff6b6b;; --primary-cta-text: #f5f4ef;; - --secondary-cta: #ecebea;; + --secondary-cta: #000000;; --secondary-cta-text: #2a2928;; - --accent: #ffffff;; - --background-accent: #c6b180;; + --accent: #ff6b6b;; + --background-accent: #ff6b6b;; /* text sizing - set by ThemeProvider */ /* --text-2xs: clamp(0.465rem, 0.62vw, 0.62rem);