PageSourceSearch

https://inkartists.com/blog

html inkartists.com collected 2026-10-02 00:50:48 UTC 94,719 bytes, 1,788 lines download raw bytes

1<!doctype html>
2<html lang="en" class="scroll-smooth">
3
4<head>
5    <meta charset="utf-8">
6    <meta name="viewport" content="width=device-width, initial-scale=1">
7        <title>InkArtists.com - Updates, notes, and studio moments</title>
8    <meta name="description" content="Discover tattoo artists, studios, and events worldwide on InkArtists.">
9    <meta name="robots" content="index,follow">
10    <meta property="og:type" content="website">
11    <meta property="og:site_name" content="InkArtists">
12    <meta property="og:title" content="InkArtists.com - Updates, notes, and studio moments">
13    <meta property="og:description" content="Discover tattoo artists, studios, and events worldwide on InkArtists.">
14    <meta property="og:url" content="https://www.inkartists.com/blog">
15    <meta name="twitter:card" content="summary_large_image">
16    <meta name="twitter:title" content="InkArtists.com - Updates, notes, and studio moments">
17    <meta name="twitter:description" content="Discover tattoo artists, studios, and events worldwide on InkArtists.">
18            <link rel="alternate" hreflang="en"
19            href="https://www.inkartists.com/blog">
20            <link rel="alternate" hreflang="es"
21            href="https://www.inkartists.com/es/blog">
22            <link rel="alternate" hreflang="fr"
23            href="https://www.inkartists.com/fr/blog">
24            <link rel="alternate" hreflang="pt"
25            href="https://www.inkartists.com/pt/blog">
26            <link rel="alternate" hreflang="ja"
27            href="https://www.inkartists.com/ja/blog">
28            <link rel="alternate" hreflang="ko"
29            href="https://www.inkartists.com/ko/blog">
30            <link rel="alternate" hreflang="de"
31            href="https://www.inkartists.com/de/blog">
32            <link rel="alternate" hreflang="it"
33            href="https://www.inkartists.com/it/blog">
34            <link rel="alternate" hreflang="nl"
35            href="https://www.inkartists.com/nl/blog">
36            <link rel="alternate" hreflang="ru"
37            href="https://www.inkartists.com/ru/blog">
38            <link rel="alternate" hreflang="tr"
39            href="https://www.inkartists.com/tr/blog">
40            <link rel="alternate" hreflang="pl"
41            href="https://www.inkartists.com/pl/blog">
42        <link rel="alternate" hreflang="x-default"
43        href="https://www.inkartists.com/blog">
44        
45    
45<script>
46        (function () {
47            const forceDark = false;
48            const stored = window.localStorage ? localStorage.getItem('ink-theme') : null;
49            const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
50            const theme = forceDark ? 'dark' : (stored || (prefersDark ? 'dark' : 'light'));
51            document.documentElement.setAttribute('data-theme', theme);
52        })();
53    </script>
53
54
55    
56    
56<script>
57        (function () {
58            try {
59                var secure = location.protocol === 'https:' ? '; Secure' : '';
60                var year = 60 * 60 * 24 * 365;
61                var set = function (name, value, maxAge) {
62                    document.cookie = name + '=' + encodeURIComponent(value)
63                        + '; path=/; max-age=' + maxAge + '; SameSite=Lax' + secure;
64                };
65                var read = function (name) {
66                    var match = document.cookie.match('(?:^|; )' + name + '=([^;]*)');
67                    return match ? decodeURIComponent(match[1]) : null;
68                };
69
70                var deviceId = read('ia_did');
71                if (!deviceId || !/^[A-Za-z0-9_-]{16,64}$/.test(deviceId)) {
72                    var bytes = new Uint8Array(16);
73                    (window.crypto || window.msCrypto).getRandomValues(bytes);
74                    deviceId = Array.prototype.map.call(bytes, function (b) {
75                        return ('0' + b.toString(16)).slice(-2);
76                    }).join('');
77                }
78                // Re-set every load so the year rolls forward on active users.
79                set('ia_did', deviceId, year);
80
81                var zone = null;
82                if (window.Intl && Intl.DateTimeFormat) {
83                    zone = Intl.DateTimeFormat().resolvedOptions().timeZone || null;
84                }
85                if (zone) {
86                    set('ia_tz', zone, year);
87                }
88                // getTimezoneOffset() counts minutes *behind* UTC; the backend
89                // stores minutes east of it.
90                set('ia_tzo', String(-new Date().getTimezoneOffset()), year);
91            } catch (e) {
92                // A browser with cookies or crypto locked down still gets the
93                // site; it just arrives without a device fingerprint.
94            }
95        })();
96    </script>
96
97
98    
99    <link rel="preload" as="style" href="https://inkartists.com/build/assets/app-gynZxSks.css" /><link rel="preload" as="style" href="https://inkartists.com/build/assets/app-jWmyelEn.css" /><link rel="modulepreload" as="script" href="https://inkartists.com/build/assets/app-D90LKBrw.js" /><link rel="stylesheet" href="https://inkartists.com/build/assets/app-gynZxSks.css" /><link rel="stylesheet" href="https://inkartists.com/build/assets/app-jWmyelEn.css" />
99<script type="module" src="https://inkartists.com/build/assets/app-D90LKBrw.js"></script>
99    
99<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
99
100
101    <meta name="csrf-token" content="5mNMtSeAK8ErMh3KLEwAOEKBdSzlYZoUWli8a9y1">
102
103    <!-- Favicon + cool stuff -->
104    <link rel="icon" type="image/png" href="https://inkartists.com/storage/images/favicon/favicon-96x96.png" sizes="96x96" />
105    <link rel="icon" type="image/svg+xml" href="https://inkartists.com/storage/images/favicon/favicon.svg" />
106    <link rel="shortcut icon" href="https://inkartists.com/storage/images/favicon/favicon.ico" />
107    <link rel="apple-touch-icon" sizes="180x180" href="https://inkartists.com/storage/images/favicon/apple-touch-icon.png" />
108    <meta name="apple-mobile-web-app-title" content="InkArtists" />
109    <link rel="manifest" href="https://inkartists.com/storage/images/favicon/site.webmanifest" />
110
111    <!-- Google tag (gtag.js) -->
vendor: 68 bytes, lines 111-112
111
112    <script async src="https://www.googletagmanager.com/gtag/js?id=
112G-H8P3EF5QTG
vendor: 16 bytes, lines 112-113
112"></script>
113    
113<script>
114        
vendor: 154 bytes, lines 114-117
114window.dataLayer = window.dataLayer || [];
115        function gtag() { dataLayer.push(arguments); }
116        gtag('js', new Date());
117        gtag('config', '
117G-H8P3EF5QTG
vendor: 8 bytes, lines 117-118
117');
118    
118</script>
118
119    <style>
120        html[data-theme="light"] .home-light-bg {
121            background-image: url('https://inkartists.com/storage/images/background/light_bg.jpg');
122            background-size: cover;
123            background-position: center;
124            background-attachment: fixed;
125            background-color: rgba(248, 250, 252, 0.78);
126            background-blend-mode: lighten;
127        }
128
129        html[data-theme="light"] .home-hero {
130            background-color: #f8fafc;
131            background-image: url('https://inkartists.com/storage/images/background/light_bg.jpg');
132            background-size: cover;
133            background-position: center;
134        }
135
136        html[data-theme="light"] .home-hero .home-hero-media {
137            background-image: url('https://inkartists.com/storage/images/background/light_bg.jpg');
138            background-size: cover;
139            background-position: center;
140        }
141
142        html[data-theme="light"] .home-hero .home-hero-media video {
143            opacity: 0;
144        }
145
146        html[data-theme="light"] .home-hero .home-hero-media video,
147        html[data-theme="light"] .home-hero .home-hero-media img {
148            filter: saturate(0.9) brightness(1.08);
149            opacity: 0;
150        }
151
152        html[data-theme="light"] .home-hero .home-hero-overlay {
153            background: linear-gradient(to bottom, rgba(248, 250, 252, 0.35), rgba(248, 250, 252, 0.2), rgba(248, 250, 252, 0.12)) !important;
154        }
155
156        html[data-theme="light"] .home-hero .home-hero-noise {
157            opacity: 0.08 !important;
158            mix-blend-mode: normal !important;
159        }
160
161        html[data-theme="light"] .home-hero .home-hero-studio-overlay {
162            background-color: rgba(248, 250, 252, 0.85) !important;
163        }
164
165        html[data-theme="light"] .home-hero .home-hero-title-gradient {
166            background-image: linear-gradient(90deg, #0f172a, #155e75, #1d4ed8) !important;
167            color: transparent !important;
168            -webkit-text-fill-color: transparent !important;
169        }
170
171        html[data-theme="light"] .home-hero .text-white,
172        html[data-theme="light"] .home-hero .text-white\/90,
173        html[data-theme="light"] .home-hero .text-white\/80,
174        html[data-theme="light"] .home-hero .text-white\/75,
175        html[data-theme="light"] .home-hero .text-white\/70,
176        html[data-theme="light"] .home-hero .text-white\/60,
177        html[data-theme="light"] .home-hero .text-gray-300,
178        html[data-theme="light"] .home-hero .text-gray-200,
179        html[data-theme="light"] .home-hero .text-emerald-200,
180        html[data-theme="light"] .home-hero .text-cyan-300,
181        html[data-theme="light"] .home-hero .text-purple-300 {
182            color: #0f172a !important;
183        }
184
185        html[data-theme="light"] .home-hero .home-cta-button {
186            color: #ffffff !important;
187        }
188
189        html[data-theme="light"] .home-hero .bg-white\/10 {
190            background-color: rgba(15, 23, 42, 0.06) !important;
191        }
192
193        html[data-theme="light"] .home-hero .bg-white\/20 {
194            background-color: rgba(15, 23, 42, 0.1) !important;
195        }
196
197        html[data-theme="light"] .home-hero .border-white\/20,
198        html[data-theme="light"] .home-hero .border-white\/30,
199        html[data-theme="light"] .home-hero .border-white\/60 {
200            border-color: rgba(15, 23, 42, 0.16) !important;
201        }
202
203        html[data-theme="light"] .featured-cta-primary {
204            color: #ffffff !important;
205            background-color: #0f172a !important;
206        }
207
208        html[data-theme="light"] .featured-cta-primary:hover {
209            background-color: #111827 !important;
210        }
211    </style>
212</head>
213
214    <body class="min-h-screen bg-gray-50 text-gray-900 flex flex-col ">
215    
216                    <header x-data="{
217    scrolled: false,
218    userMenu: false,
219    guestMenu: false,
220    scrollToWaitlist() {
221        const target = document.getElementById('waitlist');
222        const email = document.getElementById('waitlist-email');
223        if (target) {
224            target.scrollIntoView({ behavior: 'smooth' });
225        }
226        if (email) {
227            setTimeout(() => email.focus({ preventScroll: true }), 400);
228        }
229    },
230    openLogin() {
231        window.__inkAccountType = '';
232        window.dispatchEvent(new CustomEvent('ink-login-open', {
233            detail: { accountType: '' }
234        }));
235    }
236}" x-init="scrolled = window.scrollY > 50"
237    @scroll.window="scrolled = window.scrollY > 50"
238    class="bg-gradient-to-br border-b/10"
239    :class="scrolled ? 'bg-white/80 border-white/10' : ''">
240
241    <div class="max-w-7xl mx-auto px-6 py-5 flex items-center justify-between">
242
243        <div class="flex items-center gap-4">
244            <!-- Logo -->
245            <a href="https://inkartists.com" class="relative flex items-center gap-3 group">
246                <img src="https://inkartists.com/storage/images/logo/logo_long_black.svg"
247                    alt="Ink Artists"
248                    class="h-10 w-auto object-contain transition group-hover:scale-105 ">
249            </a>
250
251                            <a href="https://inkartists.com/artists/search"
252                    class="hidden lg:flex items-center gap-3 rounded-full border border-white/60 bg-white/80 px-5 py-2 text-sm font-semibold uppercase tracking-[0.3em] text-gray-900 transition hover:-translate-y-0.5 hover:bg-white">
253                    Tattoo Artists
254                </a>
255                <a href="https://inkartists.com/tattoos"
256                    class="hidden lg:flex items-center gap-3 rounded-full border border-white/60 bg-white/80 px-5 py-2 text-sm font-semibold uppercase tracking-[0.3em] text-gray-900 transition hover:-translate-y-0.5 hover:bg-white">
257                    Tattoos
258                </a>
259                                    <a href="https://inkartists.com/jobs"
260                        class="hidden lg:flex items-center gap-3 rounded-full border border-white/60 bg-white/80 px-5 py-2 text-sm font-semibold uppercase tracking-[0.3em] text-gray-900 transition hover:-translate-y-0.5 hover:bg-white">
261                        Jobs
262                    </a>
263                                <a href="https://inkartists.com/tattoo-events"
264                    class="hidden lg:flex items-center gap-3 rounded-full border border-white/60 bg-white/80 px-5 py-2 text-sm font-semibold uppercase tracking-[0.3em] text-gray-900 transition hover:-translate-y-0.5 hover:bg-white">
265                    Tattoo Events
266                </a>
267                    </div>
268
269                    <!-- CTA Button (glows harder on scroll) -->
270            <div class="flex items-center gap-3 relative" @click.outside="guestMenu = false">
271                <button type="button" @click="guestMenu = !guestMenu"
272                    class="lg:hidden inline-flex h-11 w-11 items-center justify-center rounded-xl border border-white/60 bg-white/80 text-gray-900 hover:bg-white shadow-sm transition">
273                    <i class="fa-solid fa-bars"></i>
274                </button>
275                <a href="https://inkartists.com/login"
276                    class="hidden lg:inline-flex items-center gap-2 rounded-xl border border-white/60 bg-white/80 px-5 py-3 text-sm font-semibold text-gray-900 shadow-sm hover:-translate-y-0.5 hover:bg-white transition">
277                    Login
278                </a>
279                <button type="button" @click="openLogin()"
280                    class="hidden lg:inline-flex relative items-center gap-3 px-6 py-3 rounded-xl font-bold shadow-xl transition-all hover:scale-105 text-black bg-gradient-to-r from-cyan-400 to-purple-400 hover:from-cyan-300 hover:to-purple-300">
281                    <span class="relative z-10">Sign up</span>
282                                            <span class="absolute inset-0 rounded-full bg-white/20 blur-xl animate-pulse"></span>
283                                    </button>
284                <div x-show="guestMenu" x-transition
285                    class="lg:hidden absolute right-0 top-full mt-3 w-56 rounded-2xl border shadow-2xl overflow-hidden z-50 border-white/60 bg-white/95 backdrop-blur-xl">
286                    <div class="px-4 py-3 border-b border-slate-100">
287                        <p class="text-xs font-semibold uppercase tracking-[0.2em] text-slate-500">
288                            Explore
289                        </p>
290                    </div>
291                    <div class="py-2">
292                                                    <a href="https://inkartists.com/artists/search"
293                                class="flex items-center gap-2 px-4 py-2 text-sm font-semibold text-slate-800 hover:bg-slate-100">
294                                <i class="fa-solid fa-users text-slate-500"></i>
295                                Tattoo Artists
296                            </a>
297                            <a href="https://inkartists.com/tattoos"
298                                class="flex items-center gap-2 px-4 py-2 text-sm font-semibold text-slate-800 hover:bg-slate-100">
299                                <i class="fa-solid fa-image text-slate-500"></i>
300                                Tattoos
301                            </a>
302                                                            <a href="https://inkartists.com/jobs"
303                                    class="flex items-center gap-2 px-4 py-2 text-sm font-semibold text-slate-800 hover:bg-slate-100">
304                                    <i class="fa-solid fa-briefcase text-slate-500"></i>
305                                    Jobs
306                                </a>
307                                                        <a href="https://inkartists.com/tattoo-events"
308                                class="flex items-center gap-2 px-4 py-2 text-sm font-semibold text-slate-800 hover:bg-slate-100">
309                                <i class="fa-solid fa-calendar-days text-slate-500"></i>
310                                Tattoo Events
311                            </a>
312                                                <a href="https://inkartists.com/login"
313                            class="flex items-center gap-2 px-4 py-2 text-sm font-semibold text-slate-800 hover:bg-slate-100">
314                            <i class="fa-solid fa-right-to-bracket text-slate-500"></i>
315                            Login
316                        </a>
317                    </div>
318                    <div class="px-4 py-3 border-t border-slate-100">
319                        <button type="button" @click="openLogin(); guestMenu = false"
320                            class="w-full inline-flex items-center justify-center rounded-xl px-4 py-2 text-sm font-semibold text-white bg-slate-900 hover:bg-slate-800">
321                            Sign up
322                        </button>
323                    </div>
324                </div>
325            </div>
326        
327            </div>
328
329</header>
330
331
332            
333    <main class="flex-1  ">
334            <div class="min-h-screen bg-gradient-to-b from-amber-50 via-white to-white py-12">
335        <div class="max-w-6xl mx-auto px-4 space-y-10">
336            <div class="flex flex-col gap-4">
337                <div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
338                    <div class="space-y-3">
339                        <p class="text-xs uppercase tracking-[0.35em] text-amber-700">InkArtists Log</p>
340                        <h1 class="text-4xl md:text-5xl font-black text-gray-900">Updates, notes, and studio moments</h1>
341                    </div>
342                                    </div>
343                <p class="text-base md:text-lg text-gray-600 max-w-2xl">
344                    Short updates from the team. New features, artist highlights, and little wins worth sharing.
345                </p>
346            </div>
347
348                            <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
349                                                                    <a href="https://inkartists.com/blog/2026-02-20/inkartists-studios-search-tattoo-shops-artists-styles-and-tags-in-one-place/6"
350                            class="group rounded-3xl border border-gray-100 bg-white shadow-xl overflow-hidden hover:-translate-y-1 transition">
351                            <div class="h-44 bg-gray-100 overflow-hidden">
352                                                                    <img src="https://ink-storage.fra1.cdn.digitaloceanspaces.com/blog/covers/r5D0x2IWdKmzJRb14QZGznHYwRnlgkA3C6zVOAXy.png" alt="InkArtists Studios: Search Tattoo Shops, Artists, Styles, and Tags in One Place"
353                                        class="w-full h-full object-cover group-hover:scale-105 transition duration-300">
354                                                            </div>
355                            <div class="p-6 space-y-3">
356                                <div class="flex items-center gap-2 text-xs text-gray-500">
357                                    <span class="uppercase tracking-[0.2em]">Post</span>
358                                    <span>•</span>
359                                    <span>Feb 20, 2026</span>
360                                </div>
361                                <h2 class="text-xl font-bold text-gray-900 group-hover:text-amber-700 transition">
362                                    InkArtists Studios: Search Tattoo Shops, Artists, Styles, and Tags in One Place
363                                </h2>
364                                <p class="text-sm text-gray-600">
365                                    The new /studios directory is live, and it is built to make tattoo shop discovery feel effortless. If you are searching for the best tattoo...
366                                </p>
367                                <div class="text-sm font-semibold text-amber-700">Read →</div>
368                            </div>
369                        </a>
370                                                                    <a href="https://inkartists.com/blog/2026-01-21/tattoo-events-are-live-on-inkartists/5"
371                            class="group rounded-3xl border border-gray-100 bg-white shadow-xl overflow-hidden hover:-translate-y-1 transition">
372                            <div class="h-44 bg-gray-100 overflow-hidden">
373                                                                    <img src="https://ink-storage.fra1.cdn.digitaloceanspaces.com/blog/covers/PTmLtUNYFH9npzjrEt6eKQrolm43oqBoremMKt7I.jpg" alt="Tattoo Events Are Live on InkArtists"
374                                        class="w-full h-full object-cover group-hover:scale-105 transition duration-300">
375                                                            </div>
376                            <div class="p-6 space-y-3">
377                                <div class="flex items-center gap-2 text-xs text-gray-500">
378                                    <span class="uppercase tracking-[0.2em]">Post</span>
379                                    <span>•</span>
380                                    <span>Jan 21, 2026</span>
381                                </div>
382                                <h2 class="text-xl font-bold text-gray-900 group-hover:text-amber-700 transition">
383                                    Tattoo Events Are Live on InkArtists
384                                </h2>
385                                <p class="text-sm text-gray-600">
386                                    The Tattoo Events section is now live on InkArtists. It is the fastest way to find conventions, guest shows, and recurring editions without...
387                                </p>
388                                <div class="text-sm font-semibold text-amber-700">Read →</div>
389                            </div>
390                        </a>
391                                                                    <a href="https://inkartists.com/blog/2026-01-08/guest-spots-zero-friction/4"
392                            class="group rounded-3xl border border-gray-100 bg-white shadow-xl overflow-hidden hover:-translate-y-1 transition">
393                            <div class="h-44 bg-gray-100 overflow-hidden">
394                                                                    <img src="https://ink-storage.fra1.cdn.digitaloceanspaces.com/blog/covers/ag1V8BE0nlH6xPmCz2nDAkECkmAjRkfgxuquin21.png" alt="Guest Spots, Zero Friction"
395                                        class="w-full h-full object-cover group-hover:scale-105 transition duration-300">
396                                                            </div>
397                            <div class="p-6 space-y-3">
398                                <div class="flex items-center gap-2 text-xs text-gray-500">
399                                    <span class="uppercase tracking-[0.2em]">Post</span>
400                                    <span>•</span>
401                                    <span>Jan 08, 2026</span>
402                                </div>
403                                <h2 class="text-xl font-bold text-gray-900 group-hover:text-amber-700 transition">
404                                    Guest Spots, Zero Friction
405                                </h2>
406                                <p class="text-sm text-gray-600">
407                                    We’ve got a fresh update for artists who move around: guest spots are now easier to manage and way easier to share.
408
409You can update your g...
410                                </p>
411                                <div class="text-sm font-semibold text-amber-700">Read →</div>
412                            </div>
413                        </a>
414                                                                    <a href="https://inkartists.com/blog/2026-01-03/introducing-inkartists-jobs-guest-spots-residencies-and-studio-openings/3"
415                            class="group rounded-3xl border border-gray-100 bg-white shadow-xl overflow-hidden hover:-translate-y-1 transition">
416                            <div class="h-44 bg-gray-100 overflow-hidden">
417                                                                    <img src="https://ink-storage.fra1.cdn.digitaloceanspaces.com/blog/covers/FecWPhNeFtfeW5iZl31skz72tx9ICLNGtzTpYLQT.jpg" alt="Introducing InkArtists Jobs: guest spots, residencies, and studio openings"
418                                        class="w-full h-full object-cover group-hover:scale-105 transition duration-300">
419                                                            </div>
420                            <div class="p-6 space-y-3">
421                                <div class="flex items-center gap-2 text-xs text-gray-500">
422                                    <span class="uppercase tracking-[0.2em]">Post</span>
423                                    <span>•</span>
424                                    <span>Jan 03, 2026</span>
425                                </div>
426                                <h2 class="text-xl font-bold text-gray-900 group-hover:text-amber-700 transition">
427                                    Introducing InkArtists Jobs: guest spots, residencies, and studio openings
428                                </h2>
429                                <p class="text-sm text-gray-600">
430                                    We just launched the InkArtists Jobs board, a dedicated space to match tattoo artists and studios for guest spots, residencies, and openings...
431                                </p>
432                                <div class="text-sm font-semibold text-amber-700">Read →</div>
433                            </div>
434                        </a>
435                                                                    <a href="https://inkartists.com/blog/2026-01-02/the-studio-era-is-here-claim-organize-grow/2"
436                            class="group rounded-3xl border border-gray-100 bg-white shadow-xl overflow-hidden hover:-translate-y-1 transition">
437                            <div class="h-44 bg-gray-100 overflow-hidden">
438                                                                    <img src="https://ink-storage.fra1.cdn.digitaloceanspaces.com/blog/covers/CTt9ooKxZSF9aeTCBO8vwH0BSsnLJ60vL7fk8LiE.jpg" alt="The Studio Era Is Here: Claim, Organize, Grow"
439                                        class="w-full h-full object-cover group-hover:scale-105 transition duration-300">
440                                                            </div>
441                            <div class="p-6 space-y-3">
442                                <div class="flex items-center gap-2 text-xs text-gray-500">
443                                    <span class="uppercase tracking-[0.2em]">Post</span>
444                                    <span>•</span>
445                                    <span>Jan 02, 2026</span>
446                                </div>
447                                <h2 class="text-xl font-bold text-gray-900 group-hover:text-amber-700 transition">
448                                    The Studio Era Is Here: Claim, Organize, Grow
449                                </h2>
450                                <p class="text-sm text-gray-600">
451                                    Studios are no longer just a pin on a map. They&#039;re the heartbeat of the scene, where culture gets built and talent levels up. Clients want c...
452                                </p>
453                                <div class="text-sm font-semibold text-amber-700">Read →</div>
454                            </div>
455                        </a>
456                                                                    <a href="https://inkartists.com/blog/2025-12-29/what-is-inkartists/1"
457                            class="group rounded-3xl border border-gray-100 bg-white shadow-xl overflow-hidden hover:-translate-y-1 transition">
458                            <div class="h-44 bg-gray-100 overflow-hidden">
459                                                                    <img src="https://ink-storage.fra1.cdn.digitaloceanspaces.com/blog/covers/KLbjbKzaLjzJdpripmYAIXwD0eJqpFaL8VY99nHv.jpg" alt="What is InkArtists?"
460                                        class="w-full h-full object-cover group-hover:scale-105 transition duration-300">
461                                                            </div>
462                            <div class="p-6 space-y-3">
463                                <div class="flex items-center gap-2 text-xs text-gray-500">
464                                    <span class="uppercase tracking-[0.2em]">Post</span>
465                                    <span>•</span>
466                                    <span>Dec 29, 2025</span>
467                                </div>
468                                <h2 class="text-xl font-bold text-gray-900 group-hover:text-amber-700 transition">
469                                    What is InkArtists?
470                                </h2>
471                                <p class="text-sm text-gray-600">
472                                    InkArtists is a booking platform built for tattoo artists. It&#039;s not a marketplace that takes a cut of your work. It&#039;s a clean way to show yo...
473                                </p>
474                                <div class="text-sm font-semibold text-amber-700">Read →</div>
475                            </div>
476                        </a>
477                                    </div>
478
479                <div class="pt-4">
480                    
481                </div>
482                    </div>
483    </div>
484    </main>
485
486            <footer class="relative overflow-hidden backdrop-blur-2xl border-t bg-black/95 border-white/10">
487        
488    <div aria-hidden="true" class="absolute inset-0 opacity-20">
489        <div class="absolute -top-40 -left-40 w-96 h-96 bg-purple-600 rounded-full blur-3xl animate-blob"></div>
490        <div
491            class="absolute top-20 -right-40 w-80 h-80 bg-cyan-500 rounded-full blur-3xl animate-blob animation-delay-2000">
492        </div>
493    </div>
494    
495    <div class="relative max-w-7xl mx-auto px-6 py-16">
496        <div class="grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-5 ga
496p-8 sm:gap-10">
497
498            <!-- Brand + Tagline -->
499            <div
500                class="col-span-2 order-1 lg:order-none sm:col-span-1 md:col-span-1 flex flex-col items-center md:items-start lg:items-center gap-3">
501                <a href="https://inkartists.com" class="inline-flex">
502                    <img src="https://inkartists.com/storage/images/logo/logo_white_text.png"
503                        alt="Ink Artists" class="h-35 w-auto object-contain">
504                </a>
505                <p class="text-sm text-gray-400 max-w-xs text-center lg:text-center md:text-left">
506                    The global home for the tattoo industry. No DM chaos—work smart.
507                </p>
508
509                <div class="mt-6 flex gap-4 justify-center md:justify-start">
510                    <a href="https://www.instagram.com/inkartistscom" target="_blank" rel="noopener"
511                        class="w-10 h-10 rounded-full bg-white/10 backdrop-blur hover:bg-white/20 flex items-center justify-center transition">
512                        <span class="sr-only">Instagram</span>
513                        <i class="fa-brands fa-instagram text-white"></i>
514                    </a>
515                </div>
516                <div class="mt-2 flex flex-wrap gap-2 justify-center md:justify-start">
517                    <a href="https://play.google.com/store/apps/details?id=com.inkartists.app" target="_blank" rel="noopener noreferrer"
518                        class="inline-flex items-center gap-2 rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-xs font-semibold text-gray-200 transition hover:border-white/25 hover:bg-white/10 hover:text-white">
519                        <i class="fa-brands fa-google-play text-sm text-emerald-300"></i>
520                        Get it on Google Play
521                    </a>
522                    <a href="https://apps.apple.com/app/inkartists-tattoo-booking-app/id6793135266" target="_blank" rel="noopener noreferrer"
523                        class="inline-flex items-center gap-2 rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-xs font-semibold text-gray-200 transition hover:border-white/25 hover:bg-white/10 hover:text-white">
524                        <i class="fa-brands fa-apple text-sm text-cyan-300"></i>
525                        Download on the App Store
526                    </a>
527                </div>
528            </div>
529
530            <!-- Quick Links -->
531            <div class="col-span-1 order-2 lg:order-none text-center sm:text-left">
532                <h3 class="font-bold text-white mb-3">Discover</h3>
533                <ul class="space-y-2 text-sm text-gray-300">
534                    <li><a href="https://inkartists.com/partners"
535                            class="block px-1 py-1 hover:text-white transition">For Artists</a>
536                    </li>
537                    <li><a href="https://inkartists.com/clients"
538                            class="block px-1 py-1 hover:text-white transition">For Clients</a>
539                    </li>
540                    <li><a href="https://inkartists.com/for-studios"
541                            class="block px-1 py-1 hover:text-white transition">For Studios</a>
542                    </li>
543                    <li><a href="https://inkartists.com/pricing"
544                            class="block px-1 py-1 hover:text-white transition">Pricing</a>
545                    </li>
546                    <li><a href="https://inkartists.com/support"
547                            class="block px-1 py-1 hover:text-white transition">Support</a>
548                    </li>
549                </ul>
550            </div>
551
552            <!-- Discover -->
553            <div class="col-span-1 order-3 lg:order-none text-center sm:text-left">
554                <h3 class="font-bold text-white mb-3">Platform</h3>
555                <ul class="space-y-2 text-sm text-gray-300">
556                    <li><a href="https://inkartists.com/studios"
557                            class="block px-1 py-1 hover:text-white transition">Tattoo Studios</a>
558                    </li>
559                    <li><a href="https://inkartists.com/jobs"
560                            class="block px-1 py-1 hover:text-white transition">Jobs</a>
561                    </li>
562                    <li><a href="https://inkartists.com/tattoo-apprenticeships"
563                            class="block px-1 py-1 hover:text-white transition">Apprenticeships</a>
564                    </li>
565                    <li><a href="https://inkartists.com/tattoo-guest-spots"
566                            class="block px-1 py-1 hover:text-white transition">Guest spots</a>
567                    </li>
568                    <li><a href="https://inkartists.com/marketplace"
569                            class="block px-1 py-1 hover:text-white transition">Equipment marketplace</a>
570                    </li>
571                    <li><a href="https://inkartists.com/guides"
572                            class="block px-1 py-1 hover:text-white transition">Licensing guides</a>
573                    </li>
574                    <li><a href="https://inkartists.com/artists"
575                            class="block px-1 py-1 hover:text-white transition">Tattoo artists</a>
576                    </li>
577                    <li><a href="https://inkartists.com/tattoos"
578                            class="block px-1 py-1 hover:text-white transition">Tattoo Designs</a>
579                    </li>
580                </ul>
581            </div>
582
583            <!-- Company -->
584            <div class="col-span-1 order-4 lg:order-none text-center sm:text-left">
585                <h3 class="font-bold text-white mb-3">Company</h3>
586                <ul class="space-y-2 text-sm text-gray-300">
587                    <li><a href="https://inkartists.com/company"
588                            class="block px-1 py-1 hover:text-white transition">Company</a>
589                    </li>
590                    <li><a href="https://inkartists.com/blog"
591                            class="block px-1 py-1 hover:text-white transition">Blog</a>
592                    </li>
593                    <li><a href="https://inkartists.com/instagram"
594                            class="block px-1 py-1 hover:text-white transition">Our Instagram</a>
595                    </li>
596                    <li><a href="https://inkartists.com/support"
597                            class="block px-1 py-1 hover:text-white transition">Contact</a>
598                    </li>
599                </ul>
600                            </div>
601
602            <!-- Legal + Final CTA -->
603            <div class="col-span-2 order-5 lg:order-none lg:col-span-1 text-center sm:text-left lg:text-center">
604                <div
605                    class="flex flex-wrap items-start justify-between gap-6 lg:block lg:space-y-6 lg:text-center">
606                    <div class="min-w-[140px]">
607                        <h3 class="font-bold text-white mb-3 lg:mb-3">Legal</h3>
608                        <ul class="flex flex-col gap-2 text-sm text-gray-300">
609                            <li><a href="https://inkartists.com/privacy"
610                                    class="px-1 py-1 hover:text-white transition">Privacy Policy</a>
611                            </li>
612                            <li><a href="https://inkartists.com/terms"
613                                    class="px-1 py-1 hover:text-white transition">Terms of Service</a>
614                            </li>
615                            <li><a href="https://inkartists.com/cookies"
616                                    class="px-1 py-1 hover:text-white transition">Cookie Policy</a>
617                            </li>
618                        </ul>
619                    </div>
620                    <div class="min-w-[160px] lg:mt-6">
621                        <label class="block text-xs uppercase tracking-[0.2em] text-gray-400 mb-2"
622                            for="footer-locale">Language</label>
623                                                <select id="footer-locale"
624                            class="w-full rounded-xl bg-white/10 border border-white/10 text-white text-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-cyan-400/60"
625                            onchange="window.location = this.value">
626                                                            <option value="https://inkartists.com/locale/de?to=%2Fblog" >
627                                    Deutsch
628                                </option>
629                                                            <option value="https://inkartists.com/locale/en?to=%2Fblog" selected>
630                                    English
631                                </option>
632                                                            <option value="https://inkartists.com/locale/es?to=%2Fblog" >
633                                    Español
634                                </option>
635                                                            <option value="https://inkartists.com/locale/fr?to=%2Fblog" >
636                                    Français
637                                </option>
638                                                            <option value="https://inkartists.com/locale/it?to=%2Fblog" >
639                                    Italiano
640                                </option>
641                                                            <option value="https://inkartists.com/locale/nl?to=%2Fblog" >
642                                    Nederlands
643                                </option>
644                                                            <option value="https://inkartists.com/locale/pl?to=%2Fblog" >
645                                    Polski
646                                </option>
647                                                            <option value="https://inkartists.com/locale/pt?to=%2Fblog" >
648                                    Português
649                                </option>
650                                                            <option value="https://inkartists.com/locale/tr?to=%2Fblog" >
651                                    Türkçe
652                                </option>
653                                                            <option value="https://inkartists.com/locale/ru?to=%2Fblog" >
654                                    Русский
655                                </option>
656                                                            <option value="https://inkartists.com/locale/ko?to=%2Fblog" >
657                                    한국어
658                                </option>
659                                                            <option value="https://inkartists.com/locale/ja?to=%2Fblog" >
660                                    日本語
661                                </option>
662                                                    </select>
663                        <noscript>
664                            <ul class="mt-2 space-y-1 text-sm text-gray-300">
665                                                                    <li>
666                                        <a href="https://inkartists.com/locale/de?to=%2Fblog" class="hover:text-white">
666Deutsch</a>
667                                    </li>
668                                                                    <li>
669                                        <a href="https://inkartists.com/locale/en?to=%2Fblog" class="hover:text-white">English</a>
670                                    </li>
671                                                                    <li>
672                                        <a href="https://inkartists.com/locale/es?to=%2Fblog" class="hover:text-white">Español</a>
673                                    </li>
674                                                                    <li>
675                                        <a href="https://inkartists.com/locale/fr?to=%2Fblog" class="hover:text-white">Français</a>
676                                    </li>
677                                                                    <li>
678                                        <a href="https://inkartists.com/locale/it?to=%2Fblog" class="hover:text-white">Italiano</a>
679                                    </li>
680                                                                    <li>
681                                        <a href="https://inkartists.com/locale/nl?to=%2Fblog" class="hover:text-white">Nederlands</a>
682                                    </li>
683                                                                    <li>
684                                        <a href="https://inkartists.com/locale/pl?to=%2Fblog" class="hover:text-white">Polski</a>
685                                    </li>
686                                                                    <li>
687                                        <a href="https://inkartists.com/locale/pt?to=%2Fblog" class="hover:text-white">Português</a>
688                                    </li>
689                                                                    <li>
690                                        <a href="https://inkartists.com/locale/tr?to=%2Fblog" class="hover:text-white">Türkçe</a>
691                                    </li>
692                                                                    <li>
693                                        <a href="https://inkartists.com/locale/ru?to=%2Fblog" class="hover:text-white">Русский</a>
694                                    </li>
695                                                                    <li>
696                                        <a href="https://inkartists.com/locale/ko?to=%2Fblog" class="hover:text-white">한국어</a>
697                                    </li>
698                                                                    <li>
699                                        <a href="https://inkartists.com/locale/ja?to=%2Fblog" class="hover:text-white">日本語</a>
700                                    </li>
701                                                            </ul>
702                        </noscript>
703                    </div>
704                    <div
705                        class="min-w-[200px] lg:mt-10 w-full lg:w-auto p-5 rounded-2xl border bg-gradient-to-r from-cyan-500/10 to-purple-500/10 border-white/10">
706                        <p class="text-xs text-gray-300">
707                            © 2026 InkArtists. All rights reserved.
708                        </p>
709                        <p class="mt-2 text-xs font-medium text-cyan-300">
710                            Made for the culture 🖤
711                        </p>
712                    </div>
713                </div>
714            </div>
715        </div>
716
717    </div>
718</footer>
719    
720            <div x-data="{
721            open: false,
722            lastClosedAt: 0,
723            close() {
724                this.open = false;
725                this.lastClosedAt = Date.now();
726                window.dispatchEvent(new Event('ink-login-close'));
727            },
728            openIfAllowed() {
729                if (Date.now() - this.lastClosedAt < 250) {
730                    return;
731                }
732                this.open = true;
733            }
734        }" x-show="open" x-cloak x-transition.opacity
735        x-on:ink-login-open.window="openIfAllowed()"
736        x-on:keydown.escape.window="close()"
737        @click.self="close()"
738        @mousedown.self.prevent="close()"
739        class="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm">
740        <div class="relative z-10 w-full max-w-md rounded-3xl border border-white/70 bg-white/95 shadow-2xl ring-1 ring-black/5"
741            data-ink-login-card @click.stop
742            x-data="inkAccountStepper(null, false)"
743            x-on:ink-login-open.window="applyType($event.detail && $event.detail.accountType)"
744            x-on:ink-login-close.window="onClose()">
745            <div class="p-6">
746                <div x-show="step === 1" x-cloak>
747                    <div class="mb-2">
748                        <h4 class="text-xl font-black text-gray-900">Choose your account type</h4>
749                        <span class="mt-2 block h-1 w-16 rounded-full bg-gradient-to-r from-indigo-500 via-pink-500 to-amber-400"></span>
750                    </div>
751                    <p class="text-sm text-gray-600 mb-5">Who are you signing up as?</p>
752                    <div class="mb-6">
753                        <p class="text-[11px] uppercase tracking-[0.2em] text-gray-400">I am a</p>
754                        <div class="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-3">
755                            <button type="button" @click="select('artist')"
756                                :class="selected === 'artist' ? 'bg-gray-900 text-white border-gray-900' : 'border-gray-200 text-gray-700 hover:border-gray-300 hover:text-gray-900'"
757                                class="rounded-2xl border px-4 py-3 text-sm font-semibold transition">
758                                Artist
759                            </button>
760                            <button type="button" @click="select('client')"
761                                :class="selected === 'client' ? 'bg-gray-900 text-white border-gray-900' : 'border-gray-200 text-gray-700 hover:border-gray-300 hover:text-gray-900'"
762                                class="rounded-2xl border px-4 py-3 text-sm font-semibold transition">
763                                Client
764                            </button>
765                            <button type="button" @click="select('studio')"
766                                :class="selected === 'studio' ? 'bg-gray-900 text-white border-gray-900' : 'border-gray-200 text-gray-700 hover:border-gray-300 hover:text-gray-900'"
767                                class="rounded-2xl border px-4 py-3 text-sm font-semibold transition">
768                                Studio
769                            </button>
770                        </div>
771                    </div>
772                    <button type="button" @click="step = 2" :disabled="!selected"
773                        class="w-full rounded-2xl bg-gray-900 px-4 py-3 text-sm font-semibold text-white transition hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed">
774                        Continue
775                    </button>
776                </div>
777
778                <input type="hidden" data-ink-login-redirect value="https://inkartists.com/blog">
779
780                <div x-show="step === 2" x-cloak class="space-y-3">
781                    <div class="mb-2">
782                        <h4 class="text-xl font-black text-gray-900">Sign in to continue</h4>
783                        <span class="mt-2 block h-1 w-16 rounded-full bg-gradient-to-r from-indigo-500 via-pink-500 to-amber-400"></span>
784                    </div>
785                    <p class="text-sm text-gray-600 mb-5">Choose a sign-in method to continue.</p>
786                    <div class="flex flex-wrap items-center justify-between gap-3">
787                        <button type="button" @click="step = 1" x-show="!skipType" x-cloak
788                            class="inline-flex items-center gap-2 text-xs font-semibold text-gray-500 hover:text-gray-800">
789                            <i class="fa-solid fa-arrow-left"></i>
790                            Back
791                        </button>
792                        <span class="text-xs font-semibold text-gray-500" x-text="selectedLabel()"></span>
793                    </div>
794                    <button
795                        type="button"
796                        class="group w-full rounded-2xl border border-gray-200 bg-white px-4 py-3 font-semibold text-gray-900 shadow-sm transition hover:-translate-y-0.5 hover:border-gray-300 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
797                        data-ink-login="google"
798                        data-ink-login-idle="Continue with Google"
799                        data-ink-login-loading="Signing in…"
800                    >
801                        <span class="flex items-center justify-center gap-2">
802                            <span class="inline-flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 text-gray-700 group-hover:bg-gray-200">
803                                <i class="fa-brands fa-google"></i>
804                            </span>
805                            <span>Continue with Google</span>
806                        </span>
807                    </button>
808                    <button
809                        type="button"
810                        class="group w-full rounded-2xl border border-gray-200 bg-white px-4 py-3 font-semibold text-gray-900 shadow-sm transition hover:-translate-y-0.5 hover:border-gray-300 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
811                        data-ink-login="facebook"
812                        data-ink-login-idle="Continue with Facebook"
813                        data-ink-login-loading="Signing in…"
814                    >
815                        <span class="flex items-center justify-center gap-2">
816                            <span class="inline-flex h-8 w-8 items-center justify-center rounded-full bg-blue-50 text-blue-600 group-hover:bg-blue-100">
817                                <i class="fa-brands fa-facebook-f"></i>
818                            </span>
819                            <span>Continue with Facebook</span>
820                        </span>
821                    </button>
822                    <div class="flex items-center gap-3 text-xs text-gray-400">
823                        <span class="h-px flex-1 bg-gray-200"></span>
824                        <span>or use email</span>
825                        <span class="h-px flex-1 bg-gray-200"></span>
826                    </div>
827                    <form data-ink-login-email-form class="space-y-3">
828                        <input type="hidden" data-ink-email-mode value="signup">
829                        <div class="grid gap-3">
830                            <input type="email" name="email" autocomplete="email" required
831                                class="w-full rounded-2xl border border-gray-200 bg-white px-4 py-3 text-sm text-gray-900 placeholder:text-gray-400 focus:border-indigo-400 focus:ring-indigo-400"
832                                placeholder="Email address">
833                            <input type="password" name="password" autocomplete="current-password" required
834                                class="w-full rounded-2xl border border-gray-200 bg-white px-4 py-3 text-sm text-gray-900 placeholder:text-gray-400 focus:border-indigo-400 focus:ring-indigo-400"
835                                placeholder="Password">
836                        </div>
837                        <button type="submit" data-ink-email-action="signin"
838                            class="w-full rounded-2xl bg-gray-900 px-4 py-3 text-sm font-semibold text-white transition hover:bg-gray-800">
839                            <span class="inline-flex items-center justify-center gap-2">
840                                <i class="fa-solid fa-envelope" data-ink-email-action-icon></i>
841                                <span data-ink-email-action-label>Sign in with email</span>
842                            </span>
843                        </button>
844                        <button type="button" data-ink-email-toggle
845                            class="w-full text-sm font-semibold text-gray-600 hover:text-gray-900">
846                            <span class="inline-flex items-center justify-center gap-2">
847                                <i class="fa-solid fa-user-plus" data-ink-email-toggle-icon></i>
848                                <span data-ink-email-toggle-label>Don’t have an account? Create one</span>
849                            </span>
850                        </button>
851                        <p data-ink-login-email-success class="text-emerald-600 text-sm text-center hidden"></p>
852                        <p data-ink-login-email-error class="text-red-600 text-sm text-center hidden"></p>
853                    </form>
854                    
855                </div>
856
857                <p data-ink-login-error class="text-red-600 text-sm text-center hidden mt-4"></p>
858            </div>
859        </div>
860    </div>
861
862    
862<script src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js" defer></script>
862
863
864<script type="module">
865    import {
866        initializeApp
867    } from "https://www.gstatic.com/firebasejs/10.11.1/firebase-app.js";
868    import {
869        initializeAuth,
870        indexedDBLocalPersistence,
871        browserLocalPersistence,
872        inMemoryPersistence,
873        browserPopupRedirectResolver,
874        signInWithCredential,
875        linkWithCredential,
876        linkWithPopup,
877        linkWithRedirect,
878        OAuthProvider,
879        GoogleAuthProvider,
880        FacebookAuthProvider,
881        fetchSignInMethodsForEmail,
882        signInWithPopup,
883        signInWithRedirect,
884        getRedirectResult,
885        createUserWithEmailAndPassword,
886        signInWithEmailAndPassword,
887        sendEmailVerification,
888        signOut,
889    } from "https://www.gstatic.com/firebasejs/10.11.1/firebase-auth.js";
890
891    function gtagSafe() {
892        try {
893            window.gtag && window.gtag(...arguments);
894        } catch {}
895    }
896
897    function isAccountExistsError(err) {
898        const code = err && err.code ? String(err.code) : '';
899        return code === 'auth/account-exists-with-different-credential';
900    }
901
902    function formatAuthError(err) {
903        const code = err && err.code ? String(err.code) : '';
904        if (code === 'auth/account-exists-with-different-credential') {
905            return 'This email is already registered with another sign-in method. Please sign in with that method first.';
906        }
907        if (code === 'auth/popup-closed-by-user') {
908            return 'Sign-in popup was closed.';
909        }
910        return (err && err.message) || 'Something went wrong.';
911    }
912
913    function labelsFromMethods(methods = []) {
914        const map = {
915            'google.com': 'Google',
916            'facebook.com': 'Facebook',
917            'apple.com': 'Apple',
918            'password': 'Email',
919        };
920        const labels = methods.map((m) => map[m] || m).filter(Boolean);
921        return Array.from(new Set(labels));
922    }
923
924    function fallbackProviderList() {
925        const labels = [];
926        if (document.querySelector('[data-ink-login="google"]')) labels.push('Google');
927        if (document.querySelector('[data-ink-login-email-form]')) labels.push('Email');
928        return labels;
929    }
930
931    function labelFromProviderId(providerId) {
932        if (providerId === 'google.com') return 'Google';
933        if (providerId === 'facebook.com') return 'Facebook';
934        if (providerId === 'apple.com') return 'Apple';
935        if (providerId === 'password') return 'Email';
936        return '';
937    }
938
939    // --- helpers ---
940    async function sha256Hex(str) {
941        const enc = new TextEncoder().encode(str);
942        const hash = await crypto.subtle.digest("SHA-256", enc);
943        return [...new Uint8Array(hash)].map(b => b.toString(16).padStart(2, "0")).join("");
944    }
945
946    function randomNonce(len = 32) {
947        const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
948        const arr = new Uint8Array(len);
949        crypto.getRandomValues(arr);
950        return [...arr].map(b => chars[b % chars.length]).join("");
951    }
952    async function waitForApple() {
953        if (window.AppleID?.auth) return;
954        await new Promise(resolve => {
955            const t = setInterval(() => {
956                if (window.AppleID?.auth) {
957                    clearInterval(t);
958                    resolve();
959                }
960            }, 50);
961            setTimeout(() => {
962                clearInterval(t);
963                resolve();
964            }, 3000);
965        });
966    }
967
968    // --- one-time init ---
969    if (!window.__inkAuthInit) {
970        window.__inkAuthInit = true;
971
972        const firebaseConfig = {
973            apiKey: "AIzaSyBnuY0IrO5vbzBiALrFvOoifF-dGCNDRc4",
974            authDomain: "www.inkartists.com",
975            projectId: "inkartists-1d4b3",
976            appId: "1:156698075123:web:548d07ee6c6fc9cec07c86",
977            messagingSenderId: "156698075123",
978            storageBucket: "inkartists-1d4b3.firebasestorage.app",
979        };
980
981        const app = initializeApp(firebaseConfig);
982        const auth = initializeAuth(app, {
983            persistence: [indexedDBLocalPersistence, browserLocalPersistence, inMemoryPersistence],
984            popupRedirectResolver: browserPopupRedirectResolver,
985        });
986        window.__inkFirebaseAuth = auth;
987        window.__inkLastProviderId = '';
988        window.__inkLastProviderLabel = '';
989        window.__inkAuthInFlight = false;
990
991        // redirect param persistence
992        const getRedirectParam = () => {
993            try {
994                return new URL(window.location.href).searchParams.get("redirect") || "";
995            } catch {
996                return "";
997            }
998        };
999        const persistRedirect = v => v && sessionStorage.setItem("ink_redirect", v);
1000        const readPersistedRedirect = () => sessionStorage.getItem("ink_redirect") || "";
1001        const clearPersistedRedirect = () => sessionStorage.removeItem("ink_redirect");
1002        const applyLoginRedirect = (value) => {
1003            if (!value) return;
1004            persistRedirect(value);
1005            document.querySelectorAll('[data-ink-login-redirect]').forEach((el) => {
1006                el.value = value;
1007            });
1008        };
1009        window.addEventListener('ink-login-open', (event) => {
1010            const redirectValue = event?.detail?.redirect || '';
1011            if (!redirectValue) return;
1012            applyLoginRedirect(redirectValue);
1013        });
1014
1015        // env detection
1016        const ua = navigator.userAgent || "";
1017        const isIOS = /iP(hone|od|ad)/.test(ua) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
1018
1019        // Apple popup (desktop)
1020        async function signInWithAppleDesktopPopup() {
1021            await waitForApple();
1022            const rawNonce = randomNonce();
1023            const hashed = await sha256Hex(rawNonce);
1024            window.AppleID.auth.init({
1025                clientId: "login.inkartists.com", // <-- Apple Service ID for InkArtists
1026                scope: "name email",
1027                redirectURI: "https://www.inkartists.com/__/auth/handler", // or https://auth.inkartists.com/__/auth/handler
1028                usePopup: true,
1029                nonce: hashed,
1030            });
1031            const res = await window.AppleID.auth.signIn();
1032            const idToken = res?.authorization?.id_token;
1033            if (!idToken) throw new Error("apple_missing_id_token");
1034            const provider = new OAuthProvider("apple.com");
1035            const cred = provider.credential({
1036                idToken,
1037                rawNonce
1038            });
1039            const userCred = await signInWithCredential(auth, cred);
1040            return userCred.user;
1041        }
1042
1043        function markAuthProgress() {
1044            try {
1045                sessionStorage.setItem("ink_auth_progress", "1");
1046            } catch {}
1047            window.dispatchEvent(new Event("ink-auth-progress"));
1048            window.__inkAuthTimeout && clearTimeout(window.__inkAuthTimeout);
1049            window.__inkAuthTimeout = setTimeout(() => {
1050                try {
1051                    sessionStorage.removeItem("ink_auth_progress");
1052                } catch {}
1053                window.dispatchEvent(new Event("ink-auth-complete"));
1054            }, 15000);
1055        }
1056
1057        function clearAuthProgress() {
1058            try {
1059                sessionStorage.removeItem("ink_auth_progress");
1060            } catch {}
1061            window.__inkAuthInFlight = false;
1062            window.dispatchEvent(new Event("ink-auth-complete"));
1063        }
1064
1065        function showGlobalLoginError(message) {
1066            if (window.__inkLastAuthError === message) return;
1067            window.__inkLastAuthError = message;
1068            const showOnce = (selector) => {
1069                const nodes = Array.from(document.querySelectorAll(selector));
1070                if (!nodes.length) return;
1071                nodes.forEach((el) => el.classList.add('hidden'));
1072                const target = nodes.find((el) => !el.closest('[hidden]')) || nodes[0];
1073                target.textContent = message;
1074                target.classList.remove('hidden');
1075            };
1076            const hasLoginError = document.querySelector('[data-ink-login-error]');
1077            showOnce(hasLoginError ? '[data-ink-login-error]' : '[data-ink-login-email-error]');
1078        }
1079
1080        function persistPendingLink(pending) {
1081            try {
1082                sessionStorage.setItem('ink_pending_link', JSON.stringify(pending));
1083            } catch {}
1084        }
1085
1086        function restorePendingLink() {
1087            try {
1088                const raw = sessionStorage.getItem('ink_pending_link');
1089                if (!raw) return null;
1090                const data = JSON.parse(raw);
1091                if (!data || !data.providerId) return null;
1092                let credential = null;
1093                if (data.providerId === 'facebook.com' && data.accessToken) {
1094                    credential = FacebookAuthProvider.credential(data.accessToken);
1095                } else if (data.providerId === 'google.com' && (data.idToken || data.accessToken)) {
1096                    credential = GoogleAuthProvider.credential(data.idToken || null, data.accessToken || null);
1097                } else if (data.providerId === 'apple.com' && data.idToken) {
1098                    const provider = new OAuthProvider('apple.com');
1099                    credential = provider.credential({
1100                        idToken: data.idToken,
1101                        rawNonce: data.rawNonce || undefined
1102                    });
1103                }
1104                if (!credential) return null;
1105                return {
1106                    credential,
1107                    email: data.email || '',
1108                    attempted: data.attempted || labelFromProviderId(data.providerId),
1109                    providerId: data.providerId
1110                };
1111            } catch {
1112                return null;
1113            }
1114        }
1115
1116        function capturePendingCredential(err, attemptedProviderLabel = '', providerIdOverride = '') {
1117            const email = err?.customData?.email || err?.email || '';
1118            const providerId = providerIdOverride || err?.customData?.providerId || window.__inkLastProviderId || '';
1119            let credential = null;
1120            if (providerId === 'facebook.com') {
1121                credential = FacebookAuthProvider.credentialFromError(err);
1122            } else if (providerId === 'google.com') {
1123                credential = GoogleAuthProvider.credentialFromError(err);
1124            } else if (providerId === 'apple.com') {
1125                credential = OAuthProvider.credentialFromError(err);
1126            }
1127            if (!credential) {
1128                try {
1129                } catch {}
1130            }
1131            if (credential) {
1132                window.__inkPendingLink = {
1133                    credential,
1134                    email,
1135                    attempted: attemptedProviderLabel || labelFromProviderId(providerId),
1136                    providerId
1137                };
1138                const payload = {
1139                    providerId,
1140                    email,
1141                    attempted: attemptedProviderLabel || labelFromProviderId(providerId),
1142                    accessToken: credential.accessToken || null,
1143                    idToken: credential.idToken || null,
1144                    rawNonce: credential.rawNonce || null
1145                };
1146                persistPendingLink(payload);
1147                try {
1148                } catch {}
1149            }
1150            if (!credential && providerId) {
1151                const payload = {
1152                    providerId,
1153                    email,
1154                    attempted: attemptedProviderLabel || labelFromProviderId(providerId),
1155                    accessToken: null,
1156                    idToken: null,
1157                    rawNonce: null
1158                };
1159                persistPendingLink(payload);
1160                window.__inkPendingLink = {
1161                    credential: null,
1162                    email,
1163                    attempted: attemptedProviderLabel || labelFromProviderId(providerId),
1164                    providerId
1165                };
1166            }
1167        }
1168
1169        async function linkPendingCredential(user, providerLabel = '') {
1170            const pending = window.__inkPendingLink || restorePendingLink();
1171            if (!pending || !pending.credential) return false;
1172            const pendingEmail = (pending.email || '').toLowerCase();
1173            const userEmail = (user?.email || '').toLowerCase();
1174            if (pendingEmail && userEmail && pendingEmail !== userEmail) {
1175                return false;
1176            }
1177            try {
1178                await linkWithCredential(user, pending.credential);
1179                window.__inkPendingLink = null;
1180                try {
1181                    sessionStorage.removeItem('ink_pending_link');
1182                } catch {}
1183                try {
1184                } catch {}
1185                return true;
1186            } catch (err) {
1187                const code = err && err.code ? String(err.code) : '';
1188                if (code === 'auth/provider-already-linked' || code === 'auth/credential-already-in-use') {
1189                    window.__inkPendingLink = null;
1190                    try {
1191                        sessionStorage.removeItem('ink_pending_link');
1192                    } catch {}
1193                    return false;
1194                }
1195                try {
1196                } catch {}
1197                return false;
1198            }
1199        }
1200
1201        async function linkPendingProvider(user) {
1202            const pending = window.__inkPendingLink || restorePendingLink();
1203            if (!pending || pending.credential || !pending.providerId) return { linked: false, redirecting: false };
1204            const providerId = pending.providerId;
1205            let provider = null;
1206            if (providerId === 'facebook.com') {
1207                provider = new FacebookAuthProvider();
1208                provider.setCustomParameters({ scope: 'public_profile,email' });
1209            } else if (providerId === 'google.com') {
1210                provider = new GoogleAuthProvider();
1211            } else if (providerId === 'apple.com') {
1212                provider = new OAuthProvider('apple.com');
1213            } else {
1214                return { linked: false, redirecting: false };
1215            }
1216            try {
1217                await linkWithPopup(user, provider);
1218                window.__inkPendingLink = null;
1219                try {
1220                    sessionStorage.removeItem('ink_pending_link');
1221                } catch {}
1222                return { linked: true, redirecting: false };
1223            } catch (err) {
1224                try {
1225                } catch {}
1226                try {
1227                    await linkWithRedirect(user, provider);
1228                    return { linked: false, redirecting: true };
1229                } catch {}
1230                return { linked: false, redirecting: false };
1231            }
1232        }
1233
1234        // Expose helpers for handlers that may run outside this module's scope.
1235        window.linkPendingCredential = linkPendingCredential;
1236        window.linkPendingProvider = linkPendingProvider;
1237        window.clearAuthProgress = clearAuthProgress;
1238        window.showGlobalLoginError = showGlobalLoginError;
1239
1240        async function handleAccountExistsError(err) {
1241            const email = err?.customData?.email || err?.email || '';
1242            const providerId = err?.customData?.providerId || window.__inkLastProviderId || '';
1243            const attemptedProvider = labelFromProviderId(providerId) || window.__inkLastProviderLabel || '';
1244            capturePendingCredential(err, attemptedProvider, providerId);
1245            if (!email) {
1246                showGlobalLoginError(formatAuthError(err));
1247                return;
1248            }
1249            try {
1250                const methods = await fetchSignInMethodsForEmail(auth, email);
1251                try {
1252                } catch {}
1253                const labels = labelsFromMethods(methods);
1254                if (methods.length) {
1255                    const providerList = (labels.length ? labels : methods).join(', ');
1256                    showGlobalLoginError(
1257                        'This email is already registered with another sign-in method.' +
1258                            ' ' +
1259                            'Please sign in with' +
1260                            ' ' +
1261                            providerList +
1262                            (attemptedProvider ? ' ' + 'to link' + ' ' + attemptedProvider + '.' : '.')
1263                    );
1264                    return;
1265                }
1266                const fallback = fallbackProviderList();
1267                if (fallback.length) {
1268                    showGlobalLoginError(
1269                        'This email is already registered with another sign-in method.' +
1270                            ' ' +
1271                            'Please sign in with' +
1272                            ' ' +
1273                            fallback.join(', ') +
1274                            (attemptedProvider ? ' ' + 'to link' + ' ' + attemptedProvider + '.' : '.')
1275                    );
1276                    return;
1277                }
1278            } catch {}
1279            showGlobalLoginError(formatAuthError(err));
1280        }
1281
1282        async function finishLogin(user, providerLabel = 'Unknown') {
1283            markAuthProgress();
1284            const meta = user?.metadata || {};
1285            const isNew = meta.creationTime && meta.lastSignInTime && (meta.creationTime === meta.lastSignInTime);
1286            try {
1287            } catch {}
1288
1289            // GA4 events
1290            gtagSafe('event', isNew ? 'sign_up' : 'login', {
1291                method: providerLabel,
1292                transport_type: 'beacon'
1293            });
1294            gtagSafe('set', 'user_properties', {
1295                auth_provider: providerLabel,
1296                signup_flow: isNew ? 'new' : 'returning'
1297            });
1298
1299            // server session on InkArtists
1300            const idToken = await user.getIdToken();
1301            const clientRedirect = getRedirectParam() || readPersistedRedirect();
1302            const linked = typeof linkPendingCredential === 'function'
1303                ? await linkPendingCredential(user, providerLabel)
1304                : false;
1305            if (!linked) {
1306                const linkProviderResult = typeof linkPendingProvider === 'function'
1307                    ? await linkPendingProvider(user)
1308                    : { linked: false, redirecting: false };
1309                if (linkProviderResult.redirecting) {
1310                    return;
1311                }
1312            }
1313            const xhr = new XMLHttpRequest();
1314            xhr.open("POST", "/login/callback",
1315                true); // <-- InkArtists endpoint (same origin)
1316            xhr.withCredentials = true;
1317            xhr.setRequestHeader("Content-Type", "application/json");
1318            xhr.setRequestHeader("Authorization", "Bearer " + idToken);
1319            xhr.setRequestHeader("X-CSRF-TOKEN", "5mNMtSeAK8ErMh3KLEwAOEKBdSzlYZoUWli8a9y1");
1320            xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
1321            xhr.onreadystatechange = function() {
1322                if (xhr.readyState !== 4) return;
1323                clearPersistedRedirect();
1324                try {
1325                    const body = JSON.parse(xhr.responseText || "{}");
1326                    try {
1327                    } catch {}
1328                    if (xhr.status === 200) {
1329                        window.__inkAccountType = '';
1330                        const target = body.redirect || clientRedirect || window.location.href;
1331                        if (target === window.location.href) window.location.reload();
1332                        else window.location.assign(target);
1333                    } else {
1334                        clearAuthProgress();
1335                        showGlobalLoginError(body.message || 'Something went wrong.');
1336                        gtagSafe('event', 'login_error', {
1337                            method: providerLabel,
1338                            flow: 'callback',
1339                            message: 'callback_failed',
1340                            transport_type: 'beacon'
1341                        });
1342                    }
1343                } catch {
1344                    try {
1345                    } catch {}
1346                    clearAuthProgress();
1347                    showGlobalLoginError('Something went wrong.');
1348                    gtagSafe('event', 'login_error', {
1349                        method: providerLabel,
1350                        flow: 'callback',
1351                        message: 'callback_parse_error',
1352                        transport_type: 'beacon'
1353                    });
1354                }
1355            };
1356            let accountType = "";
1357            try {
1358                accountType = window.__inkAccountType || "";
1359            } catch {}
1360            xhr.send(JSON.stringify({
1361                _token: "5mNMtSeAK8ErMh3KLEwAOEKBdSzlYZoUWli8a9y1",
1362                redirect: clientRedirect,
1363                idToken: idToken,
1364                account_type: accountType,
1365                provider: providerLabel
1366            }));
1367        }
1368
1369        window.__inkFinishLogin = finishLogin;
1370
1371        // finalize redirect flows
1372        getRedirectResult(auth).then(async (res) => {
1373            if (res && res.user) {
1374                markAuthProgress();
1375                const providerId = res._tokenResponse?.providerId || res.providerId || res.operationType;
1376                const label = providerId?.includes('apple') ? 'Apple' : (providerId?.includes('google') ?
1377                    'Google' : (providerId?.includes('facebook') ? 'Facebook' : 'Unknown'));
1378                try {
1379                } catch {}
1380                await finishLogin(res.user, label);
1381            }
1382        }).catch(async (err) => {
1383            clearAuthProgress();
1384            if (isAccountExistsError(err)) {
1385                await handleAccountExistsError(err);
1386                return;
1387            }
1388            showGlobalLoginError(formatAuthError(err));
1389            try {
1390            } catch {}
1391        });
1392
1393        // public entry
1394        window.signInWithFirebase = async function(gateway = 1, redirectOverride = '') {
1395            if (window.__inkAuthInFlight) {
1396                return;
1397            }
1398            const hiddenRedirect = redirectOverride || document.querySelector('[data-ink-login-redirect]')?.value || '';
1399            const redirectValue = getRedirectParam() || readPersistedRedirect() || hiddenRedirect;
1400            if (redirectValue) {
1401                persistRedirect(redirectValue);
1402            }
1403            window.__inkAuthInFlight = true;
1404            markAuthProgress();
1405
1406            const isApple = gateway === 2;
1407            const isFacebook = gateway === 3;
1408            const isLoginPage = /\/login\/?$/.test(window.location.pathname || '');
1409            const provider = isApple
1410                ? new OAuthProvider("apple.com")
1411                : (isFacebook ? new FacebookAuthProvider() : new GoogleAuthProvider());
1412            const providerLabel = isApple ? 'Apple' : (isFacebook ? 'Facebook' : 'Google');
1413            window.__inkLastProviderId = isApple ? 'apple.com' : (isFacebook ? 'facebook.com' : 'google.com');
1414            window.__inkLastProviderLabel = providerLabel;
1415            if (isFacebook) {
1416                provider.setCustomParameters({
1417                    scope: 'public_profile,email'
1418                });
1419            }
1420
1421            // Prefer redirect for Facebook except on the dedicated login page.
1422            if (isFacebook && !isLoginPage) {
1423                await signInWithRedirect(auth, provider);
1424                return;
1425            }
1426
1427            // iOS + Apple => redirect (Apple rules)
1428            if (isApple && isIOS) {
1429                await signInWithRedirect(auth, provider);
1430                return;
1431            }
1432
1433            try {
1434                try {
1435                } catch {}
1436                const result = await (isApple ? signInWithAppleDesktopPopup() : signInWithPopup(auth, provider)
1437                    .then(r => r.user));
1438                await finishLogin(result, providerLabel);
1439            } catch (e) {
1440                if (isAccountExistsError(e)) {
1441                    clearAuthProgress();
1442                    await handleAccountExistsError(e);
1443                    try {
1444                    } catch {}
1445                    return;
1446                }
1447                const errorCode = e && e.code ? String(e.code) : '';
1448                if (errorCode === 'auth/cancelled-popup-request') {
1449                    return;
1450                }
1451                if (errorCode === 'auth/popup-blocked') {
1452                    await signInWithRedirect(auth, provider);
1453                    return;
1454                }
1455                try {
1456                } catch {}
1457                gtagSafe('event', 'login_error', {
1458                    method: providerLabel,
1459                    flow: 'popup',
1460                    message: String(e && e.code || e),
1461                    transport_type: 'beacon'
1462                });
1463                showGlobalLoginError(formatAuthError(e));
1464                clearAuthProgress();
1465            }
1466        };
1467    }
1468
1469    // clear progress on page show
1470    (function() {
1471        const clear = () => {
1472            try {
1473                if (sessionStorage.getItem('ink_auth_progress')) {
1474                    sessionStorage.removeItem('ink_auth_progress');
1475                    window.dispatchEvent(new Event('ink-auth-complete'));
1476                }
1477            } catch {}
1478        };
1479        window.addEventListener('pageshow', clear);
1480        document.addEventListener('DOMContentLoaded', () => setTimeout(clear, 0));
1481    })();
1482
1483    window.addEventListener('DOMContentLoaded', () => {
1484        const googleBtns = document.querySelectorAll('[data-ink-login="google"]');
1485        const facebookBtns = document.querySelectorAll('[data-ink-login="facebook"]');
1486        const emailForms = document.querySelectorAll('[data-ink-login-email-form]');
1487
1488        function setLoading(el, on = true) {
1489            if (!el) return;
1490            const idleText = el.getAttribute('data-ink-login-idle') || 'Continue with Google';
1491            const loadingText = el.getAttribute('data-ink-login-loading') || 'Signing in…';
1492            el.disabled = on;
1493            el.classList.toggle('opacity-60', on);
1494            el.classList.toggle('pointer-events-none', on);
1495            el.textContent = on ? loadingText : idleText;
1496        }
1497
1498        googleBtns.forEach((btn) => {
1499            const card = btn.closest('[data-ink-login-card]');
1500            const errorEl = card ? card.querySelector('[data-ink-login-error]') : null;
1501            const redirectInput = card ? card.querySelector('[data-ink-login-redirect]') : null;
1502            const redirectValue = redirectInput ? redirectInput.value : '';
1503
1504            btn.addEventListener('click', async (e) => {
1505                e.preventDefault();
1506                errorEl?.classList.add('hidden');
1507                setLoading(btn, true);
1508                try {
1509                    await window.signInWithFirebase(1, redirectValue); // 1 = Google (your code)
1510                    // If popup fails, your code falls back to redirect, so no more UI work needed here.
1511                } catch (err) {
1512                    // Only hits if something throws before redirect.
1513                    errorEl.textContent = (err && err.message) || 'Something went wrong.';
1514                    errorEl.classList.remove('hidden');
1515                    setLoading(btn, false);
1516                }
1517            });
1518        });
1519
1520        facebookBtns.forEach((btn) => {
1521            const card = btn.closest('[data-ink-login-card]');
1522            const errorEl = card ? card.querySelector('[data-ink-login-error]') : null;
1523            const redirectInput = card ? card.querySelector('[data-ink-login-redirect]') : null;
1524            const redirectValue = redirectInput ? redirectInput.value : '';
1525
1526            btn.addEventListener('click', async (e) => {
1527                e.preventDefault();
1528                errorEl?.classList.add('hidden');
1529                setLoading(btn, true);
1530                try {
1531                    await window.signInWithFirebase(3, redirectValue); // 3 = Facebook
1532                } catch (err) {
1533                    errorEl.textContent = (err && err.message) || 'Something went wrong.';
1534                    errorEl.classList.remove('hidden');
1535                    setLoading(btn, false);
1536                }
1537            });
1538        });
1539
1540        function setEmailLoading(form, on = true) {
1541            const buttons = form.querySelectorAll('button');
1542            buttons.forEach((btn) => {
1543                btn.disabled = on;
1544                btn.classList.toggle('opacity-60', on);
1545                btn.classList.toggle('pointer-events-none', on);
1546            });
1547        }
1548
1549        function showEmailMessage(form, type, message) {
1550            const errorEl = form.querySelector('[data-ink-login-email-error]');
1551            const successEl = form.querySelector('[data-ink-login-email-success]');
1552            if (errorEl) errorEl.classList.add('hidden');
1553            if (successEl) successEl.classList.add('hidden');
1554
1555            const target = type === 'success' ? successEl : errorEl;
1556            if (target) {
1557                target.textContent = message;
1558                target.classList.remove('hidden');
1559            }
1560        }
1561
1562        emailForms.forEach((form) => {
1563            const modeInput = form.querySelector('[data-ink-email-mode]');
1564            const toggleBtn = form.querySelector('[data-ink-email-toggle]');
1565            const submitBtn = form.querySelector('[data-ink-email-action="signin"]');
1566            const submitLabel = form.querySelector('[data-ink-email-action-label]');
1567            const toggleLabel = form.querySelector('[data-ink-email-toggle-label]');
1568            const submitIcon = form.querySelector('[data-ink-email-action-icon]');
1569            const toggleIcon = form.querySelector('[data-ink-email-toggle-icon]');
1570
1571            function syncEmailMode(mode) {
1572                if (modeInput) modeInput.value = mode;
1573                if (submitLabel) {
1574                    submitLabel.textContent = mode === 'signup'
1575                        ? 'Create account'
1576                        : 'Sign in with email';
1577                }
1578                if (toggleLabel) {
1579                    toggleLabel.textContent = mode === 'signup'
1580                        ? 'Already have an account? Sign in'
1581                        : 'Don’t have an account? Create one';
1582                }
1583                if (submitIcon) {
1584                    submitIcon.classList.remove('fa-envelope', 'fa-user-plus');
1585                    submitIcon.classList.add(mode === 'signup' ? 'fa-user-plus' : 'fa-envelope');
1586                }
1587                if (toggleIcon) {
1588                    toggleIcon.classList.remove('fa-user-plus', 'fa-right-to-bracket');
1589                    toggleIcon.classList.add(mode === 'signup' ? 'fa-right-to-bracket' : 'fa-user-plus');
1590                }
1591            }
1592
1593            syncEmailMode(modeInput?.value || 'signin');
1594
1595            toggleBtn?.addEventListener('click', () => {
1596                const current = modeInput?.value || 'signin';
1597                syncEmailMode(current === 'signin' ? 'signup' : 'signin');
1598            });
1599
1600            form.addEventListener('submit', async (e) => {
1601                e.preventDefault();
1602                const auth = window.__inkFirebaseAuth;
1603                if (!auth) return;
1604
1605                const email = (form.querySelector('input[name="email"]')?.value || '').trim();
1606                const password = form.querySelector('input[name="password"]')?.value || '';
1607                const mode = modeInput?.value || 'signin';
1608
1609                showEmailMessage(form, 'error', '');
1610                showEmailMessage(form, 'success', '');
1611                setEmailLoading(form, true);
1612
1613                try {
1614                    let userCred;
1615                    if (mode === 'signup') {
1616                        userCred = await createUserWithEmailAndPassword(auth, email, password);
1617                    } else {
1618                        userCred = await signInWithEmailAndPassword(auth, email, password);
1619                    }
1620
1621                    const user = userCred.user;
1622                    await user.reload();
1623                    if (!user.emailVerified) {
1624                        try {
1625                            await sendEmailVerification(user);
1626                        } catch {}
1627                        await signOut(auth);
1628                        syncEmailMode('signin');
1629                        showEmailMessage(form, 'success',
1630                            'Check your email to verify your account before signing in.');
1631                        return;
1632                    }
1633
1634                    const finisher = window.__inkFinishLogin;
1635                    if (!finisher) {
1636                        throw new Error('login_finish_unavailable');
1637                    }
1638                    await linkPendingCredential(user, 'Email');
1639                    await finisher(user, 'Email');
1640                } catch (err) {
1641                    const code = err && err.code ? String(err.code) : '';
1642                    const rawMessage = err?.message || err?.error?.message || '';
1643                    const normalized = `${code} ${rawMessage}`.toLowerCase();
1644                    const isEmailExists = normalized.includes('email_exists')
1645                        || normalized.includes('email-already-in-use')
1646                        || normalized.includes('email already in use');
1647                    let message = rawMessage || 'Something went wrong.';
1648                    if (isEmailExists) {
1649                        let existingCred = null;
1650                        try {
1651                            existingCred = await signInWithEmailAndPassword(auth, email, password);
1652                        } catch (signinErr) {
1653                            const signinCode = signinErr && signinErr.code ? String(signinErr.code) : '';
1654                            const signinMessage = signinErr?.message || '';
1655                            const signinNormalized = `${signinCode} ${signinMessage}`.toLowerCase();
1656                            const isBadPassword = signinNormalized.includes('wrong-password')
1657                                || signinNormalized.includes('invalid-credential')
1658                                || signinNormalized.includes('invalid-login-credentials')
1659                                || signinNormalized.includes('user-mismatch');
1660                            if (isBadPassword) {
1661                                message = 'Incorrect email or password.';
1662                            } else {
1663                                message = 'Email already in use. Try signing in instead.';
1664                            }
1665                            showEmailMessage(form, 'error', message);
1666                            return;
1667                        }
1668
1669                        const existingUser = existingCred.user;
1670                        await existingUser.reload();
1671                        if (!existingUser.emailVerified) {
1672                            try {
1673                                await sendEmailVerification(existingUser);
1674                            } catch {}
1675                            await signOut(auth);
1676                            syncEmailMode('signin');
1677                            showEmailMessage(form, 'success',
1678                                'Check your email to verify your account before signing in.');
1679                            return;
1680                        }
1681
1682                        try {
1683                            const finisher = window.__inkFinishLogin;
1684                            if (!finisher) {
1685                                throw new Error('login_finish_unavailable');
1686                            }
1687                            if (typeof linkPendingCredential === 'function') {
1688                                await linkPendingCredential(existingUser, 'Email');
1689                            }
1690                            await finisher(existingUser, 'Email');
1691                            return;
1692                        } catch (finishErr) {
1693                            if (typeof clearAuthProgress === 'function') {
1694                                clearAuthProgress();
1695                            } else {
1696                                window.__inkAuthInFlight = false;
1697                                window.dispatchEvent(new Event("ink-auth-complete"));
1698                            }
1699                            if (typeof showGlobalLoginError === 'function') {
1700                                showGlobalLoginError('Something went wrong.');
1701                            } else {
1702                                showEmailMessage(form, 'error', 'Something went wrong.');
1703                            }
1704                            return;
1705                        }
1706                    } else if (code.includes('auth/user-not-found')) {
1707                        message = 'No account found for that email.';
1708                    } else if (code.includes('auth/wrong-password') || code.includes('auth/invalid-credential')) {
1709                        message = 'Incorrect email or password.';
1710                    } else if (code.includes('auth/weak-password')) {
1711                        message = 'Password is too weak.';
1712                    } else if (code.includes('auth/invalid-email')) {
1713                        message = 'Enter a valid email address.';
1714                    }
1715                    showEmailMessage(form, 'error', message);
1716                } finally {
1717                    setEmailLoading(form, false);
1718                }
1719            });
1720        });
1721    });
1722</script>
1722
1723
1724    
1724<script>
1725        function inkAccountStepper(initialType, skipType) {
1726            return {
1727                step: skipType || initialType ? 2 : 1,
1728                selected: initialType || '',
1729                skipType: !!skipType,
1730                init() {
1731                    if (initialType) {
1732                        this.selected = initialType;
1733                        this.step = 2;
1734                        window.__inkAccountType = initialType;
1735                        return;
1736                    }
1737                    if (skipType) {
1738                        this.step = 2;
1739                        this.selected = '';
1740                        window.__inkAccountType = '';
1741                    }
1742                },
1743                applyType(value) {
1744                    if (!value) {
1745                        if (!initialType) {
1746                            this.selected = '';
1747                            this.step = skipType ? 2 : 1;
1748                            window.__inkAccountType = '';
1749                        }
1750                        return;
1751                    }
1752                    this.selected = value;
1753                    this.step = 2;
1754                    window.__inkAccountType = value;
1755                },
1756                select(value) {
1757                    this.selected = value;
1758                    window.__inkAccountType = value;
1759                },
1760                onClose() {
1761                    this.step = (skipType || initialType) ? 2 : 1;
1762                    this.selected = initialType || '';
1763                    if (initialType) {
1764                        window.__inkAccountType = initialType;
1765                    } else {
1766                        window.__inkAccountType = '';
1767                    }
1768                },
1769                selectedLabel() {
1770                    if (!this.selected) {
1771                        return '';
1772                    }
1773                    const labels = {
1774                        artist: 'Signing up as Artist',
1775                        client: 'Signing up as Client',
1776                        studio: 'Signing up as Studio',
1777                    };
1778                    return labels[this.selected] || '';
1779                },
1780            };
1781        }
1782    </script>
1782
1783    
1784    
1785    
1786    </body>
1787
1788</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.