@smbcloud / nord / commits / 01cd847

convert to ssr

Seto committed Apr 5, 2026 at 14:31 UTC 01cd847828da8ab5d676dd69fda46035bc1ecfa0
17 files changed +775 -571
app/layout.tsx
+7 -8
@@ -1,6 +1,8 @@
1 import type { Metadata } from "next";
2 import { Inter } from "next/font/google";
3 import "./globals.css";
4 +import NordHeader from "@/components/nord-header";
5 +import NordNav from "@/components/nord-nav";
6
7 const inter = Inter({
8 subsets: ["latin"],
@@ -10,19 +12,16 @@ const inter = Inter({
12
13 export const metadata: Metadata = {
14 title: "NORD — Elite Style OS",
13 - description:
14 - "A curated gallery-to-booking pipeline for Scandinavian beauty. One tap to transform.",
15 + description: "A curated gallery-to-booking pipeline for Scandinavian beauty. One tap to transform.",
16 };
17
17 -export default function RootLayout({
18 - children,
19 -}: Readonly<{
20 - children: React.ReactNode;
21 -}>) {
18 +export default function RootLayout({ children }: { children: React.ReactNode }) {
19 return (
20 <html lang="en" className={inter.className}>
21 <body className="bg-nord-black text-nord-white antialiased">
25 - {children}
22 + <NordHeader />
23 + <main>{children}</main>
24 + <NordNav />
25 </body>
26 </html>
27 );
app/me/page.tsx new
+30
@@ -0,0 +1,30 @@
1 +import type { Metadata } from "next";
2 +import MeForm from "@/components/me/me-form";
3 +
4 +export const metadata: Metadata = {
5 + title: "Me — NORD",
6 + description: "Sign in or create your NORD member account.",
7 +};
8 +
9 +export default function MePage() {
10 + return (
11 + <div className="pt-24 pb-32 px-6 max-w-md mx-auto">
12 + {/* Wordmark */}
13 + <div className="flex flex-col items-center py-10 space-y-3">
14 + <h2 className="text-4xl font-light tracking-brand text-nord-white">NORD</h2>
15 + <p className="text-[10px] uppercase tracking-widest text-nord-white/30">Member Access</p>
16 + </div>
17 +
18 + <MeForm />
19 +
20 + {/* Footer links */}
21 + <div className="mt-10 flex items-center justify-center gap-4">
22 + <a href="/privacy" className="text-[9px] uppercase tracking-widest text-nord-white/20 hover:text-nord-white/50 transition-colors">Privacy</a>
23 + <span className="text-nord-white/10">·</span>
24 + <a href="/terms" className="text-[9px] uppercase tracking-widest text-nord-white/20 hover:text-nord-white/50 transition-colors">Terms</a>
25 + <span className="text-nord-white/10">·</span>
26 + <a href="/contact" className="text-[9px] uppercase tracking-widest text-nord-white/20 hover:text-nord-white/50 transition-colors">Contact</a>
27 + </div>
28 + </div>
29 + );
30 +}
app/page.tsx
+7 -559
@@ -1,410 +1,14 @@
1 -"use client";
1 +import { POSTS, STORIES } from "@/lib/data";
2 +import StoriesBar from "@/components/feed/stories-bar";
3 +import FeedPost from "@/components/feed/feed-post";
4
3 -import Image from "next/image";
4 -import { useState } from "react";
5 -
6 -// ─── Types ────────────────────────────────────────────────────────────────────
7 -
8 -type Tab = "feed" | "studio" | "me";
9 -
10 -interface Story {
11 - id: number;
12 - handle: string;
13 - avatar: string;
14 - seen: boolean;
15 -}
16 -
17 -interface Post {
18 - id: number;
19 - stylist: string;
20 - handle: string;
21 - location: string;
22 - avatar: string;
23 - image: string;
24 - likes: number;
25 - caption: string;
26 - tags: string[];
27 - time: string;
28 - style: string;
29 -}
30 -
31 -// ─── Feed data ────────────────────────────────────────────────────────────────
32 -
33 -const STORIES: Story[] = [
34 - { id: 1, handle: "anders", avatar: "A", seen: false },
35 - { id: 2, handle: "maja", avatar: "M", seen: false },
36 - { id: 3, handle: "erik", avatar: "E", seen: true },
37 - { id: 4, handle: "linn", avatar: "L", seen: false },
38 - { id: 5, handle: "bjorn", avatar: "B", seen: true },
39 - { id: 6, handle: "saga", avatar: "S", seen: false },
40 - { id: 7, handle: "oskar", avatar: "O", seen: true },
41 -];
42 -
43 -const POSTS: Post[] = [
44 - {
45 - id: 1,
46 - stylist: "Anders Lindqvist",
47 - handle: "@anderscuts",
48 - location: "Östermalm, Stockholm",
49 - avatar: "A",
50 - image: "https://images.unsplash.com/photo-1522337360788-8b13dee7a37e?q=80&w=800&auto=format&fit=crop",
51 - likes: 1204,
52 - caption: "Golden hour, golden strands. Precision silk blowout on Maja — three hours of work that disappears into the light.",
53 - tags: ["#NORDstyle", "#BlondeScandi", "#SilkBlowout"],
54 - time: "34m",
55 - style: "Silk Blowout",
56 - },
57 - {
58 - id: 2,
59 - stylist: "Erik Bergström",
60 - handle: "@erikb.studio",
61 - location: "Södermalm, Stockholm",
62 - avatar: "E",
63 - image: "https://images.unsplash.com/photo-1580618672591-eb180b1a973f?q=80&w=800&auto=format&fit=crop",
64 - likes: 847,
65 - caption: "The Sharp Edge. Every millimetre deliberate. This is what precision fade looks like when you stop rushing.",
66 - tags: ["#NORDmen", "#PrecisionFade", "#ScandiFade"],
67 - time: "2h",
68 - style: "Precision Fade",
69 - },
70 - {
71 - id: 3,
72 - stylist: "Maja Söderström",
73 - handle: "@maja.nord",
74 - location: "Vasastan, Stockholm",
75 - avatar: "M",
76 - image: "https://images.unsplash.com/photo-1559599101-f09722fb4948?q=80&w=800&auto=format&fit=crop",
77 - likes: 2341,
78 - caption: "Ash and ice. When the colour does the talking, the cut just has to be perfect. Nothing more. Nothing less.",
79 - tags: ["#AshBlonde", "#IceHair", "#NORDcolour"],
80 - time: "5h",
81 - style: "Ash Blonde",
82 - },
83 - {
84 - id: 4,
85 - stylist: "Johan Åkerlund",
86 - handle: "@johancuts",
87 - location: "Kungsholmen, Stockholm",
88 - avatar: "J",
89 - image: "https://images.unsplash.com/photo-1503951914875-452162b0f3f1?q=80&w=800&auto=format&fit=crop",
90 - likes: 619,
91 - caption: "Texture over length. A modern men's cut built around movement — not product. The hair should do the work.",
92 - tags: ["#TextureCut", "#NORDmen", "#ModernBarber"],
93 - time: "8h",
94 - style: "Texture Cut",
95 - },
96 - {
97 - id: 5,
98 - stylist: "Linn Ekström",
99 - handle: "@linnekstrom",
100 - location: "Lidingö, Stockholm",
101 - avatar: "L",
102 - image: "https://images.unsplash.com/photo-1534528741775-53994a69daeb?q=80&w=800&auto=format&fit=crop",
103 - likes: 3108,
104 - caption: "Nordic natural. No filter needed when the light in Stockholm is this good and the hair is this healthy.",
105 - tags: ["#NordicNatural", "#HealthyHair", "#NORDglow"],
106 - time: "12h",
107 - style: "Nordic Natural",
108 - },
109 - {
110 - id: 6,
111 - stylist: "Björn Hallgren",
112 - handle: "@bjornhall",
113 - location: "Stureplan, Stockholm",
114 - avatar: "B",
115 - image: "https://images.unsplash.com/photo-1621605815971-fbc98d665033?q=80&w=800&auto=format&fit=crop",
116 - likes: 988,
117 - caption: "The Obsidian Line. Clean architecture for the modern Stockholmer. Zero tolerance for the almost-right.",
118 - tags: ["#ObsidianLine", "#CleanCut", "#NORDmen"],
119 - time: "1d",
120 - style: "Obsidian Line",
121 - },
122 - {
123 - id: 7,
124 - stylist: "Saga Lindgren",
125 - handle: "@saga.cuts",
126 - location: "Djurgården, Stockholm",
127 - avatar: "S",
128 - image: "https://images.unsplash.com/photo-1517841905240-472988babdf9?q=80&w=800&auto=format&fit=crop",
129 - likes: 4472,
130 - caption: "Effortless. That is the hardest thing to achieve. Six years of craft distilled into one afternoon appointment.",
131 - tags: ["#Effortless", "#NORDwoman", "#ScandinavianBeauty"],
132 - time: "1d",
133 - style: "Effortless",
134 - },
135 -];
136 -
137 -// ─── Studio data ──────────────────────────────────────────────────────────────
138 -
139 -const STUDIOS = [
140 - {
141 - id: 1,
142 - name: "NORD",
143 - location: "Hågerstensvägen 15, Stockholm",
144 - neighbourhood: "Hägersten",
145 - rating: 4.8,
146 - reviews: 124,
147 - price: "$$",
148 - openNow: true,
149 - hours: "09:00 – 19:00",
150 - phone: "+46 72-853 82 88",
151 - services: ["Precision Cut", "Fade", "Beard Trim"],
152 - image: "https://images.unsplash.com/photo-1521590832167-7bcbfaa6381f?q=80&w=800&auto=format&fit=crop",
153 - },
154 - {
155 - id: 2,
156 - name: "Barber Nord",
157 - location: "Hägerstens Torg 8, Stockholm",
158 - neighbourhood: "Södermalm",
159 - rating: 4.6,
160 - reviews: 89,
161 - price: "$$",
162 - openNow: true,
163 - hours: "10:00 – 18:00",
164 - phone: "+46 8 234 5678",
165 - services: ["Barbershop", "Beard Trimming"],
166 - image: "https://images.unsplash.com/photo-1585747860715-2ba37e788b70?q=80&w=800&auto=format&fit=crop",
167 - },
168 - {
169 - id: 3,
170 - name: "Classic Cut Stockholm",
171 - location: "Liljeholmsvägen 22, Stockholm",
172 - neighbourhood: "Liljeholmen",
173 - rating: 4.7,
174 - reviews: 156,
175 - price: "$$$",
176 - openNow: true,
177 - hours: "09:00 – 19:00",
178 - phone: "+46 8 345 6789",
179 - services: ["Hair Salon", "Grooming", "Styling"],
180 - image: "https://images.unsplash.com/photo-1560066984-138dadb4c035?q=80&w=800&auto=format&fit=crop",
181 - },
182 - {
183 - id: 4,
184 - name: "Style Master",
185 - location: "Granbergsvägen 33, Stockholm",
186 - neighbourhood: "Kungsholmen",
187 - rating: 4.9,
188 - reviews: 203,
189 - price: "$$$",
190 - openNow: false,
191 - hours: "Opens 10:00",
192 - phone: "+46 8 567 8901",
193 - services: ["Hair Salon", "Colour", "Styling"],
194 - image: "https://images.unsplash.com/photo-1562322140-8baeececf3df?q=80&w=800&auto=format&fit=crop",
195 - },
196 - {
197 - id: 5,
198 - name: "The Classic Barber",
199 - location: "Telefonvägen 12, Stockholm",
200 - neighbourhood: "Vasastan",
201 - rating: 4.8,
202 - reviews: 145,
203 - price: "$$",
204 - openNow: true,
205 - hours: "09:00 – 18:00",
206 - phone: "+46 8 789 0123",
207 - services: ["Traditional Shave", "Barbershop"],
208 - image: "https://images.unsplash.com/photo-1512690459411-b9245aed614b?q=80&w=800&auto=format&fit=crop",
209 - },
210 -] as const;
211 -
212 -// ─── Icons ────────────────────────────────────────────────────────────────────
213 -
214 -function IconFeed({ size = 18 }: { size?: number }) {
5 +export default function FeedPage() {
6 return (
216 - <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
217 - <rect x="3" y="3" width="7" height="7" /><rect x="14" y="3" width="7" height="7" />
218 - <rect x="3" y="14" width="7" height="7" /><rect x="14" y="14" width="7" height="7" />
219 - </svg>
220 - );
221 -}
222 -function IconStudio({ size = 18 }: { size?: number }) {
223 - return (
224 - <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
225 - <path d="M12 2L2 7l10 5 10-5-10-5z" /><path d="M2 17l10 5 10-5" /><path d="M2 12l10 5 10-5" />
226 - </svg>
227 - );
228 -}
229 -function IconMe({ size = 18 }: { size?: number }) {
230 - return (
231 - <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
232 - <circle cx="12" cy="8" r="4" /><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" />
233 - </svg>
234 - );
235 -}
236 -function HeartIcon({ filled }: { filled: boolean }) {
237 - return (
238 - <svg width="24" height="24" viewBox="0 0 24 24" fill={filled ? "#fff" : "none"}>
239 - <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l8.78-8.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
240 - stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
241 - </svg>
242 - );
243 -}
244 -function CommentIcon() {
245 - return (
246 - <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
247 - <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
248 - stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
249 - </svg>
250 - );
251 -}
252 -function BookmarkIcon({ filled }: { filled: boolean }) {
253 - return (
254 - <svg width="24" height="24" viewBox="0 0 24 24" fill={filled ? "#fff" : "none"}>
255 - <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"
256 - stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
257 - </svg>
258 - );
259 -}
260 -function ShareIcon() {
261 - return (
262 - <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
263 - <line x1="22" y1="2" x2="11" y2="13" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
264 - <polygon points="22 2 15 22 11 13 2 9 22 2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
265 - </svg>
266 - );
267 -}
268 -function IconStar() {
269 - return <svg width="12" height="12" viewBox="0 0 24 24" className="fill-yellow-400"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" /></svg>;
270 -}
271 -
272 -// ─── Nav Button ───────────────────────────────────────────────────────────────
273 -
274 -function NavButton({ label, icon, active, onClick }: { label: string; icon: React.ReactNode; active: boolean; onClick: () => void }) {
275 - return (
276 - <button onClick={onClick} className={`flex flex-col items-center gap-1 transition-all duration-200 ${active ? "opacity-100" : "opacity-35 hover:opacity-70"}`}>
277 - <div className={`transition-transform duration-200 ${active ? "scale-110" : ""}`}>{icon}</div>
278 - <span className="text-[9px] uppercase tracking-widest text-nord-white">{label}</span>
279 - <div className={`w-1 h-1 rounded-full bg-nord-white transition-opacity duration-200 ${active ? "opacity-100" : "opacity-0"}`} />
280 - </button>
281 - );
282 -}
283 -
284 -// ─── Feed components ──────────────────────────────────────────────────────────
285 -
286 -function Avatar({ letter, size = "md" }: { letter: string; size?: "sm" | "md" | "lg" }) {
287 - const sizes = { sm: "w-8 h-8 text-xs", md: "w-9 h-9 text-sm", lg: "w-14 h-14 text-base" };
288 - return (
289 - <div className={`${sizes[size]} rounded-full bg-gradient-to-br from-zinc-600 to-zinc-800 border border-white/10 flex items-center justify-center font-semibold text-nord-white flex-shrink-0`}>
290 - {letter}
291 - </div>
292 - );
293 -}
294 -
295 -function StoryRing({ story, onPress }: { story: Story; onPress: () => void }) {
296 - return (
297 - <button onClick={onPress} className="flex flex-col items-center gap-1.5 flex-shrink-0 active:opacity-70 transition-opacity">
298 - <div className={`p-[2px] rounded-full ${story.seen ? "bg-white/15" : "bg-gradient-to-tr from-white/80 via-white/40 to-white/80"}`}>
299 - <div className="p-[2px] rounded-full bg-nord-black">
300 - <Avatar letter={story.avatar} size="lg" />
301 - </div>
302 - </div>
303 - <span className="text-[10px] text-nord-white/60 tracking-tight max-w-[52px] truncate">{story.handle}</span>
304 - </button>
305 - );
306 -}
307 -
308 -function FeedPost({ post, onBook }: { post: Post; onBook: () => void }) {
309 - const [liked, setLiked] = useState(false);
310 - const [saved, setSaved] = useState(false);
311 - const [likeCount, setLikeCount] = useState(post.likes);
7 + <div className="pt-24 pb-32 max-w-2xl mx-auto">
8 + <StoriesBar stories={STORIES} />
9
313 - return (
314 - <article className="border-b border-white/6">
315 - {/* Header */}
316 - <div className="flex items-center justify-between px-4 py-3">
317 - <div className="flex items-center gap-3">
318 - <div className="p-[2px] rounded-full bg-gradient-to-tr from-white/60 to-white/20">
319 - <div className="p-[2px] rounded-full bg-nord-black">
320 - <Avatar letter={post.avatar} size="sm" />
321 - </div>
322 - </div>
323 - <div>
324 - <p className="text-nord-white text-sm font-semibold leading-tight">{post.handle}</p>
325 - <p className="text-nord-white/40 text-[10px] uppercase tracking-widest leading-tight mt-0.5">{post.location}</p>
326 - </div>
327 - </div>
328 - <button
329 - onClick={onBook}
330 - className="text-[10px] font-bold uppercase tracking-widest text-nord-white border border-white/20 px-3 py-1.5 hover:bg-white/5 transition-colors"
331 - >
332 - Book
333 - </button>
334 - </div>
335 -
336 - {/* Image */}
337 - <div className="relative w-full aspect-square overflow-hidden bg-nord-gray">
338 - <Image src={post.image} alt={post.style} fill className="object-cover" sizes="100vw" />
339 - <div className="absolute bottom-3 left-3">
340 - <span className="bg-nord-black/60 backdrop-blur-sm text-nord-white/80 text-[9px] uppercase tracking-widest px-2 py-1 border border-white/10">
341 - {post.style}
342 - </span>
343 - </div>
344 - </div>
345 -
346 - {/* Actions */}
347 - <div className="px-4 pt-3 pb-1 flex items-center justify-between">
348 - <div className="flex items-center gap-4">
349 - <button
350 - onClick={() => { setLiked(p => !p); setLikeCount(p => liked ? p - 1 : p + 1); }}
351 - className={`transition-all duration-150 active:scale-90 ${liked ? "text-white" : "text-nord-white/70"}`}
352 - >
353 - <HeartIcon filled={liked} />
354 - </button>
355 - <button className="text-nord-white/70"><CommentIcon /></button>
356 - <button className="text-nord-white/70"><ShareIcon /></button>
357 - </div>
358 - <button
359 - onClick={() => setSaved(p => !p)}
360 - className={`transition-all duration-150 active:scale-90 ${saved ? "text-white" : "text-nord-white/70"}`}
361 - >
362 - <BookmarkIcon filled={saved} />
363 - </button>
364 - </div>
365 -
366 - {/* Likes + caption */}
367 - <div className="px-4 pt-1 pb-4 space-y-1">
368 - <p className="text-nord-white text-sm font-semibold">{likeCount.toLocaleString()} likes</p>
369 - <p className="text-sm text-nord-white/90 leading-relaxed">
370 - <span className="font-semibold mr-1">{post.handle}</span>{post.caption}
371 - </p>
372 - <p className="text-[11px] text-nord-white/35 tracking-wide">{post.tags.join(" ")}</p>
373 - <p className="text-[10px] text-nord-white/25 uppercase tracking-widest pt-0.5">{post.time} ago</p>
374 - </div>
375 - </article>
376 - );
377 -}
378 -
379 -function FeedTab({ onBook }: { onBook: () => void }) {
380 - const [seenStories, setSeenStories] = useState<Set<number>>(new Set());
381 -
382 - return (
383 - <div className="pt-24 pb-32">
384 - {/* Stories bar */}
385 - <div className="overflow-x-auto flex gap-4 px-4 py-4 border-b border-white/6" style={{ scrollbarWidth: "none" }}>
386 - {/* Your story slot */}
387 - <button className="flex flex-col items-center gap-1.5 flex-shrink-0 active:opacity-70 transition-opacity">
388 - <div className="w-14 h-14 rounded-full border-2 border-dashed border-white/20 flex items-center justify-center">
389 - <svg width="20" height="20" viewBox="0 0 24 24" fill="none" className="text-nord-white/40">
390 - <line x1="12" y1="5" x2="12" y2="19" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
391 - <line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
392 - </svg>
393 - </div>
394 - <span className="text-[10px] text-nord-white/40 tracking-tight">your story</span>
395 - </button>
396 - {STORIES.map((story) => (
397 - <StoryRing
398 - key={story.id}
399 - story={{ ...story, seen: seenStories.has(story.id) || story.seen }}
400 - onPress={() => setSeenStories(prev => new Set(prev).add(story.id))}
401 - />
402 - ))}
403 - </div>
404 -
405 - {/* Posts */}
10 {POSTS.map((post) => (
407 - <FeedPost key={post.id} post={post} onBook={onBook} />
11 + <FeedPost key={post.id} post={post} />
12 ))}
13
14 {/* End of feed */}
@@ -420,159 +24,3 @@ function FeedTab({ onBook }: { onBook: () => void }) {
24 </div>
25 );
26 }
423 -
424 -// ─── Studio Tab ───────────────────────────────────────────────────────────────
425 -
426 -function StudioCard({ studio }: { studio: (typeof STUDIOS)[number] }) {
427 - return (
428 - <article className="border-b border-white/6 last:border-0">
429 - <div className="relative w-full h-52 overflow-hidden bg-nord-gray">
430 - <Image src={studio.image} alt={studio.name} fill className="object-cover brightness-75 hover:brightness-90 transition-all duration-500" sizes="100vw" />
431 - <div className="absolute inset-0 bg-gradient-to-t from-nord-black/80 via-transparent to-transparent" />
432 - <div className="absolute top-4 right-4">
433 - <span className={`text-[9px] uppercase tracking-widest font-medium px-2.5 py-1 border ${studio.openNow ? "border-emerald-500/40 text-emerald-400 bg-emerald-950/60" : "border-red-500/30 text-red-400/80 bg-red-950/60"}`}>
434 - {studio.openNow ? "Open" : "Closed"}
435 - </span>
436 - </div>
437 - <div className="absolute bottom-4 left-4">
438 - <span className="text-[9px] uppercase tracking-widest text-nord-white/50">{studio.neighbourhood}</span>
439 - </div>
440 - </div>
441 - <div className="px-6 py-5">
442 - <div className="flex items-start justify-between mb-3">
443 - <h3 className="text-lg font-semibold text-nord-white tracking-tightest">{studio.name}</h3>
444 - <div className="flex items-center gap-1.5 mt-0.5">
445 - <IconStar />
446 - <span className="text-sm font-semibold text-nord-white">{studio.rating}</span>
447 - <span className="text-nord-white/30 text-xs">({studio.reviews})</span>
448 - </div>
449 - </div>
450 - <div className="flex flex-wrap gap-1.5 mb-4">
451 - {studio.services.map((s) => (
452 - <span key={s} className="text-[9px] uppercase tracking-widest text-nord-white/50 bg-white/5 border border-white/8 px-2 py-1">{s}</span>
453 - ))}
454 - <span className="text-[9px] uppercase tracking-widest text-nord-white/30 px-2 py-1">{studio.price}</span>
455 - </div>
456 - <div className="space-y-1.5 mb-5">
457 - <div className="flex items-center gap-2">
458 - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-nord-white/30"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" /><circle cx="12" cy="10" r="3" /></svg>
459 - <span className="text-nord-white/50 text-xs">{studio.location}</span>
460 - </div>
461 - <div className="flex items-center gap-2">
462 - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-nord-white/30"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z" /></svg>
463 - <a href={`tel:${studio.phone}`} className="text-nord-white/50 text-xs hover:text-nord-white transition-colors">{studio.phone}</a>
464 - </div>
465 - <div className="flex items-center gap-2">
466 - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-nord-white/30"><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></svg>
467 - <span className="text-nord-white/50 text-xs">{studio.hours}</span>
468 - </div>
469 - </div>
470 - <button className="w-full py-3.5 bg-nord-white text-nord-black font-bold text-[11px] uppercase tracking-widest rounded-none hover:bg-zinc-200 transition-colors active:scale-[0.98] transform">
471 - Book Now
472 - </button>
473 - </div>
474 - </article>
475 - );
476 -}
477 -
478 -function StudioTab() {
479 - const openCount = STUDIOS.filter((s) => s.openNow).length;
480 - return (
481 - <div className="min-h-screen pt-24 pb-32">
482 - <div className="px-6 pb-5 border-b border-white/6">
483 - <p className="text-[10px] uppercase tracking-widest text-nord-white/30 mb-1">Stockholm</p>
484 - <div className="flex items-baseline justify-between">
485 - <h1 className="text-2xl font-semibold text-nord-white tracking-tightest">Studios</h1>
486 - <span className="text-[10px] uppercase tracking-widest text-emerald-400/80">{openCount} open now</span>
487 - </div>
488 - </div>
489 - {STUDIOS.map((studio) => (
490 - <StudioCard key={studio.id} studio={studio} />
491 - ))}
492 - </div>
493 - );
494 -}
495 -
496 -// ─── Me Tab ───────────────────────────────────────────────────────────────────
497 -
498 -function MeTab() {
499 - const [mode, setMode] = useState<"signin" | "signup">("signin");
500 - const [email, setEmail] = useState("");
501 - const [password, setPassword] = useState("");
502 -
503 - return (
504 - <div className="min-h-screen pt-24 pb-32 px-6 max-w-md mx-auto">
505 - <div className="flex flex-col items-center py-10 space-y-3">
506 - <h2 className="text-4xl font-light tracking-brand text-nord-white">NORD</h2>
507 - <p className="text-[10px] uppercase tracking-widest text-nord-white/30">Member Access</p>
508 - </div>
509 - <div className="bg-nord-gray border border-white/8 p-6 space-y-5">
510 - <div className="flex gap-px bg-white/5">
511 - <button onClick={() => setMode("signin")} className={`flex-1 py-2.5 text-[11px] uppercase tracking-widest font-bold transition-colors ${mode === "signin" ? "bg-nord-white text-nord-black" : "text-nord-white/40 hover:text-nord-white/70"}`}>Sign In</button>
512 - <button onClick={() => setMode("signup")} className={`flex-1 py-2.5 text-[11px] uppercase tracking-widest font-bold transition-colors ${mode === "signup" ? "bg-nord-white text-nord-black" : "text-nord-white/40 hover:text-nord-white/70"}`}>Join</button>
513 - </div>
514 - <div className="space-y-3">
515 - {mode === "signup" && (
516 - <input type="text" placeholder="Full name" className="w-full bg-white/5 border border-white/10 text-nord-white placeholder:text-nord-white/25 px-4 py-3.5 text-sm focus:outline-none focus:border-white/30 transition-colors" />
517 - )}
518 - <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} className="w-full bg-white/5 border border-white/10 text-nord-white placeholder:text-nord-white/25 px-4 py-3.5 text-sm focus:outline-none focus:border-white/30 transition-colors" />
519 - <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} className="w-full bg-white/5 border border-white/10 text-nord-white placeholder:text-nord-white/25 px-4 py-3.5 text-sm focus:outline-none focus:border-white/30 transition-colors" />
520 - </div>
521 - <button disabled={!email || !password} className="w-full py-4 bg-nord-white text-nord-black font-bold text-[11px] uppercase tracking-widest rounded-none hover:bg-zinc-200 transition-colors disabled:opacity-25 disabled:cursor-not-allowed">
522 - {mode === "signin" ? "Sign In" : "Create Account"}
523 - </button>
524 - {mode === "signin" && <p className="text-center text-[10px] text-nord-white/25 hover:text-nord-white/50 cursor-pointer transition-colors">Forgot password?</p>}
525 - </div>
526 - <div className="flex items-center gap-4 my-6">
527 - <div className="flex-1 h-px bg-white/8" />
528 - <span className="text-[9px] uppercase tracking-widest text-nord-white/20">Or continue with</span>
529 - <div className="flex-1 h-px bg-white/8" />
530 - </div>
531 - <div className="space-y-3">
532 - <button className="w-full py-3.5 bg-white/5 border border-white/10 text-nord-white text-[11px] uppercase tracking-widest font-medium hover:bg-white/10 transition-colors flex items-center justify-center gap-3">
533 - <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" className="text-nord-white/70"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>
534 - Google
535 - </button>
536 - <button className="w-full py-3.5 bg-white/5 border border-white/10 text-nord-white text-[11px] uppercase tracking-widest font-medium hover:bg-white/10 transition-colors flex items-center justify-center gap-3">
537 - <svg width="16" height="16" viewBox="0 0 814 1000" fill="currentColor" className="text-nord-white/70"><path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76 0-103.7 40.8-165.9 40.8s-105-57.8-155.5-127.4C46 382.8-.3 341.5 0 300.3 0 266.9 3.2 258.2 12 258.2c11.6 0 24.5 7.7 35.5 11.9 32 13.7 66.8 36.2 100.9 70.4 59.6 61.8 102.8 130 169.7 130 72.2 0 117.2-39.5 170.4-39.5 47.2 0 86.4 28.8 155.9 39.5z"/></svg>
538 - Apple
539 - </button>
540 - </div>
541 - <div className="mt-10 flex items-center justify-center gap-4">
542 - <a href="/privacy" className="text-[9px] uppercase tracking-widest text-nord-white/20 hover:text-nord-white/50 transition-colors">Privacy</a>
543 - <span className="text-nord-white/10">·</span>
544 - <a href="/terms" className="text-[9px] uppercase tracking-widest text-nord-white/20 hover:text-nord-white/50 transition-colors">Terms</a>
545 - <span className="text-nord-white/10">·</span>
546 - <a href="/contact" className="text-[9px] uppercase tracking-widest text-nord-white/20 hover:text-nord-white/50 transition-colors">Contact</a>
547 - </div>
548 - </div>
549 - );
550 -}
551 -
552 -// ─── Page ─────────────────────────────────────────────────────────────────────
553 -
554 -export default function Home() {
555 - const [activeTab, setActiveTab] = useState<Tab>("feed");
556 -
557 - return (
558 - <div className="h-screen w-full bg-nord-black font-sans overflow-y-scroll">
559 - {/* Fixed Header */}
560 - <header className="fixed top-0 w-full z-50 px-8 pt-14 pb-6 flex justify-between items-baseline pointer-events-none">
561 - <h1 className="text-nord-white text-xl font-light tracking-brand pointer-events-auto select-none">NORD</h1>
562 - <span className="text-[10px] text-nord-white/40 tracking-widest uppercase">Stockholm</span>
563 - </header>
564 -
565 - {/* Tab content */}
566 - {activeTab === "feed" && <FeedTab onBook={() => setActiveTab("studio")} />}
567 - {activeTab === "studio" && <StudioTab />}
568 - {activeTab === "me" && <MeTab />}
569 -
570 - {/* Floating Nav */}
571 - <nav className="fixed bottom-8 left-1/2 -translate-x-1/2 w-[90%] max-w-md h-16 bg-nord-gray/80 backdrop-blur-2xl rounded-full border border-white/10 flex justify-around items-center px-8 z-50">
572 - <NavButton label="Feed" icon={<IconFeed />} active={activeTab === "feed"} onClick={() => setActiveTab("feed")} />
573 - <NavButton label="Studio" icon={<IconStudio />} active={activeTab === "studio"} onClick={() => setActiveTab("studio")} />
574 - <NavButton label="Me" icon={<IconMe />} active={activeTab === "me"} onClick={() => setActiveTab("me")} />
575 - </nav>
576 - </div>
577 - );
578 -}
app/studio/page.tsx new
+31
@@ -0,0 +1,31 @@
1 +import type { Metadata } from "next";
2 +import { STUDIOS } from "@/lib/data";
3 +import StudioCard from "@/components/studio/studio-card";
4 +
5 +export const metadata: Metadata = {
6 + title: "Studios — NORD",
7 + description: "Find and book elite hair studios across Stockholm.",
8 +};
9 +
10 +export default function StudioPage() {
11 + const openCount = STUDIOS.filter((s) => s.openNow).length;
12 +
13 + return (
14 + <div className="pt-24 pb-32 max-w-2xl mx-auto">
15 + {/* Page header */}
16 + <div className="px-6 pb-5 border-b border-white/6">
17 + <p className="text-[10px] uppercase tracking-widest text-nord-white/30 mb-1">Stockholm</p>
18 + <div className="flex items-baseline justify-between">
19 + <h1 className="text-2xl font-semibold text-nord-white tracking-tightest">Studios</h1>
20 + <span className="text-[10px] uppercase tracking-widest text-emerald-400/80">
21 + {openCount} open now
22 + </span>
23 + </div>
24 + </div>
25 +
26 + {STUDIOS.map((studio) => (
27 + <StudioCard key={studio.id} studio={studio} />
28 + ))}
29 + </div>
30 + );
31 +}
components/feed/feed-post.tsx new
+95
@@ -0,0 +1,95 @@
1 +"use client";
2 +
3 +import { useState } from "react";
4 +import Image from "next/image";
5 +import Link from "next/link";
6 +import { type Post } from "@/lib/data";
7 +import { Avatar } from "@/components/ui/avatar";
8 +import { IconHeart, IconComment, IconBookmark, IconShare } from "@/components/ui/icons";
9 +
10 +export default function FeedPost({ post }: { post: Post }) {
11 + const [liked, setLiked] = useState(false);
12 + const [saved, setSaved] = useState(false);
13 + const [likeCount, setLikeCount] = useState(post.likes);
14 +
15 + return (
16 + <article className="border-b border-white/6">
17 + {/* Header */}
18 + <div className="flex items-center justify-between px-4 py-3">
19 + <div className="flex items-center gap-3">
20 + <div className="p-[2px] rounded-full bg-gradient-to-tr from-white/60 to-white/20">
21 + <div className="p-[2px] rounded-full bg-nord-black">
22 + <Avatar letter={post.avatar} size="sm" />
23 + </div>
24 + </div>
25 + <div>
26 + <p className="text-nord-white text-sm font-semibold leading-tight">{post.handle}</p>
27 + <p className="text-nord-white/40 text-[10px] uppercase tracking-widest leading-tight mt-0.5">
28 + {post.location}
29 + </p>
30 + </div>
31 + </div>
32 + <Link
33 + href="/studio"
34 + className="text-[10px] font-bold uppercase tracking-widest text-nord-white border border-white/20 px-3 py-1.5 hover:bg-white/5 transition-colors"
35 + >
36 + Book
37 + </Link>
38 + </div>
39 +
40 + {/* Image */}
41 + <div className="relative w-full aspect-square overflow-hidden bg-nord-gray">
42 + <Image
43 + src={post.image}
44 + alt={post.style}
45 + fill
46 + className="object-cover"
47 + sizes="(max-width: 768px) 100vw, 680px"
48 + />
49 + <div className="absolute bottom-3 left-3">
50 + <span className="bg-nord-black/60 backdrop-blur-sm text-nord-white/80 text-[9px] uppercase tracking-widest px-2 py-1 border border-white/10">
51 + {post.style}
52 + </span>
53 + </div>
54 + </div>
55 +
56 + {/* Actions */}
57 + <div className="px-4 pt-3 pb-1 flex items-center justify-between">
58 + <div className="flex items-center gap-4">
59 + <button
60 + onClick={() => {
61 + setLiked((p) => !p);
62 + setLikeCount((p) => (liked ? p - 1 : p + 1));
63 + }}
64 + className={`transition-all duration-150 active:scale-90 ${liked ? "text-white" : "text-nord-white/70"}`}
65 + >
66 + <IconHeart filled={liked} />
67 + </button>
68 + <button className="text-nord-white/70 hover:text-nord-white transition-colors">
69 + <IconComment />
70 + </button>
71 + <button className="text-nord-white/70 hover:text-nord-white transition-colors">
72 + <IconShare />
73 + </button>
74 + </div>
75 + <button
76 + onClick={() => setSaved((p) => !p)}
77 + className={`transition-all duration-150 active:scale-90 ${saved ? "text-white" : "text-nord-white/70"}`}
78 + >
79 + <IconBookmark filled={saved} />
80 + </button>
81 + </div>
82 +
83 + {/* Likes + caption */}
84 + <div className="px-4 pt-1 pb-4 space-y-1">
85 + <p className="text-nord-white text-sm font-semibold">{likeCount.toLocaleString()} likes</p>
86 + <p className="text-sm text-nord-white/90 leading-relaxed">
87 + <span className="font-semibold mr-1">{post.handle}</span>
88 + {post.caption}
89 + </p>
90 + <p className="text-[11px] text-nord-white/35 tracking-wide">{post.tags.join(" ")}</p>
91 + <p className="text-[10px] text-nord-white/25 uppercase tracking-widest pt-0.5">{post.time} ago</p>
92 + </div>
93 + </article>
94 + );
95 +}
components/feed/stories-bar.tsx new
+59
@@ -0,0 +1,59 @@
1 +"use client";
2 +
3 +import { useState } from "react";
4 +import { type Story } from "@/lib/data";
5 +import { Avatar } from "@/components/ui/avatar";
6 +
7 +function StoryRing({ story, onPress }: { story: Story & { seen: boolean }; onPress: () => void }) {
8 + return (
9 + <button
10 + onClick={onPress}
11 + className="flex flex-col items-center gap-1.5 shrink-0 active:opacity-70 transition-opacity"
12 + >
13 + <div
14 + className={`p-[2px] rounded-full ${
15 + story.seen
16 + ? "bg-white/15"
17 + : "bg-gradient-to-tr from-white/80 via-white/40 to-white/80"
18 + }`}
19 + >
20 + <div className="p-[2px] rounded-full bg-nord-black">
21 + <Avatar letter={story.avatar} size="lg" />
22 + </div>
23 + </div>
24 + <span className="text-[10px] text-nord-white/60 tracking-tight max-w-[52px] truncate">
25 + {story.handle}
26 + </span>
27 + </button>
28 + );
29 +}
30 +
31 +export default function StoriesBar({ stories }: { stories: Story[] }) {
32 + const [seenIds, setSeenIds] = useState<Set<number>>(new Set());
33 +
34 + return (
35 + <div
36 + className="overflow-x-auto flex gap-4 px-4 py-4 border-b border-white/6"
37 + style={{ scrollbarWidth: "none" }}
38 + >
39 + {/* Your story slot */}
40 + <button className="flex flex-col items-center gap-1.5 shrink-0 active:opacity-70 transition-opacity">
41 + <div className="w-14 h-14 rounded-full border-2 border-dashed border-white/20 flex items-center justify-center">
42 + <svg width="20" height="20" viewBox="0 0 24 24" fill="none" className="text-nord-white/40">
43 + <line x1="12" y1="5" x2="12" y2="19" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
44 + <line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
45 + </svg>
46 + </div>
47 + <span className="text-[10px] text-nord-white/40 tracking-tight">your story</span>
48 + </button>
49 +
50 + {stories.map((story) => (
51 + <StoryRing
52 + key={story.id}
53 + story={{ ...story, seen: seenIds.has(story.id) || story.seen }}
54 + onPress={() => setSeenIds((prev) => new Set(prev).add(story.id))}
55 + />
56 + ))}
57 + </div>
58 + );
59 +}
components/me/me-form.tsx new
+98
@@ -0,0 +1,98 @@
1 +"use client";
2 +
3 +import { useState } from "react";
4 +
5 +export default function MeForm() {
6 + const [mode, setMode] = useState<"signin" | "signup">("signin");
7 + const [email, setEmail] = useState("");
8 + const [password, setPassword] = useState("");
9 +
10 + return (
11 + <>
12 + {/* Auth card */}
13 + <div className="bg-nord-gray border border-white/8 p-6 space-y-5">
14 + <div className="flex gap-px bg-white/5">
15 + <button
16 + onClick={() => setMode("signin")}
17 + className={`flex-1 py-2.5 text-[11px] uppercase tracking-widest font-bold transition-colors ${
18 + mode === "signin" ? "bg-nord-white text-nord-black" : "text-nord-white/40 hover:text-nord-white/70"
19 + }`}
20 + >
21 + Sign In
22 + </button>
23 + <button
24 + onClick={() => setMode("signup")}
25 + className={`flex-1 py-2.5 text-[11px] uppercase tracking-widest font-bold transition-colors ${
26 + mode === "signup" ? "bg-nord-white text-nord-black" : "text-nord-white/40 hover:text-nord-white/70"
27 + }`}
28 + >
29 + Join
30 + </button>
31 + </div>
32 +
33 + <div className="space-y-3">
34 + {mode === "signup" && (
35 + <input
36 + type="text"
37 + placeholder="Full name"
38 + className="w-full bg-white/5 border border-white/10 text-nord-white placeholder:text-nord-white/25 px-4 py-3.5 text-sm focus:outline-none focus:border-white/30 transition-colors"
39 + />
40 + )}
41 + <input
42 + type="email"
43 + placeholder="Email"
44 + value={email}
45 + onChange={(e) => setEmail(e.target.value)}
46 + className="w-full bg-white/5 border border-white/10 text-nord-white placeholder:text-nord-white/25 px-4 py-3.5 text-sm focus:outline-none focus:border-white/30 transition-colors"
47 + />
48 + <input
49 + type="password"
50 + placeholder="Password"
51 + value={password}
52 + onChange={(e) => setPassword(e.target.value)}
53 + className="w-full bg-white/5 border border-white/10 text-nord-white placeholder:text-nord-white/25 px-4 py-3.5 text-sm focus:outline-none focus:border-white/30 transition-colors"
54 + />
55 + </div>
56 +
57 + <button
58 + disabled={!email || !password}
59 + className="w-full py-4 bg-nord-white text-nord-black font-bold text-[11px] uppercase tracking-widest rounded-none hover:bg-zinc-200 transition-colors disabled:opacity-25 disabled:cursor-not-allowed"
60 + >
61 + {mode === "signin" ? "Sign In" : "Create Account"}
62 + </button>
63 +
64 + {mode === "signin" && (
65 + <p className="text-center text-[10px] text-nord-white/25 hover:text-nord-white/50 cursor-pointer transition-colors">
66 + Forgot password?
67 + </p>
68 + )}
69 + </div>
70 +
71 + {/* Divider */}
72 + <div className="flex items-center gap-4 my-6">
73 + <div className="flex-1 h-px bg-white/8" />
74 + <span className="text-[9px] uppercase tracking-widest text-nord-white/20">Or continue with</span>
75 + <div className="flex-1 h-px bg-white/8" />
76 + </div>
77 +
78 + {/* OAuth */}
79 + <div className="space-y-3">
80 + <button className="w-full py-3.5 bg-white/5 border border-white/10 text-nord-white text-[11px] uppercase tracking-widest font-medium hover:bg-white/10 transition-colors flex items-center justify-center gap-3">
81 + <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" className="text-nord-white/70">
82 + <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
83 + <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
84 + <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
85 + <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
86 + </svg>
87 + Google
88 + </button>
89 + <button className="w-full py-3.5 bg-white/5 border border-white/10 text-nord-white text-[11px] uppercase tracking-widest font-medium hover:bg-white/10 transition-colors flex items-center justify-center gap-3">
90 + <svg width="16" height="16" viewBox="0 0 814 1000" fill="currentColor" className="text-nord-white/70">
91 + <path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76 0-103.7 40.8-165.9 40.8s-105-57.8-155.5-127.4C46 382.8-.3 341.5 0 300.3 0 266.9 3.2 258.2 12 258.2c11.6 0 24.5 7.7 35.5 11.9 32 13.7 66.8 36.2 100.9 70.4 59.6 61.8 102.8 130 169.7 130 72.2 0 117.2-39.5 170.4-39.5 47.2 0 86.4 28.8 155.9 39.5z"/>
92 + </svg>
93 + Apple
94 + </button>
95 + </div>
96 + </>
97 + );
98 +}
components/nord-header.tsx new
+12
@@ -0,0 +1,12 @@
1 +export default function NordHeader() {
2 + return (
3 + <header className="fixed top-0 w-full z-50 px-8 pt-14 pb-6 flex justify-between items-baseline pointer-events-none">
4 + <h1 className="text-nord-white text-xl font-light tracking-brand pointer-events-auto select-none">
5 + NORD
6 + </h1>
7 + <span className="text-[10px] text-nord-white/40 tracking-widest uppercase">
8 + Stockholm
9 + </span>
10 + </header>
11 + );
12 +}
components/nord-nav.tsx new
+48
@@ -0,0 +1,48 @@
1 +"use client";
2 +
3 +import Link from "next/link";
4 +import { usePathname } from "next/navigation";
5 +import { IconFeed, IconStudio, IconMe } from "@/components/ui/icons";
6 +
7 +function NavLink({
8 + href,
9 + label,
10 + icon,
11 + active,
12 +}: {
13 + href: string;
14 + label: string;
15 + icon: React.ReactNode;
16 + active: boolean;
17 +}) {
18 + return (
19 + <Link
20 + href={href}
21 + className={`flex flex-col items-center gap-1 transition-all duration-200 ${
22 + active ? "opacity-100" : "opacity-35 hover:opacity-70"
23 + }`}
24 + >
25 + <div className={`transition-transform duration-200 ${active ? "scale-110" : ""}`}>
26 + {icon}
27 + </div>
28 + <span className="text-[9px] uppercase tracking-widest text-nord-white">{label}</span>
29 + <div
30 + className={`w-1 h-1 rounded-full bg-nord-white transition-opacity duration-200 ${
31 + active ? "opacity-100" : "opacity-0"
32 + }`}
33 + />
34 + </Link>
35 + );
36 +}
37 +
38 +export default function NordNav() {
39 + const pathname = usePathname();
40 +
41 + return (
42 + <nav className="fixed bottom-8 left-1/2 -translate-x-1/2 w-[90%] max-w-md h-16 bg-nord-gray/80 backdrop-blur-2xl rounded-full border border-white/10 flex justify-around items-center px-8 z-50">
43 + <NavLink href="/" label="Feed" icon={<IconFeed />} active={pathname === "/"} />
44 + <NavLink href="/studio" label="Studio" icon={<IconStudio />} active={pathname === "/studio"} />
45 + <NavLink href="/me" label="Me" icon={<IconMe />} active={pathname === "/me"} />
46 + </nav>
47 + );
48 +}
components/studio/studio-card.tsx new
+84
@@ -0,0 +1,84 @@
1 +import Image from "next/image";
2 +import { type Studio } from "@/lib/data";
3 +import { IconStar, IconPin, IconPhone, IconClock } from "@/components/ui/icons";
4 +
5 +export default function StudioCard({ studio }: { studio: Studio }) {
6 + return (
7 + <article className="border-b border-white/6 last:border-0">
8 + {/* Image */}
9 + <div className="relative w-full h-52 overflow-hidden bg-nord-gray">
10 + <Image
11 + src={studio.image}
12 + alt={studio.name}
13 + fill
14 + className="object-cover brightness-75 hover:brightness-90 transition-all duration-500"
15 + sizes="(max-width: 768px) 100vw, 680px"
16 + />
17 + <div className="absolute inset-0 bg-gradient-to-t from-nord-black/80 via-transparent to-transparent" />
18 + <div className="absolute top-4 right-4">
19 + <span
20 + className={`text-[9px] uppercase tracking-widest font-medium px-2.5 py-1 border ${
21 + studio.openNow
22 + ? "border-emerald-500/40 text-emerald-400 bg-emerald-950/60"
23 + : "border-red-500/30 text-red-400/80 bg-red-950/60"
24 + }`}
25 + >
26 + {studio.openNow ? "Open" : "Closed"}
27 + </span>
28 + </div>
29 + <div className="absolute bottom-4 left-4">
30 + <span className="text-[9px] uppercase tracking-widest text-nord-white/50">
31 + {studio.neighbourhood}
32 + </span>
33 + </div>
34 + </div>
35 +
36 + {/* Content */}
37 + <div className="px-6 py-5">
38 + <div className="flex items-start justify-between mb-3">
39 + <h3 className="text-lg font-semibold text-nord-white tracking-tightest">{studio.name}</h3>
40 + <div className="flex items-center gap-1.5 mt-0.5">
41 + <IconStar />
42 + <span className="text-sm font-semibold text-nord-white">{studio.rating}</span>
43 + <span className="text-nord-white/30 text-xs">({studio.reviews})</span>
44 + </div>
45 + </div>
46 +
47 + <div className="flex flex-wrap gap-1.5 mb-4">
48 + {studio.services.map((s) => (
49 + <span
50 + key={s}
51 + className="text-[9px] uppercase tracking-widest text-nord-white/50 bg-white/5 border border-white/8 px-2 py-1"
52 + >
53 + {s}
54 + </span>
55 + ))}
56 + <span className="text-[9px] uppercase tracking-widest text-nord-white/30 px-2 py-1">
57 + {studio.price}
58 + </span>
59 + </div>
60 +
61 + <div className="space-y-1.5 mb-5">
62 + <div className="flex items-center gap-2">
63 + <IconPin />
64 + <span className="text-nord-white/50 text-xs">{studio.location}</span>
65 + </div>
66 + <div className="flex items-center gap-2">
67 + <IconPhone />
68 + <a href={`tel:${studio.phone}`} className="text-nord-white/50 text-xs hover:text-nord-white transition-colors">
69 + {studio.phone}
70 + </a>
71 + </div>
72 + <div className="flex items-center gap-2">
73 + <IconClock />
74 + <span className="text-nord-white/50 text-xs">{studio.hours}</span>
75 + </div>
76 + </div>
77 +
78 + <button className="w-full py-3.5 bg-nord-white text-nord-black font-bold text-[11px] uppercase tracking-widest rounded-none hover:bg-zinc-200 transition-colors active:scale-[0.98] transform">
79 + Book Now
80 + </button>
81 + </div>
82 + </article>
83 + );
84 +}
components/ui/avatar.tsx new
+8
@@ -0,0 +1,8 @@
1 +export function Avatar({ letter, size = "md" }: { letter: string; size?: "sm" | "md" | "lg" }) {
2 + const sizes = { sm: "w-8 h-8 text-xs", md: "w-9 h-9 text-sm", lg: "w-14 h-14 text-base" };
3 + return (
4 + <div className={`${sizes[size]} rounded-full bg-gradient-to-br from-zinc-600 to-zinc-800 border border-white/10 flex items-center justify-center font-semibold text-nord-white shrink-0`}>
5 + {letter}
6 + </div>
7 + );
8 +}
components/ui/icons.tsx new
+82
@@ -0,0 +1,82 @@
1 +export function IconFeed({ size = 18 }: { size?: number }) {
2 + return (
3 + <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
4 + <rect x="3" y="3" width="7" height="7" /><rect x="14" y="3" width="7" height="7" />
5 + <rect x="3" y="14" width="7" height="7" /><rect x="14" y="14" width="7" height="7" />
6 + </svg>
7 + );
8 +}
9 +export function IconStudio({ size = 18 }: { size?: number }) {
10 + return (
11 + <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
12 + <path d="M12 2L2 7l10 5 10-5-10-5z" /><path d="M2 17l10 5 10-5" /><path d="M2 12l10 5 10-5" />
13 + </svg>
14 + );
15 +}
16 +export function IconMe({ size = 18 }: { size?: number }) {
17 + return (
18 + <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round">
19 + <circle cx="12" cy="8" r="4" /><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" />
20 + </svg>
21 + );
22 +}
23 +export function IconHeart({ filled }: { filled: boolean }) {
24 + return (
25 + <svg width="24" height="24" viewBox="0 0 24 24" fill={filled ? "#fff" : "none"}>
26 + <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l8.78-8.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
27 + stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
28 + </svg>
29 + );
30 +}
31 +export function IconComment() {
32 + return (
33 + <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
34 + <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
35 + stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
36 + </svg>
37 + );
38 +}
39 +export function IconBookmark({ filled }: { filled: boolean }) {
40 + return (
41 + <svg width="24" height="24" viewBox="0 0 24 24" fill={filled ? "#fff" : "none"}>
42 + <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"
43 + stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
44 + </svg>
45 + );
46 +}
47 +export function IconShare() {
48 + return (
49 + <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
50 + <line x1="22" y1="2" x2="11" y2="13" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
51 + <polygon points="22 2 15 22 11 13 2 9 22 2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
52 + </svg>
53 + );
54 +}
55 +export function IconStar() {
56 + return (
57 + <svg width="12" height="12" viewBox="0 0 24 24" className="fill-yellow-400">
58 + <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
59 + </svg>
60 + );
61 +}
62 +export function IconPin() {
63 + return (
64 + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-nord-white/30 shrink-0">
65 + <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" /><circle cx="12" cy="10" r="3" />
66 + </svg>
67 + );
68 +}
69 +export function IconPhone() {
70 + return (
71 + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-nord-white/30 shrink-0">
72 + <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z" />
73 + </svg>
74 + );
75 +}
76 +export function IconClock() {
77 + return (
78 + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-nord-white/30 shrink-0">
79 + <circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" />
80 + </svg>
81 + );
82 +}
lib/data.ts new
+214
@@ -0,0 +1,214 @@
1 +// ─── Shared data for NORD feed, studios, and stories ─────────────────────────
2 +
3 +export interface Story {
4 + id: number;
5 + handle: string;
6 + avatar: string;
7 + seen: boolean;
8 +}
9 +
10 +export interface Post {
11 + id: number;
12 + stylist: string;
13 + handle: string;
14 + location: string;
15 + avatar: string;
16 + image: string;
17 + likes: number;
18 + caption: string;
19 + tags: string[];
20 + time: string;
21 + style: string;
22 +}
23 +
24 +export interface Studio {
25 + id: number;
26 + name: string;
27 + location: string;
28 + neighbourhood: string;
29 + rating: number;
30 + reviews: number;
31 + price: string;
32 + openNow: boolean;
33 + hours: string;
34 + phone: string;
35 + services: string[];
36 + image: string;
37 +}
38 +
39 +export const STORIES: Story[] = [
40 + { id: 1, handle: "anders", avatar: "A", seen: false },
41 + { id: 2, handle: "maja", avatar: "M", seen: false },
42 + { id: 3, handle: "erik", avatar: "E", seen: true },
43 + { id: 4, handle: "linn", avatar: "L", seen: false },
44 + { id: 5, handle: "bjorn", avatar: "B", seen: true },
45 + { id: 6, handle: "saga", avatar: "S", seen: false },
46 + { id: 7, handle: "oskar", avatar: "O", seen: true },
47 +];
48 +
49 +export const POSTS: Post[] = [
50 + {
51 + id: 1,
52 + stylist: "Anders Lindqvist",
53 + handle: "@anderscuts",
54 + location: "Östermalm, Stockholm",
55 + avatar: "A",
56 + image: "https://images.unsplash.com/photo-1522337360788-8b13dee7a37e?q=80&w=800&auto=format&fit=crop",
57 + likes: 1204,
58 + caption: "Golden hour, golden strands. Precision silk blowout on Maja — three hours of work that disappears into the light.",
59 + tags: ["#NORDstyle", "#BlondeScandi", "#SilkBlowout"],
60 + time: "34m",
61 + style: "Silk Blowout",
62 + },
63 + {
64 + id: 2,
65 + stylist: "Erik Bergström",
66 + handle: "@erikb.studio",
67 + location: "Södermalm, Stockholm",
68 + avatar: "E",
69 + image: "https://images.unsplash.com/photo-1580618672591-eb180b1a973f?q=80&w=800&auto=format&fit=crop",
70 + likes: 847,
71 + caption: "The Sharp Edge. Every millimetre deliberate. This is what precision fade looks like when you stop rushing.",
72 + tags: ["#NORDmen", "#PrecisionFade", "#ScandiFade"],
73 + time: "2h",
74 + style: "Precision Fade",
75 + },
76 + {
77 + id: 3,
78 + stylist: "Maja Söderström",
79 + handle: "@maja.nord",
80 + location: "Vasastan, Stockholm",
81 + avatar: "M",
82 + image: "https://images.unsplash.com/photo-1559599101-f09722fb4948?q=80&w=800&auto=format&fit=crop",
83 + likes: 2341,
84 + caption: "Ash and ice. When the colour does the talking, the cut just has to be perfect. Nothing more. Nothing less.",
85 + tags: ["#AshBlonde", "#IceHair", "#NORDcolour"],
86 + time: "5h",
87 + style: "Ash Blonde",
88 + },
89 + {
90 + id: 4,
91 + stylist: "Johan Åkerlund",
92 + handle: "@johancuts",
93 + location: "Kungsholmen, Stockholm",
94 + avatar: "J",
95 + image: "https://images.unsplash.com/photo-1503951914875-452162b0f3f1?q=80&w=800&auto=format&fit=crop",
96 + likes: 619,
97 + caption: "Texture over length. A modern men's cut built around movement — not product. The hair should do the work.",
98 + tags: ["#TextureCut", "#NORDmen", "#ModernBarber"],
99 + time: "8h",
100 + style: "Texture Cut",
101 + },
102 + {
103 + id: 5,
104 + stylist: "Linn Ekström",
105 + handle: "@linnekstrom",
106 + location: "Lidingö, Stockholm",
107 + avatar: "L",
108 + image: "https://images.unsplash.com/photo-1534528741775-53994a69daeb?q=80&w=800&auto=format&fit=crop",
109 + likes: 3108,
110 + caption: "Nordic natural. No filter needed when the light in Stockholm is this good and the hair is this healthy.",
111 + tags: ["#NordicNatural", "#HealthyHair", "#NORDglow"],
112 + time: "12h",
113 + style: "Nordic Natural",
114 + },
115 + {
116 + id: 6,
117 + stylist: "Björn Hallgren",
118 + handle: "@bjornhall",
119 + location: "Stureplan, Stockholm",
120 + avatar: "B",
121 + image: "https://images.unsplash.com/photo-1621605815971-fbc98d665033?q=80&w=800&auto=format&fit=crop",
122 + likes: 988,
123 + caption: "The Obsidian Line. Clean architecture for the modern Stockholmer. Zero tolerance for the almost-right.",
124 + tags: ["#ObsidianLine", "#CleanCut", "#NORDmen"],
125 + time: "1d",
126 + style: "Obsidian Line",
127 + },
128 + {
129 + id: 7,
130 + stylist: "Saga Lindgren",
131 + handle: "@saga.cuts",
132 + location: "Djurgården, Stockholm",
133 + avatar: "S",
134 + image: "https://images.unsplash.com/photo-1517841905240-472988babdf9?q=80&w=800&auto=format&fit=crop",
135 + likes: 4472,
136 + caption: "Effortless. That is the hardest thing to achieve. Six years of craft distilled into one afternoon appointment.",
137 + tags: ["#Effortless", "#NORDwoman", "#ScandinavianBeauty"],
138 + time: "1d",
139 + style: "Effortless",
140 + },
141 +];
142 +
143 +export const STUDIOS: Studio[] = [
144 + {
145 + id: 1,
146 + name: "NORD",
147 + location: "Hågerstensvägen 15, Stockholm",
148 + neighbourhood: "Hägersten",
149 + rating: 4.8,
150 + reviews: 124,
151 + price: "$$",
152 + openNow: true,
153 + hours: "09:00 – 19:00",
154 + phone: "+46 72-853 82 88",
155 + services: ["Precision Cut", "Fade", "Beard Trim"],
156 + image: "https://images.unsplash.com/photo-1521590832167-7bcbfaa6381f?q=80&w=800&auto=format&fit=crop",
157 + },
158 + {
159 + id: 2,
160 + name: "Barber Nord",
161 + location: "Hägerstens Torg 8, Stockholm",
162 + neighbourhood: "Södermalm",
163 + rating: 4.6,
164 + reviews: 89,
165 + price: "$$",
166 + openNow: true,
167 + hours: "10:00 – 18:00",
168 + phone: "+46 8 234 5678",
169 + services: ["Barbershop", "Beard Trimming"],
170 + image: "https://images.unsplash.com/photo-1585747860715-2ba37e788b70?q=80&w=800&auto=format&fit=crop",
171 + },
172 + {
173 + id: 3,
174 + name: "Classic Cut Stockholm",
175 + location: "Liljeholmsvägen 22, Stockholm",
176 + neighbourhood: "Liljeholmen",
177 + rating: 4.7,
178 + reviews: 156,
179 + price: "$$$",
180 + openNow: true,
181 + hours: "09:00 – 19:00",
182 + phone: "+46 8 345 6789",
183 + services: ["Hair Salon", "Grooming", "Styling"],
184 + image: "https://images.unsplash.com/photo-1560066984-138dadb4c035?q=80&w=800&auto=format&fit=crop",
185 + },
186 + {
187 + id: 4,
188 + name: "Style Master",
189 + location: "Granbergsvägen 33, Stockholm",
190 + neighbourhood: "Kungsholmen",
191 + rating: 4.9,
192 + reviews: 203,
193 + price: "$$$",
194 + openNow: false,
195 + hours: "Opens 10:00",
196 + phone: "+46 8 567 8901",
197 + services: ["Hair Salon", "Colour", "Styling"],
198 + image: "https://images.unsplash.com/photo-1562322140-8baeececf3df?q=80&w=800&auto=format&fit=crop",
199 + },
200 + {
201 + id: 5,
202 + name: "The Classic Barber",
203 + location: "Telefonvägen 12, Stockholm",
204 + neighbourhood: "Vasastan",
205 + rating: 4.8,
206 + reviews: 145,
207 + price: "$$",
208 + openNow: true,
209 + hours: "09:00 – 18:00",
210 + phone: "+46 8 789 0123",
211 + services: ["Traditional Shave", "Barbershop"],
212 + image: "https://images.unsplash.com/photo-1512690459411-b9245aed614b?q=80&w=800&auto=format&fit=crop",
213 + },
214 +];
public/file.svg deleted
-1
@@ -1 +0,0 @@
1 -<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
\ No newline at end of file
public/globe.svg deleted
-1
@@ -1 +0,0 @@
1 -<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
\ No newline at end of file
public/next.svg deleted
-1
@@ -1 +0,0 @@
1 -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
\ No newline at end of file
public/window.svg deleted
-1
@@ -1 +0,0 @@
1 -<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
\ No newline at end of file