Restore toolbar background to bg-nebula; add toolbar backdrop blur

This commit is contained in:
2026-02-15 09:24:43 +01:00
parent 79192345e3
commit 9dbe848412
28 changed files with 736 additions and 110 deletions

View File

@@ -7,13 +7,13 @@
}
.form-input {
@apply w-full bg-deep border border-nebula-500/30 rounded-lg px-4 py-2
@apply w-full bg-deep border border-nova-500/30 rounded-lg px-4 py-2
text-white placeholder-gray-500
focus:outline-none focus:ring-2 focus:ring-accent;
}
.form-textarea {
@apply w-full bg-deep border border-nebula-500/30 rounded-lg px-4 py-2
@apply w-full bg-deep border border-nova-500/30 rounded-lg px-4 py-2
text-white resize-none
focus:outline-none focus:ring-2 focus:ring-accent;
}
@@ -22,7 +22,7 @@
@apply w-full text-sm text-soft
file:bg-panel file:border-0 file:px-4 file:py-2
file:rounded-lg file:text-white
hover:file:bg-nebula-600/40;
hover:file:bg-nova-600/40;
}
.btn-primary {
@@ -31,8 +31,8 @@
}
.btn-secondary {
@apply bg-nebula-500/30 text-white px-5 py-2 rounded-lg
hover:bg-nebula-500/50 transition;
@apply bg-nova-500/30 text-white px-5 py-2 rounded-lg
hover:bg-nova-500/50 transition;
}
@layer components {
@@ -44,7 +44,7 @@
input[type="password"],
textarea,
select {
@apply bg-deep text-white border border-nebula-500/30 rounded-lg px-4 py-2
@apply bg-deep text-white border border-nova-500/30 rounded-lg px-4 py-2
placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-accent;
}

View File

@@ -5,7 +5,7 @@ export default function UploadStepper({ steps = [], activeStep = 1, highestUnloc
return (
<nav aria-label="Upload steps" className="rounded-xl border border-white/50 bg-slate-900/70 px-3 py-3 sm:px-4">
<ol className="flex flex-nowrap items-center gap-2 overflow-x-auto sm:gap-3">
<ol className="flex flex-nowrap items-center gap-3 overflow-x-auto sm:gap-4">
{steps.map((step, index) => {
const number = index + 1
const isActive = number === safeActive
@@ -29,21 +29,21 @@ export default function UploadStepper({ steps = [], activeStep = 1, highestUnloc
: 'border-white/20 bg-white/5 text-white/80'
return (
<li key={step.key} className="flex min-w-0 items-center gap-2">
<li key={step.key} className="flex-shrink-0 flex items-center gap-3">
<button
type="button"
onClick={() => canNavigate && onStepClick?.(number)}
disabled={isLocked}
aria-disabled={isLocked ? 'true' : 'false'}
aria-current={isActive ? 'step' : undefined}
className={`${baseBtn} ${stateClass}`}
className={`${baseBtn} ${stateClass} flex-shrink-0`}
>
<span className={`grid h-5 w-5 place-items-center rounded-full border text-[11px] ${circleClass}`}>
{isComplete ? '✓' : number}
</span>
<span className="whitespace-nowrap">{step.label}</span>
<span className="whitespace-nowrap pr-3">{step.label}</span>
</button>
{index < steps.length - 1 && <span className="text-white/50"></span>}
{index < steps.length - 1 && <span className="text-white/50 mx-1 select-none"></span>}
</li>
)
})}

View File

@@ -354,6 +354,17 @@ export default function UploadWizard({
rightsAccepted: false,
contentType: '',
})
const [visionSuggestedTags, setVisionSuggestedTags] = useState([])
const [visionDebug, setVisionDebug] = useState({
enabled: null,
queueConnection: '',
queuedJobs: 0,
failedJobs: 0,
triggered: false,
aiTagCount: 0,
totalTagCount: 0,
lastError: '',
})
const [isUploadLocked, setIsUploadLocked] = useState(false)
const [resolvedArtworkId, setResolvedArtworkId] = useState(() => {
const parsed = Number(initialDraftId)
@@ -371,6 +382,7 @@ export default function UploadWizard({
const requestControllersRef = useRef(new Set())
const publishLockRef = useRef(false)
const hasAutoAdvancedRef = useRef(false)
const lastVisionFetchAtRef = useRef(0)
const effectiveChunkSize = useMemo(() => {
const parsed = Number(chunkSize)
@@ -422,6 +434,32 @@ export default function UploadWizard({
}, [metadata, requiresSubCategory])
const detailsValid = Object.keys(metadataErrors).length === 0
const mergedSuggestedTags = useMemo(() => {
const normalized = new Map()
const addTag = (item) => {
if (!item) return
const key = String(item?.slug || item?.tag || item?.name || item).trim().toLowerCase()
if (!key) return
if (!normalized.has(key)) {
if (typeof item === 'string') {
normalized.set(key, item)
} else {
normalized.set(key, {
id: item.id ?? key,
name: item.name || item.tag || item.slug || key,
slug: item.slug || item.tag || key,
usage_count: Number(item.usage_count || 0),
is_ai: Boolean(item.is_ai || item.source === 'ai'),
source: item.source || (item.is_ai ? 'ai' : 'manual'),
})
}
}
}
;(Array.isArray(suggestedTags) ? suggestedTags : []).forEach(addTag)
;(Array.isArray(visionSuggestedTags) ? visionSuggestedTags : []).forEach(addTag)
return Array.from(normalized.values())
}, [suggestedTags, visionSuggestedTags])
const highestUnlockedStep = uploadReady ? (detailsValid ? 3 : 2) : 1
const showProgress = machine.state !== machineStates.idle && machine.state !== machineStates.cancelled
const canStartUpload = isValidForUpload(primaryFile, primaryErrors, isArchive, screenshotErrors)
@@ -697,6 +735,68 @@ export default function UploadWizard({
}
}, [machine.sessionId, machine.uploadToken, fetchProcessingStatus, registerController, unregisterController])
const fetchVisionSuggestedTags = useCallback(async () => {
if (!resolvedArtworkId || resolvedArtworkId <= 0) return
const now = Date.now()
if (now - lastVisionFetchAtRef.current < 3000) return
lastVisionFetchAtRef.current = now
try {
const response = await window.axios.get(`/api/artworks/${resolvedArtworkId}/tags`, {
params: { trigger: 1 },
})
const payload = response?.data || {}
if (payload?.vision_enabled === false) {
setVisionSuggestedTags([])
setVisionDebug((current) => ({
...current,
enabled: false,
lastError: '',
}))
return
}
const aiTags = Array.isArray(payload?.ai_tags) ? payload.ai_tags : []
setVisionSuggestedTags(aiTags)
const debug = payload?.debug || {}
setVisionDebug({
enabled: Boolean(payload?.vision_enabled),
queueConnection: String(debug?.queue_connection || ''),
queuedJobs: Number(debug?.queued_jobs || 0),
failedJobs: Number(debug?.failed_jobs || 0),
triggered: Boolean(debug?.triggered),
aiTagCount: Number(debug?.ai_tag_count || aiTags.length || 0),
totalTagCount: Number(debug?.total_tag_count || 0),
lastError: '',
})
if (typeof window !== 'undefined') {
window.console?.debug?.('[upload][vision-tags]', {
artworkId: resolvedArtworkId,
aiTags: aiTags.map((tag) => tag?.slug || tag?.name || tag),
debug,
})
}
} catch (error) {
if (error?.response?.status === 404 || error?.response?.status === 403) return
setVisionDebug((current) => ({
...current,
lastError: error?.response?.data?.message || error?.message || 'Vision tag fetch failed.',
}))
}
}, [resolvedArtworkId])
useEffect(() => {
if (!resolvedArtworkId || activeStep < 2) return
fetchVisionSuggestedTags()
const timer = window.setInterval(() => {
fetchVisionSuggestedTags()
}, 4000)
return () => window.clearInterval(timer)
}, [resolvedArtworkId, activeStep, fetchVisionSuggestedTags])
useEffect(() => {
if (machine.state !== machineStates.processing) {
clearPolling()
@@ -745,12 +845,21 @@ export default function UploadWizard({
dispatchMachine({ type: 'PUBLISH_START' })
try {
const publishTargetId = machine.sessionId || initialDraftId || resolvedArtworkId
const publishTargetId = resolvedArtworkId || initialDraftId || machine.sessionId
const publishPayload = {
title: String(metadata.title || '').trim() || undefined,
description: String(metadata.description || '').trim() || null,
}
if (!machine.sessionId) {
if (!publishTargetId) throw new Error('Missing publish id.')
const publishController = registerController()
await window.axios.post(uploadEndpoints.publish(publishTargetId), {}, { signal: publishController.signal })
await window.axios.post(uploadEndpoints.publish(publishTargetId), publishPayload, { signal: publishController.signal })
unregisterController(publishController)
} else {
if (!publishTargetId) throw new Error('Missing publish id.')
const publishController = registerController()
await window.axios.post(uploadEndpoints.publish(publishTargetId), publishPayload, { signal: publishController.signal })
unregisterController(publishController)
}
@@ -764,7 +873,7 @@ export default function UploadWizard({
} finally {
publishLockRef.current = false
}
}, [canPublish, machine.sessionId, initialDraftId, resolvedArtworkId, registerController, unregisterController])
}, [canPublish, machine.sessionId, initialDraftId, resolvedArtworkId, metadata.title, metadata.description, registerController, unregisterController])
const handleReset = useCallback(() => {
clearPolling()
@@ -780,6 +889,7 @@ export default function UploadWizard({
rightsAccepted: false,
contentType: '',
})
setVisionSuggestedTags([])
setResolvedArtworkId(() => {
const parsed = Number(initialDraftId)
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : null
@@ -1059,13 +1169,26 @@ export default function UploadWizard({
<UploadSidebar
showHeader={false}
metadata={metadata}
suggestedTags={suggestedTags}
suggestedTags={mergedSuggestedTags}
errors={metadataErrors}
onChangeTitle={(value) => setMetadata((current) => ({ ...current, title: value }))}
onChangeTags={(value) => setMetadata((current) => ({ ...current, tags: value }))}
onChangeDescription={(value) => setMetadata((current) => ({ ...current, description: value }))}
onToggleRights={(value) => setMetadata((current) => ({ ...current, rightsAccepted: Boolean(value) }))}
/>
{resolvedArtworkId ? (
<div className="rounded-xl border border-white/10 bg-white/[0.03] p-3 text-xs text-white/70">
<div className="font-medium text-white/85">Vision debug</div>
<div className="mt-1">
enabled: {String(visionDebug.enabled)} · queue: {visionDebug.queueConnection || 'n/a'} · ai tags: {visionDebug.aiTagCount} · total tags: {visionDebug.totalTagCount}
</div>
<div className="mt-1">
queued jobs: {visionDebug.queuedJobs} · failed jobs: {visionDebug.failedJobs} · trigger sent: {String(visionDebug.triggered)}
</div>
{visionDebug.lastError ? <div className="mt-1 text-red-300">error: {visionDebug.lastError}</div> : null}
</div>
) : null}
</div>
</div>
)

View File

@@ -3,14 +3,14 @@
* The Nova layout is styled primarily via Tailwind utilities from resources/css/app.css.
*/
/* Hero radial background moved from inline styles to a class to respect Nebula rules */
/* Hero radial background moved from inline styles to a class to respect Nova rules */
.nb-hero-radial {
background-image: radial-gradient(circle at 20% 10%, rgba(77,163,255,.25), transparent 35%),
radial-gradient(circle at 70% 30%, rgba(255,196,77,.18), transparent 40%),
radial-gradient(circle at 30% 80%, rgba(180,77,255,.16), transparent 45%);
}
/* Nebula design tokens and helper classes copied from nova.html preview
/* Nova design tokens and helper classes copied from nova.html preview
These provide the same color tokens and small utilities used by the preview
so `blank` renders consistently until Tailwind config is consolidated. */
:root {
@@ -23,16 +23,16 @@
--sb-muted: #a6a6b0;
--sb-blue: #4da3ff;
/* Primary UI color tokens (Nebula palette) */
--nebula-blue: #09101acc;
/* Primary UI color tokens (Nova palette) */
--nova-blue: #09101acc;
--deep-space: #0F1724;
--panel-dark: #151E2E;
--soft-blue: #7A8CA5;
--accent-orange: #E07A21;
/* Toolbar color (Skinbase Nebula) */
/* Toolbar color (Skinbase Nova) */
--toolbar-bg: #0F1724;
/* RGB variants for subtle overlays */
--nebula-blue-rgb: 100,111,131;
--nova-blue-rgb: 100,111,131;
--deep-space-rgb: 15,23,36;
--panel-dark-rgb: 21,30,46;
--toolbar-bg-rgb: 15,23,36;
@@ -58,7 +58,7 @@
/* Ensure header and dropdowns are not clipped and render above page content */
header {
overflow: visible;
/* Use the official toolbar background token from the Nebula spec */
/* Use the official toolbar background token from the Nova spec */
background-color: var(--toolbar-bg);
/* subtle divider to separate toolbar from content */
border-bottom: 1px solid rgba(255,255,255,0.02);
@@ -72,8 +72,8 @@ header {
border: 1px solid rgba(255,255,255,0.03);
}
/* Convenience helpers for the new nebula tokens */
.bg-nebula { background-color: var(--nebula-blue) !important; }
/* Convenience helpers for the new nova tokens */
.bg-nova { background-color: var(--nova-blue) !important; -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(2px); }
.bg-deep { background-color: var(--deep-space) !important; }
.bg-panel-dark { background-color: var(--panel-dark) !important; }
.text-soft { color: var(--soft-blue) !important; }

View File

@@ -34,7 +34,7 @@
<div class="flex min-h-[calc(100vh-64px)]">
<!-- SIDEBAR -->
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nebula-900/60 backdrop-blur-sm">
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nova-900/60 backdrop-blur-sm">
<div class="p-4">
<button class="w-full h-12 rounded-xl bg-white/5 hover:bg-white/7 border border-white/5 flex items-center gap-3 px-4">
<span class="w-8 h-8 rounded-lg bg-white/5 inline-flex items-center justify-center">

View File

@@ -6,7 +6,7 @@
<div class="mx-auto w-full">
<div class="flex min-h-[calc(100vh-64px)]">
<!-- SIDEBAR -->
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nebula-900/60 backdrop-blur-sm">
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nova-900/60 backdrop-blur-sm">
<div class="p-4">
<button class="w-full h-12 rounded-xl bg-white/5 hover:bg-white/7 border border-white/5 flex items-center gap-3 px-4">
<span class="w-8 h-8 rounded-lg bg-white/5 inline-flex items-center justify-center">
@@ -69,7 +69,7 @@
<section class="mt-5 bg-white/5 border border-white/10 rounded-2xl shadow-lg">
<div class="p-5 md:p-6">
<div class="text-lg font-semibold text-white/90">Fantasy</div>
<p class="mt-2 text-sm leading-6 text-neutral-400">A small preview of the Nebula layout, server-rendered for SEO and progressive enhancement.</p>
<p class="mt-2 text-sm leading-6 text-neutral-400">A small preview of the Nova layout, server-rendered for SEO and progressive enhancement.</p>
</div>
</section>
</div>
@@ -114,21 +114,21 @@
</div>
</section>
<!-- Nebula color scale examples -->
<!-- Nova color scale examples -->
<section class="px-6 pb-10 md:px-10 mt-8">
<h2 class="text-lg font-semibold mb-4">Nebula color scale</h2>
<h2 class="text-lg font-semibold mb-4">Nova color scale</h2>
<div class="grid grid-cols-2 sm:grid-cols-5 md:grid-cols-10 gap-3">
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nebula-50 text-black">nebula-50</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nebula-100 text-black">nebula-100</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nebula-200 text-black">nebula-200</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nebula-300 text-black">nebula-300</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nebula-400 text-black">nebula-400</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nebula-500 text-white">nebula-500</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nebula-600 text-white">nebula-600</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nebula-700 text-white">nebula-700</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nebula-800 text-white">nebula-800</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nebula-900 text-white">nebula-900</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nova-50 text-black">nova-50</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nova-100 text-black">nova-100</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nova-200 text-black">nova-200</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nova-300 text-black">nova-300</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nova-400 text-black">nova-400</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nova-500 text-white">nova-500</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nova-600 text-white">nova-600</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nova-700 text-white">nova-700</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nova-800 text-white">nova-800</div>
<div class="h-20 rounded-md flex items-center justify-center text-sm font-medium border border-white/5 bg-nova-900 text-white">nova-900</div>
</div>
</section>

View File

@@ -14,8 +14,8 @@
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="font-sans antialiased">
<div class="min-h-screen bg-gray-100">
<body class="font-sans antialiased bg-nova-800">
<div class="min-h-screen">
@include('layouts.navigation')
<!-- Page Heading -->

View File

@@ -15,12 +15,12 @@
@vite(['resources/css/app.css','resources/scss/nova.scss','resources/js/nova.js'])
@stack('head')
</head>
<body class="bg-nebula-900 text-white min-h-screen">
<body class="bg-nova-800 text-white min-h-screen flex flex-col">
<!-- React Topbar mount point -->
<div id="topbar-root"></div>
@include('layouts.nova.toolbar')
<main class="pt-16">
<main class="flex-1 pt-16">
@yield('content')
</main>
@@ -35,7 +35,7 @@
@if($toastMessage)
<div x-data="{show:true}" x-show="show" x-init="setTimeout(()=>show=false,4000)" x-cloak
class="fixed right-4 bottom-6 z-50">
<div class="max-w-sm w-full rounded-lg shadow-lg overflow-hidden bg-nebula-600 border {{ $toastBorder }}">
<div class="max-w-sm w-full rounded-lg shadow-lg overflow-hidden bg-nova-600 border {{ $toastBorder }}">
<div class="px-4 py-3 flex items-start gap-3">
<div class="flex-shrink-0">
@if(session('error'))

View File

@@ -1,5 +1,5 @@
<!-- Footer -->
<footer class="border-t border-neutral-800 bg-nebula">
<footer class="border-t border-neutral-800 bg-nova">
<div class="px-6 md:px-10 py-8 flex flex-col md:flex-row md:items-center md:justify-between gap-2">
<div class="text-xl font-semibold tracking-wide flex items-center gap-1">
<img src="/gfx/skinbase_logo.png" alt="Skinbase" class="h-16 w-auto object-contain">
@@ -17,4 +17,4 @@
<div class="text-xs text-neutral-400">© 2026 Skinbase.org</div>
</div>
</footer>
</footer>

View File

@@ -1,4 +1,4 @@
<header class="fixed inset-x-0 top-0 z-50 h-16 bg-nebula border-b border-panel">
<header class="fixed inset-x-0 top-0 z-50 h-16 bg-nova border-b border-panel">
<div class="mx-auto w-full h-full px-4 flex items-center gap-3">
<!-- Mobile hamburger -->
<button id="btnSidebar" class="md:hidden inline-flex items-center justify-center w-10 h-10 rounded-lg hover:bg-white/5">
@@ -210,4 +210,4 @@
<a class="block py-2 border-b border-neutral-900" href="/profile">Profile</a>
<a class="block py-2" href="/settings">Settings</a>
</div>
</div>
</div>

View File

@@ -13,7 +13,7 @@
<div class="flex min-h-[calc(100vh-64px)]">
<!-- SIDEBAR -->
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nebula-900/60 backdrop-blur-sm">
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nova-900/60 backdrop-blur-sm">
<div class="p-4">
<button class="w-full h-12 rounded-xl bg-white/5 hover:bg-white/7 border border-white/5 flex items-center gap-3 px-4">
<span class="w-8 h-8 rounded-lg bg-white/5 inline-flex items-center justify-center">

View File

@@ -13,7 +13,7 @@
<div class="flex min-h-[calc(100vh-64px)]">
<!-- SIDEBAR -->
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nebula-900/60 backdrop-blur-sm">
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nova-900/60 backdrop-blur-sm">
<div class="p-4">
<button class="w-full h-12 rounded-xl bg-white/5 hover:bg-white/7 border border-white/5 flex items-center gap-3 px-4">
<span class="w-8 h-8 rounded-lg bg-white/5 inline-flex items-center justify-center">

View File

@@ -21,7 +21,7 @@
<div class="mx-auto w-full">
<div class="flex min-h-[calc(100vh-64px)]">
<!-- SIDEBAR -->
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nebula-900/60 backdrop-blur-sm">
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nova-900/60 backdrop-blur-sm">
<div class="p-4">
<button class="w-full h-12 rounded-xl bg-white/5 hover:bg-white/7 border border-white/5 flex items-center gap-3 px-4">
<span class="w-8 h-8 rounded-lg bg-white/5 inline-flex items-center justify-center">
@@ -72,7 +72,7 @@
<!-- MAIN -->
<main class="flex-1">
<div class="nebula-gallery grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
<div class="nova-gallery grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
@foreach($artworks as $art)
@include('legacy._artwork_card', ['art' => $art])
@endforeach
@@ -90,30 +90,30 @@
@push('styles')
<style>
/* Nebula-like gallery tweaks: fixed-height thumbnails, tighter spacing, refined typography */
.nebula-gallery {
/* Nova-like gallery tweaks: fixed-height thumbnails, tighter spacing, refined typography */
.nova-gallery {
margin-top: 1.25rem;
}
.nebula-gallery .artwork a { display: block; }
.nova-gallery .artwork a { display: block; }
/* Ensure consistent gap and card sizing across breakpoints */
@media (min-width: 1024px) {
.nebula-gallery { gap: 1rem; }
.nova-gallery { gap: 1rem; }
}
/* Typography refinements to match Nebula */
.nebula-gallery .artwork h3 { font-size: 0.95rem; line-height: 1.15; }
.nebula-gallery .artwork .text-xs { font-size: 0.72rem; }
/* Typography refinements to match Nova */
.nova-gallery .artwork h3 { font-size: 0.95rem; line-height: 1.15; }
.nova-gallery .artwork .text-xs { font-size: 0.72rem; }
/* Improve image loading artifact handling */
.nebula-gallery img { background: linear-gradient(180deg,#0b0b0b,#0f0f10); }
.nova-gallery img { background: linear-gradient(180deg,#0b0b0b,#0f0f10); }
/* Remove any default margins on article cards that can create vertical gaps */
.nebula-gallery .artwork { margin: 0; }
.nova-gallery .artwork { margin: 0; }
/* Ensure grid items don't collapse when overlay hidden */
.nebula-gallery .artwork a { min-height: 0; }
.nova-gallery .artwork a { min-height: 0; }
</style>
@endpush

View File

@@ -1,15 +1,8 @@
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
<title>{{ $page_title ?? 'Upload Artwork' }}</title>
@extends('layouts.nova')
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@push('head')
<meta name="csrf-token" content="{{ csrf_token() }}" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
<link rel="shortcut icon" href="/favicon.ico">
<script>
window.SKINBASE_FLAGS = Object.assign({}, window.SKINBASE_FLAGS || {}, {
uploads_v2: @json((bool) config('features.uploads_v2', false)),
@@ -19,16 +12,28 @@
});
</script>
@vite(['resources/css/app.css','resources/scss/nova.scss','resources/js/entry-topbar.jsx','resources/js/upload.jsx'])
</head>
<body class="bg-nebula-900 text-white min-h-screen">
<div id="topbar-root"></div>
@include('layouts.nova.toolbar')
@vite(['resources/js/entry-topbar.jsx','resources/js/upload.jsx'])
<style>
/* Upload page spacing: extra top padding and bottom space so sticky action bar won't overlap content */
body.page-upload main {
padding-top: 6rem; /* slightly larger top padding on upload */
padding-bottom: 6.5rem; /* room for sticky action bar */
}
<main class="pt-16">
@inertia
</main>
/* Ensure the footer is visually separated on short pages */
body.page-upload footer {
margin-top: 1rem;
}
</style>
@include('layouts.nova.footer')
</body>
</html>
<script>
// Mark document to apply upload-specific spacing
document.addEventListener('DOMContentLoaded', function () {
document.body.classList.add('page-upload')
})
</script>
@endpush
@section('content')
@inertia
@endsection