feat: ship creator journey v2 and profile updates

This commit is contained in:
2026-04-12 21:42:07 +02:00
parent a2457f4e49
commit d5cff21ea2
335 changed files with 20147 additions and 1545 deletions

View File

@@ -1,6 +1,8 @@
@php
$gridVersion = request()->query('grid') === 'v2' ? 'v2' : 'v1';
$deferToolbarSearch = request()->routeIs('index');
$deferFontAwesome = request()->routeIs('index');
$deferWebManifest = request()->routeIs('index');
$isInertiaPage = isset($page) && is_array($page);
$shouldRenderBladeSeo = ($useUnifiedSeo ?? false) && (($renderBladeSeo ?? false) || ! $isInertiaPage);
$novaViteEntries = [
@@ -27,60 +29,148 @@
{{-- Global RSS feed discovery --}}
<link rel="alternate" type="application/rss+xml" title="Skinbase Latest Artworks" href="{{ url('/rss') }}">
<!-- Icons (kept for now to preserve current visual output) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
<!-- Icons: keep CDN delivery, but keep homepage webfonts out of the initial critical path -->
@if(!$deferFontAwesome)
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
<link rel="dns-prefetch" href="//cdnjs.cloudflare.com">
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" as="style" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" media="print" onload="this.media='all'" crossorigin>
<noscript>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" crossorigin>
</noscript>
@endif
<link rel="icon" type="image/png" href="/favicon/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon/favicon.svg" />
<link rel="shortcut icon" href="/favicon/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
<link rel="manifest" href="/favicon/site.webmanifest" />
@if(!$deferWebManifest)
<link rel="manifest" href="/favicon/site.webmanifest" />
@endif
@vite($novaViteEntries)
<script>
window.SKINBASE_LIMITS = Object.assign({}, window.SKINBASE_LIMITS || {}, {
tags: Object.assign({}, (window.SKINBASE_LIMITS && window.SKINBASE_LIMITS.tags) || {}, {
max_user_tags: @json((int) config('tags.max_user_tags', 30)),
}),
});
</script>
@stack('head')
@if($deferToolbarSearch)
<script type="module">
(() => {
const searchEntryUrl = @js(Vite::asset('resources/js/entry-search.jsx'));
const triggerEvents = ['pointerdown', 'touchstart', 'focusin'];
let searchLoaded = false;
const loadSearch = () => {
const loadSearch = (intent = null) => {
if (searchLoaded) {
return;
}
if (intent) {
window.__sbSearchIntent = intent;
}
searchLoaded = true;
cleanup();
import(searchEntryUrl);
};
const resolveIntent = (eventTarget) => {
return eventTarget?.closest?.('[data-search-intent]')?.getAttribute('data-search-intent') || null;
};
const handlePointerEnter = () => {
loadSearch();
};
const handleActivate = (event) => {
const intent = resolveIntent(event.target);
loadSearch(intent);
};
const handleShortcut = (event) => {
if (!((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k')) {
return;
}
event.preventDefault();
loadSearch(window.matchMedia('(max-width: 767px)').matches ? 'mobile' : 'desktop');
};
const onReady = () => {
const searchRoot = document.getElementById('topbar-search-root');
if (!searchRoot) {
return;
}
triggerEvents.forEach((eventName) => {
searchRoot.addEventListener(eventName, loadSearch, { once: true, passive: true });
});
if ('requestIdleCallback' in window) {
window.requestIdleCallback(loadSearch, { timeout: 2000 });
} else {
window.setTimeout(loadSearch, 1500);
}
searchRoot.addEventListener('pointerenter', handlePointerEnter, { once: true, passive: true });
searchRoot.addEventListener('click', handleActivate, { passive: true });
searchRoot.addEventListener('touchstart', handleActivate, { passive: true });
document.addEventListener('keydown', handleShortcut);
};
const cleanup = () => {
const searchRoot = document.getElementById('topbar-search-root');
if (!searchRoot) {
if (searchRoot) {
searchRoot.removeEventListener('pointerenter', handlePointerEnter);
searchRoot.removeEventListener('click', handleActivate);
searchRoot.removeEventListener('touchstart', handleActivate);
}
document.removeEventListener('keydown', handleShortcut);
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', onReady, { once: true });
} else {
onReady();
}
})();
</script>
@endif
@if($deferFontAwesome)
<script>
(() => {
const href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css';
const linkId = 'deferred-font-awesome';
let loaded = false;
const loadFontAwesome = () => {
if (loaded || document.getElementById(linkId)) {
return;
}
triggerEvents.forEach((eventName) => {
searchRoot.removeEventListener(eventName, loadSearch);
});
loaded = true;
cleanup();
const link = document.createElement('link');
link.id = linkId;
link.rel = 'stylesheet';
link.href = href;
link.crossOrigin = 'anonymous';
document.head.appendChild(link);
};
const onReady = () => {
const toolbar = document.getElementById('nova-toolbar');
if (toolbar) {
toolbar.addEventListener('pointerenter', loadFontAwesome, { once: true, passive: true });
toolbar.addEventListener('focusin', loadFontAwesome, { once: true, passive: true });
toolbar.addEventListener('pointerdown', loadFontAwesome, { once: true, passive: true });
}
};
const cleanup = () => {
const toolbar = document.getElementById('nova-toolbar');
if (toolbar) {
toolbar.removeEventListener('pointerenter', loadFontAwesome);
toolbar.removeEventListener('focusin', loadFontAwesome);
toolbar.removeEventListener('pointerdown', loadFontAwesome);
}
};
if (document.readyState === 'loading') {