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