feat: Inertia profile settings page, Studio edit redesign, EGS, Nova UI components\n\n- Redesign /dashboard/profile as Inertia React page (Settings/ProfileEdit)\n with SettingsLayout sidebar, Nova UI components (TextInput, Textarea,\n Toggle, Select, RadioGroup, Modal, Button), avatar drag-and-drop,\n password change, and account deletion sections\n- Redesign Studio artwork edit page with two-column layout, Nova components,\n integrated TagPicker, and version history modal\n- Add shared MarkdownEditor component\n- Add Early-Stage Growth System (EGS): SpotlightEngine, FeedBlender,\n GridFiller, AdaptiveTimeWindow, ActivityLayer, admin panel\n- Fix upload category/tag persistence (V1+V2 paths)\n- Fix tag source enum, category tree display, binding resolution\n- Add settings.jsx Vite entry, settings.blade.php wrapper\n- Update ProfileController with JSON response support for API calls\n- Various route fixes (profile.edit, toolbar settings link)"
This commit is contained in:
156
resources/views/admin/early-growth/index.blade.php
Normal file
156
resources/views/admin/early-growth/index.blade.php
Normal file
@@ -0,0 +1,156 @@
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@section('page-content')
|
||||
<div class="max-w-4xl space-y-8">
|
||||
|
||||
{{-- ── Header ── --}}
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-white">Early-Stage Growth System</h2>
|
||||
<p class="mt-1 text-sm text-neutral-400">
|
||||
A non-deceptive layer that keeps Nova feeling alive when uploads are sparse.
|
||||
Toggle via <code class="text-sky-400">.env</code> — no deployment required for mode changes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{-- Cache flush button --}}
|
||||
<form method="POST" action="{{ route('admin.early-growth.cache.flush') }}" onsubmit="return confirm('Flush all EGS caches?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit"
|
||||
class="inline-flex items-center gap-2 rounded-lg bg-neutral-800 px-4 py-2 text-sm font-medium text-white
|
||||
hover:bg-neutral-700 border border-neutral-700 transition">
|
||||
🔄 Flush EGS Cache
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="rounded-lg bg-green-900/40 border border-green-700 px-4 py-3 text-green-300 text-sm">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- ── Live Status ── --}}
|
||||
<div class="rounded-lg border border-neutral-800 bg-neutral-900 p-6 space-y-4">
|
||||
<h3 class="text-sm font-semibold uppercase tracking-widest text-neutral-400">Live Status</h3>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3">
|
||||
@php
|
||||
$pill = fn(bool $on) => $on
|
||||
? '<span class="inline-block rounded-full bg-emerald-800 px-3 py-0.5 text-xs font-semibold text-emerald-200">ON</span>'
|
||||
: '<span class="inline-block rounded-full bg-neutral-700 px-3 py-0.5 text-xs font-semibold text-neutral-400">OFF</span>';
|
||||
@endphp
|
||||
|
||||
<div class="rounded-lg bg-neutral-800/50 p-4">
|
||||
<p class="text-xs text-neutral-500 mb-1">System</p>
|
||||
{!! $pill($status['enabled']) !!}
|
||||
</div>
|
||||
<div class="rounded-lg bg-neutral-800/50 p-4">
|
||||
<p class="text-xs text-neutral-500 mb-1">Mode</p>
|
||||
<span class="text-sm font-mono font-semibold {{ $mode === 'aggressive' ? 'text-amber-400' : ($mode === 'light' ? 'text-sky-400' : 'text-neutral-400') }}">
|
||||
{{ strtoupper($mode) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="rounded-lg bg-neutral-800/50 p-4">
|
||||
<p class="text-xs text-neutral-500 mb-1">Adaptive Window</p>
|
||||
{!! $pill($status['adaptive_window']) !!}
|
||||
</div>
|
||||
<div class="rounded-lg bg-neutral-800/50 p-4">
|
||||
<p class="text-xs text-neutral-500 mb-1">Grid Filler</p>
|
||||
{!! $pill($status['grid_filler']) !!}
|
||||
</div>
|
||||
<div class="rounded-lg bg-neutral-800/50 p-4">
|
||||
<p class="text-xs text-neutral-500 mb-1">Spotlight</p>
|
||||
{!! $pill($status['spotlight']) !!}
|
||||
</div>
|
||||
<div class="rounded-lg bg-neutral-800/50 p-4">
|
||||
<p class="text-xs text-neutral-500 mb-1">Activity Layer</p>
|
||||
{!! $pill($status['activity_layer']) !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Upload Stats ── --}}
|
||||
<div class="rounded-lg border border-neutral-800 bg-neutral-900 p-6 space-y-4">
|
||||
<h3 class="text-sm font-semibold uppercase tracking-widest text-neutral-400">Upload Metrics</h3>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="rounded-lg bg-neutral-800/50 p-4">
|
||||
<p class="text-xs text-neutral-500 mb-1">Uploads / day (7-day avg)</p>
|
||||
<p class="text-2xl font-bold text-white">{{ number_format($uploads_per_day, 1) }}</p>
|
||||
</div>
|
||||
<div class="rounded-lg bg-neutral-800/50 p-4">
|
||||
<p class="text-xs text-neutral-500 mb-1">Active trending window</p>
|
||||
<p class="text-2xl font-bold text-white">{{ $window_days }}d</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Activity Signals ── --}}
|
||||
@if($status['activity_layer'] && !empty($activity))
|
||||
<div class="rounded-lg border border-neutral-800 bg-neutral-900 p-6 space-y-3">
|
||||
<h3 class="text-sm font-semibold uppercase tracking-widest text-neutral-400">Activity Signals</h3>
|
||||
<ul class="space-y-2">
|
||||
@foreach($activity as $signal)
|
||||
<li class="text-sm text-neutral-200">{{ $signal['icon'] }} {{ $signal['text'] }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- ── ENV Toggles ── --}}
|
||||
<div class="rounded-lg border border-neutral-800 bg-neutral-900 p-6 space-y-4">
|
||||
<h3 class="text-sm font-semibold uppercase tracking-widest text-neutral-400">ENV Configuration</h3>
|
||||
<p class="text-xs text-neutral-500">Edit <code class="text-sky-400">.env</code> to change these values. Run <code class="text-sky-400">php artisan config:clear</code> after changes.</p>
|
||||
<div class="overflow-hidden rounded-lg border border-neutral-800">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-neutral-800 bg-neutral-800/40">
|
||||
<th class="px-4 py-2 text-left text-xs text-neutral-400">Variable</th>
|
||||
<th class="px-4 py-2 text-left text-xs text-neutral-400">Current Value</th>
|
||||
<th class="px-4 py-2 text-left text-xs text-neutral-400">Effect</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($env_toggles as $t)
|
||||
<tr class="border-b border-neutral-800/50">
|
||||
<td class="px-4 py-2 font-mono text-sky-400">{{ $t['key'] }}</td>
|
||||
<td class="px-4 py-2 font-mono text-white">{{ $t['current'] }}</td>
|
||||
<td class="px-4 py-2 text-neutral-400">
|
||||
@switch($t['key'])
|
||||
@case('NOVA_EARLY_GROWTH_ENABLED') Master switch. Set to <code>false</code> to disable entire system. @break
|
||||
@case('NOVA_EARLY_GROWTH_MODE') <code>off</code> / <code>light</code> / <code>aggressive</code> @break
|
||||
@case('NOVA_EGS_ADAPTIVE_WINDOW') Widen trending window when uploads low. @break
|
||||
@case('NOVA_EGS_GRID_FILLER') Backfill page-1 grids to 12 items. @break
|
||||
@case('NOVA_EGS_SPOTLIGHT') Daily-rotating curated picks. @break
|
||||
@case('NOVA_EGS_ACTIVITY_LAYER') Real activity summary badges. @break
|
||||
@endswitch
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg bg-neutral-800/30 border border-neutral-700 p-4 text-xs text-neutral-400 space-y-1">
|
||||
<p><strong class="text-white">To enable (light mode):</strong></p>
|
||||
<pre class="text-sky-400 font-mono">NOVA_EARLY_GROWTH_ENABLED=true
|
||||
NOVA_EARLY_GROWTH_MODE=light</pre>
|
||||
<p class="mt-2"><strong class="text-white">To disable instantly:</strong></p>
|
||||
<pre class="text-sky-400 font-mono">NOVA_EARLY_GROWTH_ENABLED=false</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Cache Keys Reference ── --}}
|
||||
<div class="rounded-lg border border-neutral-800 bg-neutral-900 p-6 space-y-3">
|
||||
<h3 class="text-sm font-semibold uppercase tracking-widest text-neutral-400">Cache Keys</h3>
|
||||
<ul class="space-y-1">
|
||||
@foreach($cache_keys as $key)
|
||||
<li class="font-mono text-xs text-neutral-400">{{ $key }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<p class="text-xs text-neutral-600">Use the "Flush EGS Cache" button above to clear these in one action.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endsection
|
||||
36
resources/views/admin/staff_applications/index.blade.php
Normal file
36
resources/views/admin/staff_applications/index.blade.php
Normal file
@@ -0,0 +1,36 @@
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@section('page-content')
|
||||
<div class="max-w-5xl">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Staff / Contact Submissions</h2>
|
||||
|
||||
<div class="overflow-hidden rounded-lg border border-neutral-800 bg-nova-900 p-4">
|
||||
<table class="w-full table-auto text-left text-sm">
|
||||
<thead>
|
||||
<tr class="text-neutral-400">
|
||||
<th class="py-2">When</th>
|
||||
<th class="py-2">Topic</th>
|
||||
<th class="py-2">Name</th>
|
||||
<th class="py-2">Email</th>
|
||||
<th class="py-2">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($items as $i)
|
||||
<tr class="border-t border-neutral-800">
|
||||
<td class="py-3 text-neutral-400">{{ $i->created_at->toDayDateTimeString() }}</td>
|
||||
<td class="py-3">{{ ucfirst($i->topic) }}</td>
|
||||
<td class="py-3">{{ $i->name }}</td>
|
||||
<td class="py-3">{{ $i->email }}</td>
|
||||
<td class="py-3"><a class="text-sky-400 hover:underline" href="{{ route('admin.applications.show', $i->id) }}">View</a></td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="5" class="py-6 text-neutral-400">No submissions yet.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="mt-4">{{ $items->links() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
36
resources/views/admin/staff_applications/show.blade.php
Normal file
36
resources/views/admin/staff_applications/show.blade.php
Normal file
@@ -0,0 +1,36 @@
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@section('page-content')
|
||||
<div class="max-w-3xl">
|
||||
<h2 class="text-xl font-semibold text-white mb-2">Submission</h2>
|
||||
|
||||
<div class="rounded-lg border border-neutral-800 bg-nova-900 p-6">
|
||||
<dl class="grid grid-cols-1 gap-4 text-sm text-neutral-300">
|
||||
<div>
|
||||
<dt class="text-neutral-400">Topic</dt>
|
||||
<dd>{{ ucfirst($item->topic) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-neutral-400">Name</dt>
|
||||
<dd>{{ $item->name }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-neutral-400">Email</dt>
|
||||
<dd>{{ $item->email }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-neutral-400">Portfolio</dt>
|
||||
<dd>{{ $item->portfolio }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-neutral-400">Message</dt>
|
||||
<dd class="whitespace-pre-line">{{ $item->message }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-neutral-400">Received</dt>
|
||||
<dd>{{ $item->created_at->toDayDateTimeString() }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
28
resources/views/components/ad-unit.blade.php
Normal file
28
resources/views/components/ad-unit.blade.php
Normal file
@@ -0,0 +1,28 @@
|
||||
{{--
|
||||
<x-ad-unit slot="1234567890" />
|
||||
<x-ad-unit slot="1234567890" format="rectangle" class="my-6" />
|
||||
|
||||
Props:
|
||||
slot — AdSense ad slot ID (required)
|
||||
format — AdSense data-ad-format (default: auto)
|
||||
class — additional wrapper classes
|
||||
|
||||
Renders nothing when:
|
||||
- GOOGLE_ADSENSE_PUBLISHER_ID is not set in .env
|
||||
- User has not given consent (handled client-side via CSS class .ads-disabled)
|
||||
--}}
|
||||
@php
|
||||
$publisherId = config('services.google_adsense.publisher_id');
|
||||
@endphp
|
||||
|
||||
@if($publisherId)
|
||||
<div class="ad-unit-wrapper {{ $attributes->get('class', '') }}">
|
||||
<ins class="adsbygoogle"
|
||||
style="display:block"
|
||||
data-ad-client="{{ $publisherId }}"
|
||||
data-ad-slot="{{ $slot }}"
|
||||
data-ad-format="{{ $format ?? 'auto' }}"
|
||||
data-full-width-responsive="true"></ins>
|
||||
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
|
||||
</div>
|
||||
@endif
|
||||
19
resources/views/components/breadcrumbs.blade.php
Normal file
19
resources/views/components/breadcrumbs.blade.php
Normal file
@@ -0,0 +1,19 @@
|
||||
{{--
|
||||
Breadcrumb component with schema.org structured data.
|
||||
|
||||
@param \Illuminate\Support\Collection $breadcrumbs
|
||||
Collection of objects with ->name and ->url properties.
|
||||
--}}
|
||||
@if(isset($breadcrumbs) && $breadcrumbs->isNotEmpty())
|
||||
<nav class="flex items-center gap-1.5 flex-wrap text-sm text-neutral-400" aria-label="Breadcrumb">
|
||||
<a class="hover:text-white transition-colors" href="/">Home</a>
|
||||
@foreach($breadcrumbs as $crumb)
|
||||
<span class="opacity-40" aria-hidden="true">›</span>
|
||||
@if(!$loop->last)
|
||||
<a class="hover:text-white transition-colors" href="{{ $crumb->url }}">{{ $crumb->name }}</a>
|
||||
@else
|
||||
<span class="text-white/70">{{ $crumb->name }}</span>
|
||||
@endif
|
||||
@endforeach
|
||||
</nav>
|
||||
@endif
|
||||
5
resources/views/components/centered-content.blade.php
Normal file
5
resources/views/components/centered-content.blade.php
Normal file
@@ -0,0 +1,5 @@
|
||||
@props(['max' => '3xl'])
|
||||
|
||||
<div {{ $attributes->merge(['class' => "mx-auto px-6 md:px-10 max-w-{$max}"]) }}>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
13
resources/views/components/hero.blade.php
Normal file
13
resources/views/components/hero.blade.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<div class="max-w-4xl mx-auto py-12 text-center">
|
||||
@if(isset($title))
|
||||
<h1 class="text-3xl font-extrabold text-white">{{ $title }}</h1>
|
||||
@endif
|
||||
|
||||
@if(isset($subtitle))
|
||||
<p class="mt-3 text-sm text-neutral-300">{{ $subtitle }}</p>
|
||||
@endif
|
||||
|
||||
@if($slot->isNotEmpty())
|
||||
<div class="mt-6">{{ $slot }}</div>
|
||||
@endif
|
||||
</div>
|
||||
82
resources/views/emails/staff_application_received.blade.php
Normal file
82
resources/views/emails/staff_application_received.blade.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<style>
|
||||
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; color:#0f172a; }
|
||||
.container { max-width:700px; margin:24px auto; background:#ffffff; border-radius:8px; padding:20px; box-shadow:0 6px 18px rgba(2,6,23,0.08); }
|
||||
.brand { display:flex; align-items:center; gap:12px; }
|
||||
.brand img { height:40px; }
|
||||
.title { margin-top:16px; font-size:20px; font-weight:700; color:#0b1220; }
|
||||
.meta { margin-top:8px; color:#475569; font-size:13px; }
|
||||
.section { margin-top:18px; }
|
||||
.label { font-weight:600; color:#0b1220; font-size:13px; }
|
||||
.value { margin-top:6px; color:#0f172a; }
|
||||
.footer { margin-top:22px; color:#64748b; font-size:12px; }
|
||||
a { color:#0ea5e9; text-decoration:none; }
|
||||
.pill { display:inline-block; padding:4px 8px; border-radius:999px; background:#f1f5f9; color:#0b1220; font-size:12px; }
|
||||
pre { white-space:pre-wrap; font-family:inherit; font-size:13px; color:#0f172a; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="brand">
|
||||
<img src="{{ asset('gfx/skinbase_logo.png') }}" alt="Skinbase">
|
||||
<div>
|
||||
<div style="font-weight:700;">Skinbase</div>
|
||||
<div style="font-size:12px;color:#64748b;">New staff application / contact form submission</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="title">New {{ $topicLabel }}: {{ $application->name }}</div>
|
||||
|
||||
<div class="meta">Received {{ $application->created_at->toDayDateTimeString() }}</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="label">Details</div>
|
||||
<div class="value">
|
||||
<div><span class="pill">Topic</span> {{ $topicLabel }}</div>
|
||||
<div><strong>Name:</strong> {{ $application->name }}</div>
|
||||
<div><strong>Email:</strong> <a href="mailto:{{ $application->email }}">{{ $application->email }}</a></div>
|
||||
@if($application->role)<div><strong>Role:</strong> {{ $application->role }}</div>@endif
|
||||
@if($application->portfolio)<div><strong>Portfolio:</strong> <a href="{{ $application->portfolio }}">{{ $application->portfolio }}</a></div>@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($application->topic === 'bug')
|
||||
<div class="section">
|
||||
<div class="label">Bug report</div>
|
||||
<div class="value">
|
||||
@if($application->payload['data']['affected_url'] ?? false)
|
||||
<div><strong>Affected URL:</strong> <a href="{{ $application->payload['data']['affected_url'] }}">{{ $application->payload['data']['affected_url'] }}</a></div>
|
||||
@endif
|
||||
@if($application->payload['data']['steps'] ?? false)
|
||||
<div style="margin-top:8px"><strong>Steps to reproduce:</strong>
|
||||
<pre>{{ $application->payload['data']['steps'] }}</pre>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="section">
|
||||
<div class="label">Message</div>
|
||||
<div class="value"><pre>{{ $application->message }}</pre></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="label">Technical</div>
|
||||
<div class="value">
|
||||
<div><strong>IP:</strong> {{ $application->ip }}</div>
|
||||
<div style="margin-top:6px"><strong>User agent:</strong> {{ $application->user_agent }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div>If you prefer to manage submissions in the admin UI, open: <a href="{{ url('/admin/applications') }}">/admin/applications</a></div>
|
||||
<div style="margin-top:8px">— Skinbase</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
New {{ $topicLabel }}: {{ $application->name }}
|
||||
|
||||
Received: {{ $application->created_at->toDayDateTimeString() }}
|
||||
|
||||
Topic: {{ $topicLabel }}
|
||||
Name: {{ $application->name }}
|
||||
Email: {{ $application->email }}
|
||||
@if($application->role)
|
||||
Role: {{ $application->role }}
|
||||
@endif
|
||||
@if($application->portfolio)
|
||||
Portfolio: {{ $application->portfolio }}
|
||||
@endif
|
||||
|
||||
Message:
|
||||
{{ $application->message }}
|
||||
|
||||
Technical:
|
||||
IP: {{ $application->ip }}
|
||||
User agent: {{ $application->user_agent }}
|
||||
|
||||
Manage submissions: {{ url('/admin/applications') }}
|
||||
28
resources/views/errors/401.blade.php
Normal file
28
resources/views/errors/401.blade.php
Normal file
@@ -0,0 +1,28 @@
|
||||
{{--
|
||||
401 — Unauthorized
|
||||
Use for: routes that require authentication when user is not logged in.
|
||||
--}}
|
||||
@extends('errors._layout', [
|
||||
'error_code' => 401,
|
||||
'error_title' => 'Sign In Required',
|
||||
'error_message' => 'Please sign in to access this page.',
|
||||
])
|
||||
|
||||
@section('badge', 'Unauthorized')
|
||||
|
||||
@section('primary-cta')
|
||||
<a href="/login"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors">
|
||||
<i class="fas fa-sign-in-alt" aria-hidden="true"></i>
|
||||
Sign In
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('secondary-ctas')
|
||||
<a href="/register" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Create Account
|
||||
</a>
|
||||
<a href="/" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Home
|
||||
</a>
|
||||
@endsection
|
||||
43
resources/views/errors/403.blade.php
Normal file
43
resources/views/errors/403.blade.php
Normal file
@@ -0,0 +1,43 @@
|
||||
{{--
|
||||
403 — Forbidden
|
||||
Use for: private artwork, banned content, region restrictions.
|
||||
Shows login button if user is a guest; profile/discover if logged in.
|
||||
--}}
|
||||
@extends('errors._layout', [
|
||||
'error_code' => 403,
|
||||
'error_title' => 'Access Denied',
|
||||
'error_message' => $message ?? 'You do not have permission to view this content.',
|
||||
])
|
||||
|
||||
@section('badge', 'Forbidden')
|
||||
|
||||
@section('primary-cta')
|
||||
@guest
|
||||
<a href="/login"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors">
|
||||
<i class="fas fa-sign-in-alt" aria-hidden="true"></i>
|
||||
Sign In
|
||||
</a>
|
||||
@else
|
||||
<a href="/discover/trending"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors">
|
||||
<i class="fas fa-compass" aria-hidden="true"></i>
|
||||
Back to Discover
|
||||
</a>
|
||||
@endguest
|
||||
@endsection
|
||||
|
||||
@section('secondary-ctas')
|
||||
@guest
|
||||
<a href="/register" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Create Account
|
||||
</a>
|
||||
@else
|
||||
<a href="/@{{ Auth::user()->username }}" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
My Profile
|
||||
</a>
|
||||
@endguest
|
||||
<a href="/" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Home
|
||||
</a>
|
||||
@endsection
|
||||
90
resources/views/errors/404.blade.php
Normal file
90
resources/views/errors/404.blade.php
Normal file
@@ -0,0 +1,90 @@
|
||||
{{--
|
||||
404 — Generic Not Found
|
||||
Returned when a route has no match.
|
||||
Includes: trending artworks (6), top tags (10), discover CTA.
|
||||
--}}
|
||||
@extends('errors._layout', [
|
||||
'error_code' => 404,
|
||||
'error_title' => '404 — Lost in the Nova',
|
||||
'error_message' => 'This page drifted into deep space. Let\'s get you back on track.',
|
||||
])
|
||||
|
||||
@section('badge', 'Page Not Found')
|
||||
|
||||
@section('primary-cta')
|
||||
<a href="/discover/trending"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors">
|
||||
<i class="fas fa-compass" aria-hidden="true"></i>
|
||||
Explore Discover
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('secondary-ctas')
|
||||
<a href="/explore/wallpapers" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Browse Wallpapers
|
||||
</a>
|
||||
<a href="/search" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
<i class="fas fa-search mr-1.5" aria-hidden="true"></i> Search
|
||||
</a>
|
||||
<a href="/" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Home
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('recovery')
|
||||
|
||||
{{-- Trending artworks --}}
|
||||
@if(isset($trendingArtworks) && $trendingArtworks->count())
|
||||
<div class="mb-12">
|
||||
<h2 class="text-sm font-semibold text-white/40 uppercase tracking-widest mb-4">Trending Right Now</h2>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
@foreach($trendingArtworks->take(6) as $artwork)
|
||||
@include('errors._artwork-card', ['artwork' => $artwork])
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Top tags --}}
|
||||
@if(isset($trendingTags) && $trendingTags->count())
|
||||
<div class="mb-12">
|
||||
<h2 class="text-sm font-semibold text-white/40 uppercase tracking-widest mb-4">Popular Tags</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach($trendingTags->take(10) as $tag)
|
||||
<a href="/tag/{{ $tag->slug }}"
|
||||
class="rounded-full bg-white/5 hover:bg-sky-500/20 border border-white/8 hover:border-sky-500/30 text-white/70 hover:text-sky-300 px-3 py-1 text-xs font-medium transition-colors">
|
||||
#{{ $tag->name }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Explore categories --}}
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold text-white/40 uppercase tracking-widest mb-4">Explore Categories</h2>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
<a href="/explore/wallpapers"
|
||||
class="rounded-xl bg-white/3 hover:bg-sky-500/10 border border-white/5 hover:border-sky-500/20 px-4 py-3 flex items-center gap-3 transition-all group">
|
||||
<i class="fas fa-image text-sky-400/60 group-hover:text-sky-400 text-lg" aria-hidden="true"></i>
|
||||
<span class="text-sm font-medium text-white/80 group-hover:text-white">Wallpapers</span>
|
||||
</a>
|
||||
<a href="/explore/skins"
|
||||
class="rounded-xl bg-white/3 hover:bg-sky-500/10 border border-white/5 hover:border-sky-500/20 px-4 py-3 flex items-center gap-3 transition-all group">
|
||||
<i class="fas fa-paint-brush text-sky-400/60 group-hover:text-sky-400 text-lg" aria-hidden="true"></i>
|
||||
<span class="text-sm font-medium text-white/80 group-hover:text-white">Skins</span>
|
||||
</a>
|
||||
<a href="/explore/photography"
|
||||
class="rounded-xl bg-white/3 hover:bg-sky-500/10 border border-white/5 hover:border-sky-500/20 px-4 py-3 flex items-center gap-3 transition-all group">
|
||||
<i class="fas fa-camera text-sky-400/60 group-hover:text-sky-400 text-lg" aria-hidden="true"></i>
|
||||
<span class="text-sm font-medium text-white/80 group-hover:text-white">Photography</span>
|
||||
</a>
|
||||
<a href="/explore/other"
|
||||
class="rounded-xl bg-white/3 hover:bg-sky-500/10 border border-white/5 hover:border-sky-500/20 px-4 py-3 flex items-center gap-3 transition-all group">
|
||||
<i class="fas fa-layer-group text-sky-400/60 group-hover:text-sky-400 text-lg" aria-hidden="true"></i>
|
||||
<span class="text-sm font-medium text-white/80 group-hover:text-white">Other</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
29
resources/views/errors/410.blade.php
Normal file
29
resources/views/errors/410.blade.php
Normal file
@@ -0,0 +1,29 @@
|
||||
{{--
|
||||
410 — Gone
|
||||
Use for permanently deleted artworks, DMCA removed content, deleted blog posts.
|
||||
Minimal content — no heavy suggestions needed by spec.
|
||||
--}}
|
||||
@extends('errors._layout', [
|
||||
'error_code' => 410,
|
||||
'error_title' => 'Content Permanently Removed',
|
||||
'error_message' => 'This content has been permanently removed and is no longer available.',
|
||||
])
|
||||
|
||||
@section('badge', 'Gone')
|
||||
|
||||
@section('primary-cta')
|
||||
<a href="/discover/trending"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors">
|
||||
<i class="fas fa-compass" aria-hidden="true"></i>
|
||||
Explore Discover
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('secondary-ctas')
|
||||
<a href="/" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Return Home
|
||||
</a>
|
||||
<a href="/search" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
<i class="fas fa-search mr-1.5" aria-hidden="true"></i> Search
|
||||
</a>
|
||||
@endsection
|
||||
24
resources/views/errors/419.blade.php
Normal file
24
resources/views/errors/419.blade.php
Normal file
@@ -0,0 +1,24 @@
|
||||
{{--
|
||||
419 — Page Expired (CSRF token mismatch)
|
||||
--}}
|
||||
@extends('errors._layout', [
|
||||
'error_code' => 419,
|
||||
'error_title' => 'Page Expired',
|
||||
'error_message' => 'Your session has expired. Please refresh the page and try again.',
|
||||
])
|
||||
|
||||
@section('badge', 'Session Expired')
|
||||
|
||||
@section('primary-cta')
|
||||
<button onclick="window.location.reload()"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors cursor-pointer">
|
||||
<i class="fas fa-redo" aria-hidden="true"></i>
|
||||
Refresh Page
|
||||
</button>
|
||||
@endsection
|
||||
|
||||
@section('secondary-ctas')
|
||||
<a href="/" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Return Home
|
||||
</a>
|
||||
@endsection
|
||||
40
resources/views/errors/500.blade.php
Normal file
40
resources/views/errors/500.blade.php
Normal file
@@ -0,0 +1,40 @@
|
||||
{{--
|
||||
500 — Server Error
|
||||
Shows a user-friendly message and a reference/correlation ID.
|
||||
Never shows a stack trace in production.
|
||||
--}}
|
||||
@extends('errors._layout', [
|
||||
'error_code' => 500,
|
||||
'error_title' => 'Something Went Wrong in the Nova',
|
||||
'error_message' => 'An unexpected error occurred. Our team has been notified and is on it.',
|
||||
])
|
||||
|
||||
@section('badge', 'Server Error')
|
||||
|
||||
@section('primary-cta')
|
||||
<button onclick="window.location.reload()"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors cursor-pointer">
|
||||
<i class="fas fa-redo" aria-hidden="true"></i>
|
||||
Try Again
|
||||
</button>
|
||||
@endsection
|
||||
|
||||
@section('secondary-ctas')
|
||||
<a href="/" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Return Home
|
||||
</a>
|
||||
<a href="/contact" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Report Issue
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('recovery')
|
||||
@if(isset($correlationId))
|
||||
<div class="flex justify-center">
|
||||
<div class="inline-flex items-center gap-2 rounded-xl bg-white/4 border border-white/8 px-5 py-3 text-xs text-white/40">
|
||||
<i class="fas fa-fingerprint text-white/25" aria-hidden="true"></i>
|
||||
Reference ID: <span class="font-mono font-semibold text-white/60 select-all">{{ $correlationId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endsection
|
||||
18
resources/views/errors/503.blade.php
Normal file
18
resources/views/errors/503.blade.php
Normal file
@@ -0,0 +1,18 @@
|
||||
{{--
|
||||
503 — Service Unavailable / Maintenance Mode
|
||||
--}}
|
||||
@extends('errors._layout', [
|
||||
'error_code' => 503,
|
||||
'error_title' => 'We\'ll Be Right Back',
|
||||
'error_message' => 'Skinbase is under scheduled maintenance. Check back soon.',
|
||||
])
|
||||
|
||||
@section('badge', 'Maintenance')
|
||||
|
||||
@section('primary-cta')
|
||||
<button onclick="window.location.reload()"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors cursor-pointer">
|
||||
<i class="fas fa-redo" aria-hidden="true"></i>
|
||||
Check Again
|
||||
</button>
|
||||
@endsection
|
||||
22
resources/views/errors/_artwork-card.blade.php
Normal file
22
resources/views/errors/_artwork-card.blade.php
Normal file
@@ -0,0 +1,22 @@
|
||||
{{--
|
||||
Shared: artwork suggestion card for error pages.
|
||||
Expects $artwork array: [id, title, author, url, thumb]
|
||||
--}}
|
||||
<a href="{{ $artwork['url'] }}" class="group relative rounded-xl overflow-hidden bg-nova-800 border border-white/5 hover:border-sky-500/30 transition-all duration-200 block">
|
||||
@if($artwork['thumb'])
|
||||
<div class="aspect-video w-full overflow-hidden bg-nova-700">
|
||||
<img src="{{ $artwork['thumb'] }}"
|
||||
alt="{{ $artwork['title'] }}"
|
||||
loading="lazy"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300 opacity-80 group-hover:opacity-100" />
|
||||
</div>
|
||||
@else
|
||||
<div class="aspect-video w-full bg-gradient-to-br from-nova-700 to-nova-800 flex items-center justify-center">
|
||||
<i class="fas fa-image text-white/20 text-3xl" aria-hidden="true"></i>
|
||||
</div>
|
||||
@endif
|
||||
<div class="p-3">
|
||||
<p class="text-sm font-semibold text-white truncate">{{ $artwork['title'] }}</p>
|
||||
<p class="text-xs text-white/50 truncate mt-0.5">by {{ $artwork['author'] }}</p>
|
||||
</div>
|
||||
</a>
|
||||
71
resources/views/errors/_layout.blade.php
Normal file
71
resources/views/errors/_layout.blade.php
Normal file
@@ -0,0 +1,71 @@
|
||||
{{--
|
||||
Error Layout — extends nova.blade.php
|
||||
Shared structure for all error pages (404, 410, 403, 401, 500, contextual variants).
|
||||
|
||||
Enforces:
|
||||
• noindex
|
||||
• No canonical link
|
||||
• Dark Nova design
|
||||
• Full navigation visible
|
||||
• Recovery CTAs
|
||||
|
||||
Variables:
|
||||
$error_code int HTTP status code
|
||||
$error_title string Short headline
|
||||
$error_message string Friendly sentence
|
||||
--}}
|
||||
@extends('layouts.nova')
|
||||
|
||||
@php
|
||||
$code = $error_code ?? 404;
|
||||
$title = $error_title ?? 'Page Not Found';
|
||||
$message = $error_message ?? 'This page drifted into deep space.';
|
||||
@endphp
|
||||
|
||||
@push('head')
|
||||
{{-- SEO: never index error pages --}}
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="min-h-[70vh] flex flex-col items-center justify-center px-4 py-16">
|
||||
|
||||
{{-- Hero block --}}
|
||||
<div class="text-center max-w-xl mx-auto">
|
||||
|
||||
{{-- Code glow --}}
|
||||
<div class="text-8xl font-extrabold text-sky-500/20 select-none leading-none mb-2">{{ $code }}</div>
|
||||
|
||||
{{-- Gradient badge --}}
|
||||
<div class="inline-flex items-center gap-2 rounded-full bg-sky-500/10 border border-sky-500/20 px-4 py-1 text-xs font-semibold text-sky-400 uppercase tracking-widest mb-6">
|
||||
@yield('badge', 'Error')
|
||||
</div>
|
||||
|
||||
<h1 class="text-3xl sm:text-4xl font-extrabold text-white leading-tight mb-4">
|
||||
{{ $title }}
|
||||
</h1>
|
||||
|
||||
<p class="text-white/60 text-base sm:text-lg leading-relaxed mb-8">
|
||||
{{ $message }}
|
||||
</p>
|
||||
|
||||
{{-- Primary CTA --}}
|
||||
@yield('primary-cta')
|
||||
|
||||
{{-- Secondary CTAs --}}
|
||||
@hasSection('secondary-ctas')
|
||||
<div class="flex flex-wrap justify-center gap-3 mt-4">
|
||||
@yield('secondary-ctas')
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Contextual recovery section --}}
|
||||
@hasSection('recovery')
|
||||
<div class="w-full max-w-5xl mx-auto mt-16">
|
||||
@yield('recovery')
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,99 @@
|
||||
{{--
|
||||
Artwork Not Found (contextual) — HTTP 404 or 403
|
||||
Shown when:
|
||||
- Artwork ID not found at all → HTTP 404
|
||||
- Artwork exists but is private/unapproved → HTTP 403 ($isForbidden=true)
|
||||
Separate view for permanently deleted → errors/410.blade.php
|
||||
|
||||
Variables:
|
||||
$isForbidden bool true when private/403
|
||||
$trendingArtworks Collection (max 6)
|
||||
$creatorArtworks Collection (max 6, optional)
|
||||
$creatorUsername string|null
|
||||
--}}
|
||||
@php
|
||||
$isForbidden = $isForbidden ?? false;
|
||||
$errorCode = $isForbidden ? 403 : 404;
|
||||
$errorTitle = $isForbidden ? 'Access Denied' : 'Artwork Not Found';
|
||||
$errorMessage = $isForbidden
|
||||
? 'This artwork is private and not publicly available.'
|
||||
: 'This artwork is no longer available, or the link may be broken.';
|
||||
$badgeLabel = $isForbidden ? 'Private Artwork' : 'Artwork Not Found';
|
||||
@endphp
|
||||
@extends('errors._layout', [
|
||||
'error_code' => $errorCode,
|
||||
'error_title' => $errorTitle,
|
||||
'error_message' => $errorMessage,
|
||||
])
|
||||
|
||||
@section('badge', $badgeLabel)
|
||||
|
||||
@section('primary-cta')
|
||||
@if($isForbidden)
|
||||
@guest
|
||||
<a href="/login"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors">
|
||||
<i class="fas fa-sign-in-alt" aria-hidden="true"></i>
|
||||
Sign In to View
|
||||
</a>
|
||||
@else
|
||||
<a href="/discover/trending"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors">
|
||||
<i class="fas fa-compass" aria-hidden="true"></i>
|
||||
Explore Discover
|
||||
</a>
|
||||
@endguest
|
||||
@else
|
||||
<a href="/discover/trending"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors">
|
||||
<i class="fas fa-compass" aria-hidden="true"></i>
|
||||
Explore Discover
|
||||
</a>
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
@section('secondary-ctas')
|
||||
<a href="/explore/wallpapers" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Browser Wallpapers
|
||||
</a>
|
||||
<a href="/search" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
<i class="fas fa-search mr-1.5" aria-hidden="true"></i> Search
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('recovery')
|
||||
|
||||
{{-- Creator's other artworks (if we have a hint about the creator) --}}
|
||||
@if(isset($creatorArtworks) && $creatorArtworks->count())
|
||||
<div class="mb-12">
|
||||
<h2 class="text-sm font-semibold text-white/40 uppercase tracking-widest mb-4">
|
||||
More from this Creator
|
||||
</h2>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
@foreach($creatorArtworks->take(6) as $artwork)
|
||||
@include('errors._artwork-card', ['artwork' => $artwork])
|
||||
@endforeach
|
||||
</div>
|
||||
@if(isset($creatorUsername))
|
||||
<div class="mt-3">
|
||||
<a href="/@{{ $creatorUsername }}" class="text-xs text-sky-400 hover:text-sky-300 transition-colors">
|
||||
View full gallery →
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Trending artworks --}}
|
||||
@if(isset($trendingArtworks) && $trendingArtworks->count())
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold text-white/40 uppercase tracking-widest mb-4">Trending Wallpapers</h2>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
@foreach($trendingArtworks->take(6) as $artwork)
|
||||
@include('errors._artwork-card', ['artwork' => $artwork])
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@endsection
|
||||
52
resources/views/errors/contextual/blog-not-found.blade.php
Normal file
52
resources/views/errors/contextual/blog-not-found.blade.php
Normal file
@@ -0,0 +1,52 @@
|
||||
{{--
|
||||
Blog Post Not Found — Contextual 404
|
||||
Shown at /blog/:slug when post doesn't exist or is unpublished.
|
||||
|
||||
Variables:
|
||||
$latestPosts Collection (max 6)
|
||||
--}}
|
||||
@extends('errors._layout', [
|
||||
'error_code' => 404,
|
||||
'error_title' => 'Article Not Found',
|
||||
'error_message' => 'This article is no longer available or the link has changed.',
|
||||
])
|
||||
|
||||
@section('badge', 'Article Not Found')
|
||||
|
||||
@section('primary-cta')
|
||||
<a href="/blog"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors">
|
||||
<i class="fas fa-newspaper" aria-hidden="true"></i>
|
||||
Visit Blog
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('secondary-ctas')
|
||||
<a href="/" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Home
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('recovery')
|
||||
|
||||
@if(isset($latestPosts) && $latestPosts->count())
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold text-white/40 uppercase tracking-widest mb-4">Latest Articles</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
@foreach($latestPosts->take(6) as $post)
|
||||
<a href="{{ $post['url'] }}"
|
||||
class="flex flex-col gap-2 rounded-xl p-4 bg-white/3 hover:bg-white/7 border border-white/5 hover:border-sky-500/20 transition-all">
|
||||
<p class="text-sm font-semibold text-white leading-snug">{{ $post['title'] }}</p>
|
||||
@if(!empty($post['excerpt']))
|
||||
<p class="text-xs text-white/50 leading-relaxed flex-1">{{ $post['excerpt'] }}</p>
|
||||
@endif
|
||||
@if(!empty($post['published_at']))
|
||||
<p class="text-xs text-white/30 mt-auto">{{ $post['published_at'] }}</p>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@endsection
|
||||
@@ -0,0 +1,94 @@
|
||||
{{--
|
||||
Creator Not Found — Contextual 404
|
||||
Shown at /@:username when user doesn't exist.
|
||||
|
||||
Variables:
|
||||
$requestedUsername string|null
|
||||
$trendingCreators Collection (max 6)
|
||||
$recentCreators Collection (max 6)
|
||||
--}}
|
||||
@extends('errors._layout', [
|
||||
'error_code' => 404,
|
||||
'error_title' => 'Creator Not Found',
|
||||
'error_message' => isset($requestedUsername)
|
||||
? 'The creator "@' . $requestedUsername . '" does not exist on Skinbase.'
|
||||
: 'This creator profile does not exist.',
|
||||
])
|
||||
|
||||
@section('badge', 'Creator Not Found')
|
||||
|
||||
@section('primary-cta')
|
||||
{{-- Inline creator search --}}
|
||||
<form action="/search" method="GET" class="flex items-center gap-2 w-full max-w-sm mx-auto mb-2">
|
||||
<input
|
||||
type="text"
|
||||
name="q"
|
||||
placeholder="Search for a creator…"
|
||||
value="{{ isset($requestedUsername) ? '@'.$requestedUsername : '' }}"
|
||||
class="flex-1 rounded-xl bg-white/8 border border-white/12 focus:border-sky-500/50 focus:ring-0 text-sm text-white placeholder-white/30 px-4 py-2.5 outline-none transition-colors"
|
||||
/>
|
||||
<button type="submit"
|
||||
class="rounded-xl bg-sky-500 hover:bg-sky-400 text-white px-4 py-2.5 text-sm font-semibold transition-colors shrink-0">
|
||||
<i class="fas fa-search" aria-hidden="true"></i>
|
||||
</button>
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@section('secondary-ctas')
|
||||
<a href="/creators/top" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
<i class="fas fa-trophy mr-1.5" aria-hidden="true"></i> Top Creators
|
||||
</a>
|
||||
<a href="/register" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
<i class="fas fa-star mr-1.5" aria-hidden="true"></i> Join Skinbase
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('recovery')
|
||||
|
||||
{{-- Trending creators --}}
|
||||
@if(isset($trendingCreators) && $trendingCreators->count())
|
||||
<div class="mb-12">
|
||||
<h2 class="text-sm font-semibold text-white/40 uppercase tracking-widest mb-4">Top Creators</h2>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||
@foreach($trendingCreators->take(6) as $creator)
|
||||
<a href="{{ $creator['url'] }}"
|
||||
class="flex flex-col items-center gap-2 rounded-xl p-4 bg-white/3 hover:bg-white/7 border border-white/5 hover:border-sky-500/20 transition-all text-center group">
|
||||
<img src="{{ $creator['avatar_url'] }}"
|
||||
alt="{{ $creator['name'] }}"
|
||||
loading="lazy"
|
||||
class="w-14 h-14 rounded-full object-cover ring-2 ring-white/10 group-hover:ring-sky-500/30 transition-all"
|
||||
onerror="this.src='https://files.skinbase.org/default/avatar_default.webp'" />
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-white truncate max-w-[100px]">{{ $creator['name'] }}</p>
|
||||
<p class="text-xs text-white/40">{{ $creator['artworks_count'] }} uploads</p>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Recently joined creators --}}
|
||||
@if(isset($recentCreators) && $recentCreators->count())
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold text-white/40 uppercase tracking-widest mb-4">Recently Joined</h2>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||
@foreach($recentCreators->take(6) as $creator)
|
||||
<a href="{{ $creator['url'] }}"
|
||||
class="flex flex-col items-center gap-2 rounded-xl p-4 bg-white/3 hover:bg-white/7 border border-white/5 hover:border-sky-500/20 transition-all text-center group">
|
||||
<img src="{{ $creator['avatar_url'] }}"
|
||||
alt="{{ $creator['name'] }}"
|
||||
loading="lazy"
|
||||
class="w-14 h-14 rounded-full object-cover ring-2 ring-white/10 group-hover:ring-sky-500/30 transition-all"
|
||||
onerror="this.src='https://files.skinbase.org/default/avatar_default.webp'" />
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-white truncate max-w-[100px]">{{ $creator['name'] }}</p>
|
||||
<p class="text-xs text-white/40">{{ $creator['artworks_count'] }} uploads</p>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@endsection
|
||||
31
resources/views/errors/contextual/page-not-found.blade.php
Normal file
31
resources/views/errors/contextual/page-not-found.blade.php
Normal file
@@ -0,0 +1,31 @@
|
||||
{{--
|
||||
Static Page Not Found — Contextual 404
|
||||
Shown at /pages/:slug or /about|/help|/contact when page not in DB.
|
||||
--}}
|
||||
@extends('errors._layout', [
|
||||
'error_code' => 404,
|
||||
'error_title' => 'Page Not Found',
|
||||
'error_message' => 'This page was removed or renamed. Try one of the links below.',
|
||||
])
|
||||
|
||||
@section('badge', 'Page Not Found')
|
||||
|
||||
@section('primary-cta')
|
||||
<a href="/help"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors">
|
||||
<i class="fas fa-question-circle" aria-hidden="true"></i>
|
||||
Help Center
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('secondary-ctas')
|
||||
<a href="/about" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
About
|
||||
</a>
|
||||
<a href="/contact" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Contact
|
||||
</a>
|
||||
<a href="/" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Home
|
||||
</a>
|
||||
@endsection
|
||||
70
resources/views/errors/contextual/tag-not-found.blade.php
Normal file
70
resources/views/errors/contextual/tag-not-found.blade.php
Normal file
@@ -0,0 +1,70 @@
|
||||
{{--
|
||||
Tag Not Found — Contextual 404
|
||||
Shown at /tag/:slug when slug not in DB.
|
||||
|
||||
Variables:
|
||||
$requestedSlug string
|
||||
$similarTags Collection (max 10)
|
||||
$trendingTags Collection (max 10)
|
||||
--}}
|
||||
@extends('errors._layout', [
|
||||
'error_code' => 404,
|
||||
'error_title' => 'Tag Not Found',
|
||||
'error_message' => 'The tag "' . ($requestedSlug ?? '') . '" doesn\'t exist yet.',
|
||||
])
|
||||
|
||||
@section('badge', 'Tag Not Found')
|
||||
|
||||
@section('primary-cta')
|
||||
<a href="/tags"
|
||||
class="inline-flex items-center gap-2 rounded-xl bg-sky-500 hover:bg-sky-400 text-white font-semibold px-6 py-3 text-sm shadow-lg shadow-sky-900/30 transition-colors">
|
||||
<i class="fas fa-tags" aria-hidden="true"></i>
|
||||
Browse All Tags
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('secondary-ctas')
|
||||
<a href="/discover/trending" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
Trending
|
||||
</a>
|
||||
<a href="/search" class="rounded-xl border border-white/10 hover:border-white/25 text-white/70 hover:text-white px-4 py-2 text-sm transition-colors">
|
||||
<i class="fas fa-search mr-1.5" aria-hidden="true"></i> Search
|
||||
</a>
|
||||
@endsection
|
||||
|
||||
@section('recovery')
|
||||
|
||||
{{-- Similar tags --}}
|
||||
@if(isset($similarTags) && $similarTags->count())
|
||||
<div class="mb-10">
|
||||
<h2 class="text-sm font-semibold text-white/40 uppercase tracking-widest mb-4">Similar Tags</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach($similarTags->take(10) as $tag)
|
||||
<a href="/tag/{{ $tag->slug }}"
|
||||
class="rounded-full bg-sky-500/10 hover:bg-sky-500/20 border border-sky-500/20 hover:border-sky-500/40 text-sky-300 hover:text-sky-200 px-3 py-1 text-xs font-medium transition-colors">
|
||||
#{{ $tag->name }}
|
||||
@if($tag->artworks_count ?? null)
|
||||
<span class="text-sky-400/60 ml-1">{{ number_format($tag->artworks_count) }}</span>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Trending tags --}}
|
||||
@if(isset($trendingTags) && $trendingTags->count())
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold text-white/40 uppercase tracking-widest mb-4">Popular Tags</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach($trendingTags->take(10) as $tag)
|
||||
<a href="/tag/{{ $tag->slug }}"
|
||||
class="rounded-full bg-white/5 hover:bg-sky-500/20 border border-white/8 hover:border-sky-500/30 text-white/70 hover:text-sky-300 px-3 py-1 text-xs font-medium transition-colors">
|
||||
#{{ $tag->name }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@endsection
|
||||
@@ -31,6 +31,22 @@
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:title" content="{{ $page_title ?? ($hero_title ?? 'Skinbase') }}" />
|
||||
<meta name="twitter:description" content="{{ $page_meta_description ?? '' }}" />
|
||||
|
||||
{{-- Breadcrumb structured data --}}
|
||||
@if(isset($breadcrumbs) && $breadcrumbs->isNotEmpty())
|
||||
<script type="application/ld+json">
|
||||
{!! json_encode([
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'BreadcrumbList',
|
||||
'itemListElement' => $breadcrumbs->values()->map(fn ($crumb, $i) => [
|
||||
'@type' => 'ListItem',
|
||||
'position' => $i + 1,
|
||||
'name' => $crumb->name,
|
||||
'item' => url($crumb->url),
|
||||
])->all(),
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) !!}
|
||||
</script>
|
||||
@endif
|
||||
@endpush
|
||||
|
||||
@php
|
||||
@@ -76,19 +92,11 @@
|
||||
<div class="relative px-6 py-10 md:px-10 md:py-14">
|
||||
|
||||
{{-- Breadcrumb --}}
|
||||
<nav class="flex items-center gap-1.5 flex-wrap text-sm text-neutral-400" aria-label="Breadcrumb">
|
||||
<a class="hover:text-white transition-colors" href="/browse">Gallery</a>
|
||||
@if(isset($contentType) && $contentType)
|
||||
<span class="opacity-40" aria-hidden="true">›</span>
|
||||
<a class="hover:text-white transition-colors" href="/{{ $contentType->slug }}">{{ $contentType->name }}</a>
|
||||
@endif
|
||||
@if(($gallery_type ?? null) === 'category')
|
||||
@foreach($breadcrumbs as $crumb)
|
||||
<span class="opacity-40" aria-hidden="true">›</span>
|
||||
<a class="hover:text-white transition-colors" href="{{ $crumb->url }}">{{ $crumb->name }}</a>
|
||||
@endforeach
|
||||
@endif
|
||||
</nav>
|
||||
@include('components.breadcrumbs', ['breadcrumbs' => collect(array_filter([
|
||||
isset($contentType) && $contentType ? (object) ['name' => 'Explore', 'url' => '/explore'] : null,
|
||||
isset($contentType) && $contentType ? (object) ['name' => $contentType->name, 'url' => '/explore/' . strtolower($contentType->slug)] : (object) ['name' => 'Explore', 'url' => '/explore'],
|
||||
...(($gallery_type ?? null) === 'category' && isset($breadcrumbs) ? $breadcrumbs->all() : []),
|
||||
]))])
|
||||
|
||||
{{-- Glass title panel --}}
|
||||
<div class="mt-4 py-5">
|
||||
|
||||
@@ -44,8 +44,62 @@
|
||||
.auth-card { max-width: 720px; margin-left: auto; margin-right: auto; }
|
||||
.auth-card h1 { font-size: 1.25rem; line-height: 1.2; }
|
||||
.auth-card p { color: rgba(203,213,225,0.9); }
|
||||
/* Global heading styles for better hierarchy */
|
||||
h1, h2, h3, h4, h5, h6 { color: #ffffff; margin-top: 1rem; margin-bottom: 0.5rem; }
|
||||
h1 { font-size: 2.25rem; line-height: 1.05; font-weight: 800; letter-spacing: -0.02em; }
|
||||
h2 { font-size: 1.5rem; line-height: 1.15; font-weight: 700; letter-spacing: -0.01em; }
|
||||
h3 { font-size: 1.125rem; line-height: 1.2; font-weight: 600; }
|
||||
h4 { font-size: 1rem; line-height: 1.25; font-weight: 600; }
|
||||
h5 { font-size: 0.95rem; line-height: 1.25; font-weight: 600; }
|
||||
h6 { font-size: 0.85rem; line-height: 1.3; font-weight: 600; text-transform: uppercase; opacity: 0.85; }
|
||||
|
||||
/* Prose (typography plugin) overrides */
|
||||
.prose h1 { font-size: 2.25rem; }
|
||||
.prose h2 { font-size: 1.5rem; }
|
||||
.prose h3 { font-size: 1.125rem; }
|
||||
.prose h4, .prose h5, .prose h6 { font-weight: 600; }
|
||||
|
||||
/* Alpine: hide x-cloak elements until Alpine picks them up */
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
@stack('head')
|
||||
|
||||
@if(config('services.google_adsense.publisher_id'))
|
||||
{{-- Google AdSense — consent-gated loader --}}
|
||||
{{-- Script is only injected after the user accepts all cookies. --}}
|
||||
{{-- If consent was given on a previous visit it fires on page load. --}}
|
||||
<script>
|
||||
(function () {
|
||||
var PUB = '{{ config('services.google_adsense.publisher_id') }}';
|
||||
var SCRIPT_ID = 'adsense-js';
|
||||
|
||||
function injectAdsense() {
|
||||
if (document.getElementById(SCRIPT_ID)) return;
|
||||
var s = document.createElement('script');
|
||||
s.id = SCRIPT_ID;
|
||||
s.async = true;
|
||||
s.crossOrigin = 'anonymous';
|
||||
s.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=' + PUB;
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
// Expose so Alpine consent banner can trigger immediately on accept
|
||||
window.sbLoadAds = injectAdsense;
|
||||
|
||||
// If the user already consented on a previous visit, load straight away
|
||||
if (localStorage.getItem('sb_cookie_consent') === 'all') {
|
||||
injectAdsense();
|
||||
}
|
||||
|
||||
// Handle consent granted in another tab
|
||||
window.addEventListener('storage', function (e) {
|
||||
if (e.key === 'sb_cookie_consent' && e.newValue === 'all') {
|
||||
injectAdsense();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@endif
|
||||
</head>
|
||||
@php
|
||||
$authBgRoutes = [
|
||||
@@ -107,6 +161,60 @@
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
{{-- Cookie Consent Banner --}}
|
||||
<div
|
||||
x-data="{
|
||||
show: false,
|
||||
init() {
|
||||
if (!localStorage.getItem('sb_cookie_consent')) {
|
||||
this.show = true;
|
||||
}
|
||||
},
|
||||
accept() {
|
||||
localStorage.setItem('sb_cookie_consent', 'all');
|
||||
this.show = false;
|
||||
if (typeof window.sbLoadAds === 'function') window.sbLoadAds();
|
||||
},
|
||||
essential() {
|
||||
localStorage.setItem('sb_cookie_consent', 'essential');
|
||||
this.show = false;
|
||||
}
|
||||
}"
|
||||
x-show="show"
|
||||
x-cloak
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 translate-y-4"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 translate-y-4"
|
||||
class="fixed bottom-0 left-0 right-0 z-50 border-t border-orange-400/30 bg-orange-950/50 backdrop-blur-2xl px-4 md:px-8 py-5"
|
||||
role="dialog"
|
||||
aria-label="Cookie consent"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div class="max-w-6xl mx-auto flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-6">
|
||||
<div class="flex items-start gap-3 flex-1">
|
||||
<span class="text-orange-400 mt-0.5 shrink-0 text-lg">🍪</span>
|
||||
<p class="text-sm text-orange-100/90 leading-relaxed">
|
||||
We use <strong class="text-white">essential cookies</strong> to keep you logged in and protect your session.
|
||||
With your permission we also load <strong class="text-white">advertising cookies</strong> from third-party networks.
|
||||
<a href="/privacy-policy#cookies" class="text-orange-300 hover:text-orange-200 hover:underline ml-1">Learn more ↗</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<button
|
||||
@click="essential()"
|
||||
class="rounded-lg border border-orange-400/40 px-4 py-2 text-sm text-orange-200 hover:text-white hover:border-orange-400/70 hover:bg-white/5 transition-colors"
|
||||
>Essential only</button>
|
||||
<button
|
||||
@click="accept()"
|
||||
class="rounded-lg bg-orange-500 hover:bg-orange-400 px-4 py-2 text-sm font-semibold text-white shadow-lg shadow-orange-900/40 transition-colors"
|
||||
>Accept all</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@stack('scripts')
|
||||
</body>
|
||||
</html>
|
||||
|
||||
83
resources/views/layouts/nova/content-layout.blade.php
Normal file
83
resources/views/layouts/nova/content-layout.blade.php
Normal file
@@ -0,0 +1,83 @@
|
||||
{{--
|
||||
ContentLayout — minimal hero for tags directory, blog, static pages, legal.
|
||||
Used by /tags, /blog/*, /pages/*, /about, /help, /legal/*
|
||||
|
||||
Expected variables:
|
||||
$page_title, $page_meta_description, $page_canonical, $page_robots
|
||||
$breadcrumbs (collection, optional)
|
||||
Content via @yield('page-content')
|
||||
--}}
|
||||
@extends('layouts.nova')
|
||||
|
||||
@push('head')
|
||||
@isset($page_canonical)
|
||||
<link rel="canonical" href="{{ $page_canonical }}" />
|
||||
@endisset
|
||||
<meta name="robots" content="{{ $page_robots ?? 'index,follow' }}">
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="{{ $page_canonical ?? url()->current() }}" />
|
||||
<meta property="og:title" content="{{ $page_title ?? 'Skinbase' }}" />
|
||||
<meta property="og:description" content="{{ $page_meta_description ?? '' }}" />
|
||||
<meta property="og:site_name" content="Skinbase" />
|
||||
|
||||
{{-- Breadcrumb structured data --}}
|
||||
@if(isset($breadcrumbs) && $breadcrumbs->isNotEmpty())
|
||||
<script type="application/ld+json">
|
||||
{!! json_encode([
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'BreadcrumbList',
|
||||
'itemListElement' => $breadcrumbs->values()->map(fn ($crumb, $i) => [
|
||||
'@type' => 'ListItem',
|
||||
'position' => $i + 1,
|
||||
'name' => $crumb->name,
|
||||
'item' => url($crumb->url),
|
||||
])->all(),
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) !!}
|
||||
</script>
|
||||
@endif
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
|
||||
{{-- Minimal hero --}}
|
||||
@if(!empty($center_content))
|
||||
<x-centered-content :max="$center_max ?? '3xl'" class="pt-10 pb-6" style="padding-top:2.5rem;padding-bottom:1.5rem;">
|
||||
{{-- Breadcrumbs --}}
|
||||
@include('components.breadcrumbs', ['breadcrumbs' => $breadcrumbs ?? collect()])
|
||||
|
||||
<div class="mt-4">
|
||||
<h1 class="text-3xl font-bold text-white leading-tight">
|
||||
{{ $hero_title ?? $page_title ?? 'Skinbase' }}
|
||||
</h1>
|
||||
@isset($hero_description)
|
||||
<p class="mt-1 text-sm text-white/50 max-w-xl">{{ $hero_description }}</p>
|
||||
@endisset
|
||||
</div>
|
||||
</x-centered-content>
|
||||
|
||||
{{-- Page body (centered) --}}
|
||||
<x-centered-content :max="$center_max ?? '3xl'" class="pb-16">
|
||||
@yield('page-content')
|
||||
</x-centered-content>
|
||||
@else
|
||||
<div class="px-6 pt-10 pb-6 md:px-10">
|
||||
{{-- Breadcrumbs --}}
|
||||
@include('components.breadcrumbs', ['breadcrumbs' => $breadcrumbs ?? collect()])
|
||||
|
||||
<div class="mt-4">
|
||||
<h1 class="text-3xl font-bold text-white leading-tight">
|
||||
{{ $hero_title ?? $page_title ?? 'Skinbase' }}
|
||||
</h1>
|
||||
@isset($hero_description)
|
||||
<p class="mt-1 text-sm text-white/50 max-w-xl">{{ $hero_description }}</p>
|
||||
@endisset
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Page body --}}
|
||||
<div class="px-6 pb-16 md:px-10">
|
||||
@yield('page-content')
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@endsection
|
||||
47
resources/views/layouts/nova/discover-layout.blade.php
Normal file
47
resources/views/layouts/nova/discover-layout.blade.php
Normal file
@@ -0,0 +1,47 @@
|
||||
{{--
|
||||
DiscoverLayout — compact header + mode pills.
|
||||
Used by /discover/* pages.
|
||||
|
||||
Expected variables:
|
||||
$page_title, $description, $icon, $section
|
||||
Content via @yield('discover-content')
|
||||
--}}
|
||||
@extends('layouts.nova')
|
||||
|
||||
@push('head')
|
||||
@isset($page_canonical)
|
||||
<link rel="canonical" href="{{ $page_canonical }}" />
|
||||
@endisset
|
||||
<meta name="robots" content="{{ $page_robots ?? 'index,follow' }}">
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="{{ $page_canonical ?? url()->current() }}" />
|
||||
<meta property="og:title" content="{{ $page_title ?? 'Discover — Skinbase' }}" />
|
||||
<meta property="og:description" content="{{ $description ?? '' }}" />
|
||||
<meta property="og:site_name" content="Skinbase" />
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
|
||||
{{-- Compact header --}}
|
||||
<div class="px-6 pt-10 pb-6 md:px-10">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-widest text-white/30 mb-1">Discover</p>
|
||||
<h1 class="text-3xl font-bold text-white leading-tight flex items-center gap-3">
|
||||
<i class="fa-solid {{ $icon ?? 'fa-compass' }} text-sky-400 text-2xl"></i>
|
||||
{{ $page_title ?? 'Discover' }}
|
||||
</h1>
|
||||
@isset($description)
|
||||
<p class="mt-1 text-sm text-white/50">{{ $description }}</p>
|
||||
@endisset
|
||||
</div>
|
||||
|
||||
{{-- Mode pills --}}
|
||||
@include('web.discover._nav', ['section' => $section ?? ''])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Page body --}}
|
||||
@yield('discover-content')
|
||||
|
||||
@endsection
|
||||
166
resources/views/layouts/nova/explore-layout.blade.php
Normal file
166
resources/views/layouts/nova/explore-layout.blade.php
Normal file
@@ -0,0 +1,166 @@
|
||||
{{--
|
||||
ExploreLayout — hero header + mode tabs + filters + paginated grid.
|
||||
Used by /explore/*, /tag/:slug, and gallery pages.
|
||||
|
||||
Expected variables:
|
||||
$hero_title, $hero_description, $breadcrumbs (collection),
|
||||
$current_sort, $sort_options, $artworks,
|
||||
$contentTypes (collection, optional), $activeType (string, optional)
|
||||
$page_title, $page_meta_description, $page_canonical, $page_robots
|
||||
--}}
|
||||
@extends('layouts.nova')
|
||||
|
||||
@php
|
||||
use App\Banner;
|
||||
|
||||
$seoPage = max(1, (int) request()->query('page', 1));
|
||||
$seoBase = url()->current();
|
||||
$seoQ = request()->query(); unset($seoQ['page']);
|
||||
$seoUrl = fn(int $p) => $seoBase . ($p > 1
|
||||
? '?' . http_build_query(array_merge($seoQ, ['page' => $p]))
|
||||
: (count($seoQ) ? '?' . http_build_query($seoQ) : ''));
|
||||
$seoPrev = $seoPage > 1 ? $seoUrl($seoPage - 1) : null;
|
||||
$seoNext = (isset($artworks) && method_exists($artworks, 'nextPageUrl'))
|
||||
? $artworks->nextPageUrl() : null;
|
||||
@endphp
|
||||
|
||||
@push('head')
|
||||
<link rel="canonical" href="{{ $page_canonical ?? $seoUrl($seoPage) }}">
|
||||
@if($seoPrev)<link rel="prev" href="{{ $seoPrev }}">@endif
|
||||
@if($seoNext)<link rel="next" href="{{ $seoNext }}">@endif
|
||||
<meta name="robots" content="{{ $page_robots ?? 'index,follow' }}">
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="{{ $page_canonical ?? $seoUrl(1) }}" />
|
||||
<meta property="og:title" content="{{ $page_title ?? ($hero_title ?? 'Skinbase') }}" />
|
||||
<meta property="og:description" content="{{ $page_meta_description ?? '' }}" />
|
||||
<meta property="og:site_name" content="Skinbase" />
|
||||
|
||||
{{-- Breadcrumb structured data --}}
|
||||
@if(isset($breadcrumbs) && $breadcrumbs->isNotEmpty())
|
||||
<script type="application/ld+json">
|
||||
{!! json_encode([
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'BreadcrumbList',
|
||||
'itemListElement' => $breadcrumbs->values()->map(fn ($crumb, $i) => [
|
||||
'@type' => 'ListItem',
|
||||
'position' => $i + 1,
|
||||
'name' => $crumb->name,
|
||||
'item' => url($crumb->url),
|
||||
])->all(),
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) !!}
|
||||
</script>
|
||||
@endif
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid legacy-page">
|
||||
@php Banner::ShowResponsiveAd(); @endphp
|
||||
|
||||
<div class="pt-0">
|
||||
<div class="mx-auto w-full">
|
||||
<div class="relative min-h-[calc(100vh-64px)]">
|
||||
<main class="w-full">
|
||||
|
||||
{{-- ══ HERO HEADER ══ --}}
|
||||
<div class="relative overflow-hidden nb-hero-radial">
|
||||
<div class="absolute inset-0 nb-hero-gradient" aria-hidden="true"></div>
|
||||
<div class="absolute inset-0 opacity-20 bg-[radial-gradient(ellipse_80%_60%_at_50%_-10%,#E07A2130,transparent)]" aria-hidden="true"></div>
|
||||
|
||||
<div class="relative px-6 py-10 md:px-10 md:py-14">
|
||||
|
||||
{{-- Breadcrumbs --}}
|
||||
@include('components.breadcrumbs', ['breadcrumbs' => $breadcrumbs ?? collect()])
|
||||
|
||||
{{-- Title panel --}}
|
||||
<div class="mt-4 py-5">
|
||||
<h1 class="text-3xl md:text-4xl font-bold tracking-tight text-white/95 leading-tight">
|
||||
{{ $hero_title ?? 'Explore' }}
|
||||
</h1>
|
||||
@if(!empty($hero_description))
|
||||
<p class="mt-2 text-sm leading-6 text-neutral-400 max-w-xl">{!! $hero_description !!}</p>
|
||||
@endif
|
||||
|
||||
@if(is_object($artworks) && method_exists($artworks, 'total') && $artworks->total() > 0)
|
||||
<div class="mt-3 flex items-center gap-1.5 text-xs text-neutral-500">
|
||||
<svg class="h-3.5 w-3.5 text-accent/70" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>{{ number_format($artworks->total()) }} artworks</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Content type chips (Explore only) --}}
|
||||
@if(isset($contentTypes) && $contentTypes->isNotEmpty())
|
||||
<div class="flex flex-wrap gap-2 mt-2">
|
||||
@foreach($contentTypes as $ct)
|
||||
<a href="{{ $ct->url }}"
|
||||
class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium transition-colors
|
||||
{{ ($activeType ?? '') === $ct->slug
|
||||
? 'bg-sky-600 text-white'
|
||||
: 'bg-white/[0.05] text-white/60 hover:bg-white/[0.1] hover:text-white' }}">
|
||||
{{ $ct->name }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
<div class="absolute left-0 right-0 bottom-0 h-16 nb-hero-fade pointer-events-none" aria-hidden="true"></div>
|
||||
</div>
|
||||
|
||||
{{-- ══ RANKING TABS ══ --}}
|
||||
@php
|
||||
$rankingTabs = $sort_options ?? [
|
||||
['value' => 'trending', 'label' => '🔥 Trending'],
|
||||
['value' => 'new-hot', 'label' => '🚀 New & Hot'],
|
||||
['value' => 'best', 'label' => '⭐ Best'],
|
||||
['value' => 'latest', 'label' => '🕐 Latest'],
|
||||
];
|
||||
$activeTab = $current_sort ?? 'trending';
|
||||
@endphp
|
||||
|
||||
<div class="sticky top-0 z-30 border-b border-white/10 bg-nova-900/90 backdrop-blur-md" id="gallery-ranking-tabs">
|
||||
<div class="px-6 md:px-10">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<nav class="flex items-center gap-0 -mb-px nb-scrollbar-none overflow-x-auto" role="tablist">
|
||||
@foreach($rankingTabs as $tab)
|
||||
@php $isActive = $activeTab === $tab['value']; @endphp
|
||||
<button
|
||||
role="tab"
|
||||
aria-selected="{{ $isActive ? 'true' : 'false' }}"
|
||||
data-rank-tab="{{ $tab['value'] }}"
|
||||
class="gallery-rank-tab relative flex items-center gap-1.5 whitespace-nowrap px-5 py-4 text-sm font-medium transition-colors focus-visible:outline-none {{ $isActive ? 'text-white' : 'text-neutral-400 hover:text-white' }}"
|
||||
>
|
||||
{{ $tab['label'] }}
|
||||
<span class="nb-tab-indicator absolute bottom-0 left-0 right-0 h-0.5 {{ $isActive ? 'bg-accent scale-x-100' : 'bg-transparent scale-x-0' }} transition-transform duration-300 origin-left rounded-full"></span>
|
||||
</button>
|
||||
@endforeach
|
||||
</nav>
|
||||
|
||||
<button id="gallery-filter-panel-toggle" type="button"
|
||||
class="hidden md:flex items-center gap-2 shrink-0 rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm text-white/80 hover:bg-white/10 hover:text-white transition-colors"
|
||||
aria-haspopup="dialog" aria-expanded="false">
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2a1 1 0 01-.293.707L13 13.414V19a1 1 0 01-.553.894l-4 2A1 1 0 017 21v-7.586L3.293 6.707A1 1 0 013 6V4z" /></svg>
|
||||
Filters
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ══ ARTWORK GRID ══ --}}
|
||||
@yield('explore-grid')
|
||||
|
||||
{{-- ══ PAGINATION ══ --}}
|
||||
@if(is_object($artworks) && method_exists($artworks, 'links'))
|
||||
<div class="px-6 md:px-10 py-8 flex justify-center">
|
||||
{{ $artworks->withQueryString()->links() }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -7,12 +7,18 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-x-6 gap-y-2 text-sm text-neutral-400">
|
||||
<a class="hover:text-white" href="/bug-report">Bug Report</a>
|
||||
<a class="hover:text-white" href="/contact">Contact / Apply</a>
|
||||
<a class="hover:text-white" href="/rss-feeds">RSS Feeds</a>
|
||||
<a class="hover:text-white" href="/faq">FAQ</a>
|
||||
<a class="hover:text-white" href="/rules-and-guidelines">Rules and Guidelines</a>
|
||||
<a class="hover:text-white" href="/staff">Staff</a>
|
||||
<a class="hover:text-white" href="/privacy-policy">Privacy Policy</a>
|
||||
<a class="hover:text-white" href="/terms-of-service">Terms of Service</a>
|
||||
<button
|
||||
x-data
|
||||
@click="localStorage.removeItem('sb_cookie_consent'); window.location.reload()"
|
||||
class="hover:text-white cursor-pointer bg-transparent border-0 p-0 text-sm text-neutral-400"
|
||||
>Cookie Preferences</button>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-neutral-400">© 2026 Skinbase.org</div>
|
||||
|
||||
@@ -220,7 +220,9 @@
|
||||
$toolbarUsername = strtolower((string) (Auth::user()->username ?? ''));
|
||||
$routeUpload = Route::has('upload') ? route('upload') : '/upload';
|
||||
$routeDashboardFavorites = Route::has('dashboard.favorites') ? route('dashboard.favorites') : '/dashboard/favorites';
|
||||
$routeEditProfile = Route::has('settings') ? route('settings') : '/settings';
|
||||
$routeEditProfile = Route::has('dashboard.profile')
|
||||
? route('dashboard.profile')
|
||||
: (Route::has('settings') ? route('settings') : '/settings');
|
||||
$routePublicProfile = Route::has('profile.show') ? route('profile.show', ['username' => $toolbarUsername]) : '/@'.$toolbarUsername;
|
||||
@endphp
|
||||
|
||||
@@ -343,7 +345,7 @@
|
||||
<a class="flex items-center gap-3 py-2.5 px-3 rounded-lg hover:bg-white/5" href="{{ $mobileProfile }}">
|
||||
<i class="fa-solid fa-circle-user w-4 text-center text-sb-muted"></i>View Profile
|
||||
</a>
|
||||
<a class="flex items-center gap-3 py-2.5 px-3 rounded-lg hover:bg-white/5" href="{{ Route::has('settings') ? route('settings') : '/settings' }}">
|
||||
<a class="flex items-center gap-3 py-2.5 px-3 rounded-lg hover:bg-white/5" href="{{ Route::has('dashboard.profile') ? route('dashboard.profile') : (Route::has('settings') ? route('settings') : '/settings') }}">
|
||||
<i class="fa-solid fa-cog w-4 text-center text-sb-muted"></i>Settings
|
||||
</a>
|
||||
@if(in_array(strtolower((string) (Auth::user()->role ?? '')), ['admin', 'moderator'], true))
|
||||
|
||||
28
resources/views/rss/feed.blade.php
Normal file
28
resources/views/rss/feed.blade.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php echo '<?xml version="1.0" encoding="UTF-8"?>'; ?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
|
||||
<channel>
|
||||
<title>{{ htmlspecialchars($channelTitle) }}</title>
|
||||
<link>{{ $channelLink }}</link>
|
||||
<description>{{ htmlspecialchars($channelDescription) }}</description>
|
||||
<language>en-us</language>
|
||||
<lastBuildDate>{{ $buildDate }}</lastBuildDate>
|
||||
<atom:link href="{{ $feedUrl }}" rel="self" type="application/rss+xml" />
|
||||
@foreach ($artworks as $artwork)
|
||||
<item>
|
||||
<title><![CDATA[{{ $artwork->title }}]]></title>
|
||||
<link>{{ url('/art/' . $artwork->id . '/' . ($artwork->slug ?? '')) }}</link>
|
||||
<guid isPermaLink="true">{{ url('/art/' . $artwork->id . '/' . ($artwork->slug ?? '')) }}</guid>
|
||||
<pubDate>{{ $artwork->published_at?->toRfc2822String() }}</pubDate>
|
||||
<author><![CDATA[{{ $artwork->user?->username ?? 'Unknown' }}]]></author>
|
||||
@if ($artwork->description)
|
||||
<description><![CDATA[{{ strip_tags($artwork->description) }}]]></description>
|
||||
@endif
|
||||
@php $thumb = $artwork->thumbUrl('sm'); @endphp
|
||||
@if ($thumb)
|
||||
<media:thumbnail url="{{ $thumb }}" />
|
||||
<media:content url="{{ $thumb }}" medium="image" />
|
||||
@endif
|
||||
</item>
|
||||
@endforeach
|
||||
</channel>
|
||||
</rss>
|
||||
18
resources/views/settings.blade.php
Normal file
18
resources/views/settings.blade.php
Normal file
@@ -0,0 +1,18 @@
|
||||
@extends('layouts.nova')
|
||||
|
||||
@push('head')
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}" />
|
||||
@vite(['resources/js/settings.jsx'])
|
||||
<style>
|
||||
body.page-settings main { padding-top: 4rem; }
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.body.classList.add('page-settings')
|
||||
})
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
@inertia
|
||||
@endsection
|
||||
86
resources/views/web/apply.blade.php
Normal file
86
resources/views/web/apply.blade.php
Normal file
@@ -0,0 +1,86 @@
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@php
|
||||
$page_title = 'Apply to Join the Team';
|
||||
$hero_description = "We're always grateful for volunteers who want to help.";
|
||||
$center_content = true;
|
||||
$center_max = '3xl';
|
||||
@endphp
|
||||
|
||||
@section('page-content')
|
||||
<div class="max-w-3xl">
|
||||
@if(session('success'))
|
||||
<div class="mb-4 rounded-lg bg-emerald-800/20 border border-emerald-700 p-4 text-emerald-200">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form x-data='{ topic: @json(old('topic','apply')) }' method="POST" action="{{ route('contact.submit') }}" class="space-y-4">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-neutral-200">Reason for contact</label>
|
||||
<select name="topic" x-model="topic" class="mt-1 block w-full rounded bg-white/2 border border-white/10 p-2 text-white">
|
||||
<option value="apply" {{ old('topic') === 'apply' ? 'selected' : '' }}>Apply to join the team</option>
|
||||
<option value="bug" {{ old('topic') === 'bug' ? 'selected' : '' }}>Report a bug / site issue</option>
|
||||
<option value="contact" {{ old('topic') === 'contact' ? 'selected' : '' }}>General contact / question</option>
|
||||
<option value="other" {{ old('topic') === 'other' ? 'selected' : '' }}>Other</option>
|
||||
</select>
|
||||
@error('topic') <p class="text-rose-400 text-sm mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-neutral-200">Full name</label>
|
||||
<input name="name" value="{{ old('name') }}" class="mt-1 block w-full rounded bg-white/2 border border-white/10 p-2 text-white" required />
|
||||
@error('name') <p class="text-rose-400 text-sm mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-neutral-200">Email</label>
|
||||
<input name="email" value="{{ old('email') }}" type="email" class="mt-1 block w-full rounded bg-white/2 border border-white/10 p-2 text-white" required />
|
||||
@error('email') <p class="text-rose-400 text-sm mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div x-show="topic === 'apply'" x-cloak>
|
||||
<label class="block text-sm font-medium text-neutral-200">Role you're applying for</label>
|
||||
<input name="role" value="{{ old('role') }}" placeholder="e.g. Moderator, Community Manager" class="mt-1 block w-full rounded bg-white/2 border border-white/10 p-2 text-white" />
|
||||
@error('role') <p class="text-rose-400 text-sm mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div x-show="topic === 'apply'" x-cloak>
|
||||
<label class="block text-sm font-medium text-neutral-200">Portfolio / profile (optional)</label>
|
||||
<input name="portfolio" value="{{ old('portfolio') }}" placeholder="https://" class="mt-1 block w-full rounded bg-white/2 border border-white/10 p-2 text-white" />
|
||||
@error('portfolio') <p class="text-rose-400 text-sm mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-neutral-200">Tell us about yourself</label>
|
||||
<textarea name="message" rows="6" class="mt-1 block w-full rounded bg-white/2 border border-white/10 p-2 text-white">{{ old('message') }}</textarea>
|
||||
@error('message') <p class="text-rose-400 text-sm mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
{{-- Bug-specific fields --}}
|
||||
<div x-show="topic === 'bug'" x-cloak>
|
||||
<label class="block text-sm font-medium text-neutral-200">Affected URL (optional)</label>
|
||||
<input name="affected_url" value="{{ old('affected_url') }}" placeholder="https://" class="mt-1 block w-full rounded bg-white/2 border border-white/10 p-2 text-white" />
|
||||
@error('affected_url') <p class="text-rose-400 text-sm mt-1">{{ $message }}</p> @enderror
|
||||
|
||||
<label class="block text-sm font-medium text-neutral-200 mt-3">Steps to reproduce (optional)</label>
|
||||
<textarea name="steps" rows="4" class="mt-1 block w-full rounded bg-white/2 border border-white/10 p-2 text-white">{{ old('steps') }}</textarea>
|
||||
@error('steps') <p class="text-rose-400 text-sm mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
{{-- Honeypot field (hidden from real users) --}}
|
||||
<div style="display:none;" aria-hidden="true">
|
||||
<label>Website</label>
|
||||
<input type="text" name="website" value="" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end">
|
||||
<button type="submit" class="inline-flex items-center gap-2 rounded bg-sky-500 px-4 py-2 text-sm font-medium text-white hover:bg-sky-600">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="mt-6 text-sm text-neutral-400">By submitting this form you consent to Skinbase storing your application details for review.</p>
|
||||
</div>
|
||||
@endsection
|
||||
54
resources/views/web/blog/index.blade.php
Normal file
54
resources/views/web/blog/index.blade.php
Normal file
@@ -0,0 +1,54 @@
|
||||
{{--
|
||||
Blog index — uses ContentLayout.
|
||||
--}}
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@php
|
||||
$hero_title = 'Blog';
|
||||
$hero_description = 'News, tutorials and community stories from the Skinbase team.';
|
||||
@endphp
|
||||
|
||||
@section('page-content')
|
||||
|
||||
@if($posts->isNotEmpty())
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@foreach($posts as $post)
|
||||
<a href="{{ $post->url }}"
|
||||
class="group block rounded-xl border border-white/[0.06] bg-white/[0.02] overflow-hidden hover:bg-white/[0.05] transition-colors">
|
||||
@if($post->featured_image)
|
||||
<div class="aspect-video bg-nova-800 overflow-hidden">
|
||||
<img src="{{ $post->featured_image }}" alt="{{ $post->title }}" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" loading="lazy" />
|
||||
</div>
|
||||
@else
|
||||
<div class="aspect-video bg-gradient-to-br from-sky-900/30 to-purple-900/30 flex items-center justify-center">
|
||||
<i class="fa-solid fa-newspaper text-3xl text-white/20"></i>
|
||||
</div>
|
||||
@endif
|
||||
<div class="p-5">
|
||||
<h2 class="text-lg font-semibold text-white group-hover:text-sky-300 transition-colors line-clamp-2">
|
||||
{{ $post->title }}
|
||||
</h2>
|
||||
@if($post->excerpt)
|
||||
<p class="mt-2 text-sm text-white/50 line-clamp-3">{{ $post->excerpt }}</p>
|
||||
@endif
|
||||
@if($post->published_at)
|
||||
<time class="mt-3 block text-xs text-white/30" datetime="{{ $post->published_at->toIso8601String() }}">
|
||||
{{ $post->published_at->format('M j, Y') }}
|
||||
</time>
|
||||
@endif
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="mt-10 flex justify-center">
|
||||
{{ $posts->withQueryString()->links() }}
|
||||
</div>
|
||||
@else
|
||||
<div class="rounded-xl border border-white/[0.06] bg-white/[0.02] px-8 py-12 text-center">
|
||||
<i class="fa-solid fa-newspaper text-4xl text-white/20 mb-4"></i>
|
||||
<p class="text-white/40 text-sm">No blog posts published yet. Check back soon!</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@endsection
|
||||
60
resources/views/web/blog/show.blade.php
Normal file
60
resources/views/web/blog/show.blade.php
Normal file
@@ -0,0 +1,60 @@
|
||||
{{--
|
||||
Blog post — uses ContentLayout.
|
||||
--}}
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@php
|
||||
$hero_title = $post->title;
|
||||
@endphp
|
||||
|
||||
@push('head')
|
||||
{{-- Article structured data --}}
|
||||
<script type="application/ld+json">
|
||||
{!! json_encode([
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'Article',
|
||||
'headline' => $post->title,
|
||||
'datePublished' => $post->published_at?->toIso8601String(),
|
||||
'dateModified' => $post->updated_at?->toIso8601String(),
|
||||
'author' => [
|
||||
'@type' => 'Organization',
|
||||
'name' => 'Skinbase',
|
||||
],
|
||||
'publisher' => [
|
||||
'@type' => 'Organization',
|
||||
'name' => 'Skinbase',
|
||||
],
|
||||
'description' => $post->meta_description ?: $post->excerpt ?: '',
|
||||
'mainEntityOfPage' => $post->url,
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) !!}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@section('page-content')
|
||||
|
||||
<article class="max-w-3xl">
|
||||
@if($post->featured_image)
|
||||
<div class="rounded-xl overflow-hidden mb-8">
|
||||
<img src="{{ $post->featured_image }}" alt="{{ $post->title }}" class="w-full" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($post->published_at)
|
||||
<time class="block text-sm text-white/40 mb-4" datetime="{{ $post->published_at->toIso8601String() }}">
|
||||
{{ $post->published_at->format('F j, Y') }}
|
||||
</time>
|
||||
@endif
|
||||
|
||||
<div class="prose prose-invert prose-headings:text-white prose-a:text-sky-400 prose-p:text-white/70 max-w-none">
|
||||
{!! $post->body !!}
|
||||
</div>
|
||||
|
||||
<div class="mt-12 pt-8 border-t border-white/10">
|
||||
<a href="/blog" class="inline-flex items-center gap-2 text-sm text-sky-400 hover:text-sky-300 transition-colors">
|
||||
<i class="fa-solid fa-arrow-left text-xs"></i>
|
||||
Back to Blog
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@endsection
|
||||
75
resources/views/web/bug-report.blade.php
Normal file
75
resources/views/web/bug-report.blade.php
Normal file
@@ -0,0 +1,75 @@
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@section('page-content')
|
||||
|
||||
@if ($success)
|
||||
<div class="mb-6 rounded-lg bg-green-500/10 border border-green-500/30 text-green-400 px-5 py-4 text-sm">
|
||||
Your report was submitted successfully. Thank you — we'll look into it as soon as possible.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="max-w-2xl">
|
||||
@guest
|
||||
<div class="rounded-lg bg-nova-800 border border-neutral-700 px-6 py-8 text-center">
|
||||
<svg class="mx-auto mb-3 h-10 w-10 text-neutral-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||
d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"/>
|
||||
</svg>
|
||||
<p class="text-neutral-400 text-sm mb-4">You need to be signed in to submit a bug report.</p>
|
||||
<a href="{{ route('login') }}" class="inline-block rounded-md bg-accent px-5 py-2 text-sm font-medium text-white hover:opacity-90">
|
||||
Sign In
|
||||
</a>
|
||||
</div>
|
||||
@else
|
||||
<p class="text-neutral-400 text-sm mb-6">
|
||||
Found a bug or have a suggestion? Fill out the form below and our team will review it.
|
||||
For security issues, please contact us directly via email.
|
||||
</p>
|
||||
|
||||
<form action="{{ route('bug-report.submit') }}" method="POST" class="space-y-5">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label for="subject" class="block text-sm font-medium text-neutral-300 mb-1">Subject</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
name="subject"
|
||||
required
|
||||
maxlength="255"
|
||||
value="{{ old('subject') }}"
|
||||
placeholder="Brief summary of the issue"
|
||||
class="w-full rounded-md bg-nova-800 border border-neutral-700 px-4 py-2.5 text-sm text-white placeholder-neutral-500 focus:border-accent focus:outline-none focus:ring-1 focus:ring-accent"
|
||||
>
|
||||
@error('subject')
|
||||
<p class="mt-1 text-xs text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="description" class="block text-sm font-medium text-neutral-300 mb-1">Description</label>
|
||||
<textarea
|
||||
id="description"
|
||||
name="description"
|
||||
required
|
||||
rows="7"
|
||||
maxlength="5000"
|
||||
placeholder="Describe the bug in detail — steps to reproduce, what you expected, and what actually happened."
|
||||
class="w-full rounded-md bg-nova-800 border border-neutral-700 px-4 py-2.5 text-sm text-white placeholder-neutral-500 focus:border-accent focus:outline-none focus:ring-1 focus:ring-accent resize-y"
|
||||
>{{ old('description') }}</textarea>
|
||||
@error('description')
|
||||
<p class="mt-1 text-xs text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit"
|
||||
class="rounded-md bg-accent px-6 py-2.5 text-sm font-medium text-white hover:opacity-90 transition-opacity">
|
||||
Submit Report
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@endguest
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
@@ -38,7 +38,7 @@
|
||||
$profileUrl = ($creator->username ?? null)
|
||||
? '/@' . $creator->username
|
||||
: '/profile/' . (int) $creator->user_id;
|
||||
$avatarUrl = \App\Support\AvatarUrl::forUser((int) $creator->user_id, null, 40);
|
||||
$avatarUrl = \App\Support\AvatarUrl::forUser((int) $creator->user_id, $creator->avatar_hash ?? null, 40);
|
||||
@endphp
|
||||
<div class="grid grid-cols-[3rem_1fr_auto] items-center gap-4 px-5 py-4
|
||||
{{ $rank <= 3 ? 'bg-white/[0.015]' : '' }} hover:bg-white/[0.03] transition-colors">
|
||||
@@ -62,7 +62,7 @@
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-white truncate">{{ $creator->uname }}</p>
|
||||
@if($creator->username ?? null)
|
||||
<p class="text-xs text-white/40 truncate">@{{ $creator->username }}</p>
|
||||
<p class="text-xs text-white/40 truncate">{{ '@' . $creator->username }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</a>
|
||||
|
||||
73
resources/views/web/explore/index.blade.php
Normal file
73
resources/views/web/explore/index.blade.php
Normal file
@@ -0,0 +1,73 @@
|
||||
{{--
|
||||
Explore index — uses ExploreLayout.
|
||||
Displays an artwork grid with hero header, mode tabs, pagination.
|
||||
--}}
|
||||
@extends('layouts.nova.explore-layout')
|
||||
|
||||
@section('explore-grid')
|
||||
|
||||
{{-- ══ EGS §11: FEATURED SPOTLIGHT ROW ══ --}}
|
||||
@if(!empty($spotlight) && $spotlight->isNotEmpty())
|
||||
<div class="px-6 md:px-10 pt-6 pb-2">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<span class="text-xs font-semibold uppercase tracking-widest text-amber-400">✦ Featured Today</span>
|
||||
<span class="flex-1 border-t border-white/10"></span>
|
||||
</div>
|
||||
<div class="flex gap-4 overflow-x-auto nb-scrollbar-none pb-2">
|
||||
@foreach($spotlight as $item)
|
||||
<a href="{{ $item->slug ? route('artwork.show', $item->slug) : '#' }}"
|
||||
class="group relative flex-none w-44 md:w-52 rounded-xl overflow-hidden
|
||||
bg-neutral-800 border border-white/10 hover:border-amber-400/40
|
||||
hover:shadow-lg hover:shadow-amber-500/10 transition-all duration-200"
|
||||
title="{{ $item->name ?? '' }}">
|
||||
|
||||
{{-- Thumbnail --}}
|
||||
<div class="aspect-[4/3] overflow-hidden bg-neutral-900">
|
||||
<img
|
||||
src="{{ $item->thumb_url ?? '' }}"
|
||||
@if(!empty($item->thumb_srcset)) srcset="{{ $item->thumb_srcset }}" @endif
|
||||
alt="{{ $item->name ?? 'Featured artwork' }}"
|
||||
loading="lazy"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{-- Label overlay --}}
|
||||
<div class="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/80 to-transparent px-3 py-2">
|
||||
<p class="text-xs font-medium text-white truncate leading-snug">{{ $item->name ?? '' }}</p>
|
||||
@if(!empty($item->uname))
|
||||
<p class="text-[10px] text-neutral-400 truncate">@{{ $item->uname }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@php
|
||||
$galleryArtworks = collect($artworks->items())->map(fn ($art) => [
|
||||
'id' => $art->id,
|
||||
'name' => $art->name ?? null,
|
||||
'thumb' => $art->thumb_url ?? $art->thumb ?? null,
|
||||
'thumb_srcset' => $art->thumb_srcset ?? null,
|
||||
'uname' => $art->uname ?? '',
|
||||
'username' => $art->username ?? $art->uname ?? '',
|
||||
'avatar_url' => $art->avatar_url ?? null,
|
||||
'category_name' => $art->category_name ?? '',
|
||||
'category_slug' => $art->category_slug ?? '',
|
||||
'slug' => $art->slug ?? '',
|
||||
'width' => $art->width ?? null,
|
||||
'height' => $art->height ?? null,
|
||||
])->values();
|
||||
@endphp
|
||||
|
||||
<div
|
||||
data-react-masonry-gallery
|
||||
data-artworks="{{ json_encode($galleryArtworks) }}"
|
||||
data-gallery-type="explore"
|
||||
data-limit="24"
|
||||
class="min-h-32 px-6 md:px-10 py-6"
|
||||
></div>
|
||||
|
||||
@endsection
|
||||
332
resources/views/web/faq.blade.php
Normal file
332
resources/views/web/faq.blade.php
Normal file
@@ -0,0 +1,332 @@
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@section('page-content')
|
||||
|
||||
<div class="max-w-3xl space-y-10">
|
||||
|
||||
{{-- Intro --}}
|
||||
<div>
|
||||
<p class="text-neutral-400 text-sm mb-2">Last updated: March 1, 2026</p>
|
||||
<p class="text-neutral-300 leading-relaxed">
|
||||
Answers to the questions we hear most often. If something isn't covered here, feel free to reach out to
|
||||
any <a href="/staff" class="text-sky-400 hover:underline">staff member</a> — we're happy to help.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{-- Table of Contents --}}
|
||||
<nav class="rounded-xl border border-white/10 bg-white/[0.03] p-5">
|
||||
<p class="text-xs font-semibold uppercase tracking-widest text-neutral-400 mb-3">Contents</p>
|
||||
<ol class="space-y-1.5 text-sm text-sky-400">
|
||||
<li><a href="#about" class="hover:underline">01 — About Skinbase</a></li>
|
||||
<li><a href="#uploading" class="hover:underline">02 — Uploading & Submissions</a></li>
|
||||
<li><a href="#copyright" class="hover:underline">03 — Copyright & Photoskins</a></li>
|
||||
<li><a href="#skinning" class="hover:underline">04 — Skinning Help</a></li>
|
||||
<li><a href="#account" class="hover:underline">05 — Account & Profile</a></li>
|
||||
<li><a href="#community" class="hover:underline">06 — Community & Forums</a></li>
|
||||
<li><a href="#policies" class="hover:underline">07 — Policies & Conduct</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
{{-- 01 About Skinbase --}}
|
||||
<section id="about">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">01</span>
|
||||
About Skinbase
|
||||
</h2>
|
||||
<dl class="space-y-6">
|
||||
<div>
|
||||
<dt class="font-medium text-white">What is Skinbase?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Skinbase is a community gallery for desktop customisation — skins, themes, wallpapers, icons, and
|
||||
more. Members upload their own creations, collect favourites, leave feedback, and discuss all things
|
||||
design in the forums. We've been online since 2001 and still going strong.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">Is Skinbase free to use?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Yes, completely. Browsing and downloading are free without an account. Registering (also free)
|
||||
unlocks uploading, commenting, favourites, collections, and messaging.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">Who runs Skinbase?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Skinbase is maintained by a small volunteer <a href="/staff" class="text-sky-400 hover:underline">staff team</a>.
|
||||
Staff moderate uploads, help members, and keep the lights on. There is no corporate ownership —
|
||||
this is a community project.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">How can I support Skinbase?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
The best support is participation — upload your work, leave constructive comments, report problems,
|
||||
and invite other creators. You can also help by flagging rule-breaking content so staff can review it quickly.
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{{-- 02 Uploading & Submissions --}}
|
||||
<section id="uploading">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">02</span>
|
||||
Uploading & Submissions
|
||||
</h2>
|
||||
<dl class="space-y-6">
|
||||
<div>
|
||||
<dt class="font-medium text-white">What file types are accepted?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Skins and resources are generally uploaded as <strong class="text-white">.zip</strong> archives.
|
||||
Preview images are accepted as JPG, PNG, or WebP. Wallpapers may be uploaded directly as image files.
|
||||
Check the upload form for the exact size and type limits per category.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">Is there a file size limit?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Yes. The current limit is displayed on the upload page. If your file exceeds the limit, try removing
|
||||
any unnecessary assets from the archive before re-uploading.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">Why was my upload removed?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Uploads are removed when they break the <a href="/rules-and-guidelines" class="text-sky-400 hover:underline">Rules & Guidelines</a> —
|
||||
most commonly for containing photographs you don't own (photoskins), missing preview images, or
|
||||
violating copyright. You will usually receive a message explaining the reason.
|
||||
If you believe a removal was in error, contact a staff member.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">Can I upload work-in-progress skins?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
You may share works-in-progress in the forums for feedback. The main gallery is intended for
|
||||
finished, download-ready submissions only.
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{{-- 03 Copyright & Photoskins --}}
|
||||
<section id="copyright">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">03</span>
|
||||
Copyright & Photoskins
|
||||
</h2>
|
||||
<dl class="space-y-6">
|
||||
<div>
|
||||
<dt class="font-medium text-white">What is a photoskin?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
A photoskin is a skin that uses photographic images — typically sourced from the internet — as its
|
||||
primary visual element. Because those photos belong to the photographer (or their publisher), using
|
||||
them without permission is copyright infringement.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">Can I upload photoskins?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
<strong class="text-white">No.</strong> Photoskins are not allowed on Skinbase. All artwork in a
|
||||
skin must be created by you, or you must include written proof of permission from the original
|
||||
artist inside the zip file. Stock images with a licence that explicitly permits use in
|
||||
redistributed works are allowed — include a copy of that licence in the zip.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">Can I base my skin on someone else's artwork?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Only with documented permission. Include the permission statement (email, forum post, etc.) inside
|
||||
your zip file and note it in your upload description. Staff may still remove the work if we cannot
|
||||
verify the permission.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">Someone uploaded my artwork without permission. What do I do?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Use the Report button on the artwork's page, or contact a
|
||||
<a href="/staff" class="text-sky-400 hover:underline">staff member</a> directly.
|
||||
Provide a link to the infringing upload and evidence that you are the copyright holder.
|
||||
We take copyright complaints seriously and act promptly.
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{{-- 04 Skinning Help --}}
|
||||
<section id="skinning">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">04</span>
|
||||
Skinning Help
|
||||
</h2>
|
||||
<dl class="space-y-6">
|
||||
<div>
|
||||
<dt class="font-medium text-white">How do I make a skin?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Every application is different, but skins generally consist of a folder of images and small
|
||||
text/config files. A good starting point is to unpack an existing skin (many Winamp skins are
|
||||
simply renamed <code class="text-sky-300">.zip</code> files), study the structure, then replace the
|
||||
images with your own artwork. Check the application's official documentation for its exact format.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">How do I apply a Windows theme?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
To change the visual style of Windows you typically need a third-party tool such as
|
||||
<strong class="text-white">WindowBlinds</strong>, <strong class="text-white">SecureUxTheme</strong>,
|
||||
or a patched <code class="text-sky-300">uxtheme.dll</code>. Install your chosen tool, download a
|
||||
compatible theme from Skinbase, then follow the tool's instructions to apply it.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">Where can I get help with a specific application?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
The forums are the best place — there are dedicated sections for popular skinnable applications.
|
||||
You can also check the comments on popular skins for tips from other members.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">What image editing software do skinners use?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
The community uses a wide range of tools. Popular choices include
|
||||
<strong class="text-white">Adobe Photoshop</strong>, <strong class="text-white">GIMP</strong> (free),
|
||||
<strong class="text-white">Affinity Designer</strong>, <strong class="text-white">Figma</strong>,
|
||||
and <strong class="text-white">Krita</strong> (free). The best tool is the one you're comfortable with.
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{{-- 05 Account & Profile --}}
|
||||
<section id="account">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">05</span>
|
||||
Account & Profile
|
||||
</h2>
|
||||
<dl class="space-y-6">
|
||||
<div>
|
||||
<dt class="font-medium text-white">How do I set a profile picture?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Go to <strong class="text-white">Settings → Avatar</strong>, choose an image from your device, and
|
||||
save. Your avatar appears on your profile page and next to all your comments.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">Can I change my username?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Username changes are handled by staff. Send a message to a
|
||||
<a href="/staff" class="text-sky-400 hover:underline">staff member</a> with your requested new name.
|
||||
We reserve the right to decline requests that are inappropriate or conflict with an existing account.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">How do I delete my account?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Account deletion requests must be sent to staff. Please be aware that your publicly submitted
|
||||
artwork may remain in the gallery under your username unless you also request removal of specific
|
||||
uploads. See our <a href="/privacy-policy" class="text-sky-400 hover:underline">Privacy Policy</a>
|
||||
for details on data retention.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">I forgot my password. How do I reset it?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Use the <strong class="text-white">Forgot password?</strong> link on the login page. An email with
|
||||
a reset link will be sent to the address on your account. If you no longer have access to that
|
||||
email, contact a staff member for assistance.
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{{-- 06 Community & Forums --}}
|
||||
<section id="community">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">06</span>
|
||||
Community & Forums
|
||||
</h2>
|
||||
<dl class="space-y-6">
|
||||
<div>
|
||||
<dt class="font-medium text-white">Do I need an account to use the forums?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Guests can read most forum threads without an account. Posting, replying, and creating new topics
|
||||
require a registered account.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">Can I promote my own work in the forums?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Yes — there are dedicated showcase and feedback sections. Limit self-promotion to those areas and
|
||||
avoid spamming multiple threads with the same content.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">How do I report a bad comment or forum post?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Every comment and post has a Report link. Use it to flag content that breaks the rules; staff will
|
||||
review it promptly. For urgent issues, message a
|
||||
<a href="/staff" class="text-sky-400 hover:underline">staff member</a> directly.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">What are the messaging rules?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Private messaging is for genuine one-to-one communication. Do not use it to harass, solicit, or
|
||||
send unsolicited promotional material. Violations can result in messaging being disabled on your account.
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{{-- 07 Policies & Conduct --}}
|
||||
<section id="policies">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">07</span>
|
||||
Policies & Conduct
|
||||
</h2>
|
||||
<dl class="space-y-6">
|
||||
<div>
|
||||
<dt class="font-medium text-white">Are there many rules to follow?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
We keep the rules straightforward: respect everyone, only upload work you own or have permission to
|
||||
share, and keep it drama-free. The full list is in our
|
||||
<a href="/rules-and-guidelines" class="text-sky-400 hover:underline">Rules & Guidelines</a>.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">What happens if I break the rules?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Depending on severity, staff may issue a warning, remove the offending content, temporarily
|
||||
restrict your account, or permanently ban you. Serious offences (harassment, illegal content)
|
||||
result in an immediate permanent ban with no prior warning.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">How do I appeal a ban or removed upload?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Contact a senior staff member and explain the situation calmly. Provide any supporting evidence.
|
||||
Staff decisions can be reversed when new information comes to light, but appeals submitted
|
||||
aggressively or repeatedly will not be reconsidered.
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-white">I still can't find what I need. What now?</dt>
|
||||
<dd class="mt-1.5 text-sm text-neutral-400 leading-relaxed">
|
||||
Send a private message to any <a href="/staff" class="text-sky-400 hover:underline">staff member</a>
|
||||
or post in the Help section of the forums. Someone from the community will usually respond
|
||||
quickly.
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{{-- Footer note --}}
|
||||
<div class="rounded-xl border border-white/10 bg-white/[0.03] p-5 text-sm text-neutral-400 leading-relaxed">
|
||||
This FAQ is reviewed periodically. For legal matters such as copyright, data, or account deletion,
|
||||
please refer to our <a href="/privacy-policy" class="text-sky-400 hover:underline">Privacy Policy</a>
|
||||
and <a href="/rules-and-guidelines" class="text-sky-400 hover:underline">Rules & Guidelines</a>,
|
||||
or contact the <a href="/staff" class="text-sky-400 hover:underline">staff team</a> directly.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
18
resources/views/web/pages/show.blade.php
Normal file
18
resources/views/web/pages/show.blade.php
Normal file
@@ -0,0 +1,18 @@
|
||||
{{--
|
||||
Static page — uses ContentLayout.
|
||||
--}}
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@php
|
||||
$hero_title = $page->title;
|
||||
@endphp
|
||||
|
||||
@section('page-content')
|
||||
|
||||
<article class="max-w-3xl">
|
||||
<div class="prose prose-invert prose-headings:text-white prose-a:text-sky-400 prose-p:text-white/70 max-w-none">
|
||||
{!! $page->body !!}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@endsection
|
||||
392
resources/views/web/privacy-policy.blade.php
Normal file
392
resources/views/web/privacy-policy.blade.php
Normal file
@@ -0,0 +1,392 @@
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@section('page-content')
|
||||
|
||||
{{-- Table of contents --}}
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm text-white/40 mb-1">Last updated: <time datetime="2026-03-01">March 1, 2026</time></p>
|
||||
<p class="text-white/60 text-sm leading-relaxed mb-8">
|
||||
This Privacy Policy explains how Skinbase ("we", "us", "our") collects, uses, stores, and protects
|
||||
information about you when you use our website at <strong class="text-white">skinbase.org</strong>.
|
||||
By using Skinbase you agree to the practices described in this policy.
|
||||
</p>
|
||||
|
||||
{{-- TOC --}}
|
||||
<nav class="mb-10 rounded-xl border border-white/[0.08] bg-white/[0.03] px-6 py-5">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-widest text-white/40 mb-3">Contents</h2>
|
||||
<ol class="space-y-1.5 text-sm text-sky-400">
|
||||
<li><a href="#information-we-collect" class="hover:text-sky-300 hover:underline transition-colors">1. Information We Collect</a></li>
|
||||
<li><a href="#how-we-use-information" class="hover:text-sky-300 hover:underline transition-colors">2. How We Use Your Information</a></li>
|
||||
<li><a href="#cookies" class="hover:text-sky-300 hover:underline transition-colors">3. Cookies & Tracking</a></li>
|
||||
<li><a href="#sharing" class="hover:text-sky-300 hover:underline transition-colors">4. Sharing of Information</a></li>
|
||||
<li><a href="#user-content" class="hover:text-sky-300 hover:underline transition-colors">5. User-Generated Content</a></li>
|
||||
<li><a href="#data-retention" class="hover:text-sky-300 hover:underline transition-colors">6. Data Retention</a></li>
|
||||
<li><a href="#security" class="hover:text-sky-300 hover:underline transition-colors">7. Security</a></li>
|
||||
<li><a href="#your-rights" class="hover:text-sky-300 hover:underline transition-colors">8. Your Rights</a></li>
|
||||
<li><a href="#advertising" class="hover:text-sky-300 hover:underline transition-colors">9. Advertising</a></li>
|
||||
<li><a href="#third-party-links" class="hover:text-sky-300 hover:underline transition-colors">10. Third-Party Links</a></li>
|
||||
<li><a href="#children" class="hover:text-sky-300 hover:underline transition-colors">11. Children's Privacy</a></li>
|
||||
<li><a href="#changes" class="hover:text-sky-300 hover:underline transition-colors">12. Changes to This Policy</a></li>
|
||||
<li><a href="#contact" class="hover:text-sky-300 hover:underline transition-colors">13. Contact Us</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
{{-- Sections --}}
|
||||
<div class="space-y-10">
|
||||
|
||||
{{-- 1 --}}
|
||||
<section id="information-we-collect">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">01</span>
|
||||
Information We Collect
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-3">
|
||||
We collect information in two ways: information you give us directly, and information
|
||||
collected automatically as you use the site.
|
||||
</p>
|
||||
<h3 class="text-base font-semibold text-white mt-5 mb-2">Information you provide</h3>
|
||||
<ul class="list-disc list-inside space-y-1.5 text-sm text-white/70 pl-2">
|
||||
<li><strong class="text-white/90">Account registration</strong> — username, email address, and password (stored as a secure hash).</li>
|
||||
<li><strong class="text-white/90">Profile information</strong> — display name, avatar, bio, website URL, and location if you choose to provide them.</li>
|
||||
<li><strong class="text-white/90">Uploaded content</strong> — artworks, wallpapers, skins, and photographs, along with their titles, descriptions, and tags.</li>
|
||||
<li><strong class="text-white/90">Communications</strong> — messages sent through features such as private messaging, forum posts, comments, and bug reports.</li>
|
||||
</ul>
|
||||
<h3 class="text-base font-semibold text-white mt-5 mb-2">Information collected automatically</h3>
|
||||
<ul class="list-disc list-inside space-y-1.5 text-sm text-white/70 pl-2">
|
||||
<li><strong class="text-white/90">Log data</strong> — IP address, browser type and version, operating system, referring URL, pages visited, and timestamps.</li>
|
||||
<li><strong class="text-white/90">Usage data</strong> — download counts, favourite actions, search queries, and interaction events used to improve recommendations.</li>
|
||||
<li><strong class="text-white/90">Cookies & local storage</strong> — see Section 3 for full details.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{{-- 2 --}}
|
||||
<section id="how-we-use-information">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">02</span>
|
||||
How We Use Your Information
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-3">We use the information we collect to:</p>
|
||||
<ul class="list-disc list-inside space-y-1.5 text-sm text-white/70 pl-2">
|
||||
<li>Provide, operate, and maintain the Skinbase service.</li>
|
||||
<li>Authenticate your identity and keep your account secure.</li>
|
||||
<li>Personalise your experience, including content recommendations.</li>
|
||||
<li>Send transactional emails (password resets, email verification, notifications you subscribe to).</li>
|
||||
<li>Moderate content and enforce our <a href="/rules-and-guidelines" class="text-sky-400 hover:underline">Rules & Guidelines</a>.</li>
|
||||
<li>Analyse usage patterns to improve site performance and features.</li>
|
||||
<li>Detect, prevent, and investigate fraud, abuse, or security incidents.</li>
|
||||
<li>Comply with legal obligations.</li>
|
||||
</ul>
|
||||
<p class="mt-4 text-sm text-white/50">
|
||||
We will never sell your personal data or use it for purposes materially different from those
|
||||
stated above without first obtaining your explicit consent.
|
||||
</p>
|
||||
|
||||
{{-- Lawful basis table (GDPR Art. 13(1)(c)) --}}
|
||||
<h3 class="text-base font-semibold text-white mt-6 mb-3">Lawful basis for processing (GDPR Art. 6)</h3>
|
||||
<div class="overflow-hidden rounded-lg border border-white/[0.08]">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-white/[0.05]">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-white/40">Processing activity</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-white/40">Lawful basis</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/[0.05]">
|
||||
<tr class="bg-white/[0.02]">
|
||||
<td class="px-4 py-3 text-white/80">Account registration & authentication</td>
|
||||
<td class="px-4 py-3 text-white/60">Art. 6(1)(b) — Performance of contract</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-white/80">Delivering and operating the Service</td>
|
||||
<td class="px-4 py-3 text-white/60">Art. 6(1)(b) — Performance of contract</td>
|
||||
</tr>
|
||||
<tr class="bg-white/[0.02]">
|
||||
<td class="px-4 py-3 text-white/80">Transactional emails (password reset, verification)</td>
|
||||
<td class="px-4 py-3 text-white/60">Art. 6(1)(b) — Performance of contract</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-white/80">Security, fraud prevention, abuse detection</td>
|
||||
<td class="px-4 py-3 text-white/60">Art. 6(1)(f) — Legitimate interests</td>
|
||||
</tr>
|
||||
<tr class="bg-white/[0.02]">
|
||||
<td class="px-4 py-3 text-white/80">Analytics & site-performance monitoring</td>
|
||||
<td class="px-4 py-3 text-white/60">Art. 6(1)(f) — Legitimate interests</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-white/80">Essential cookies (session, CSRF, remember-me)</td>
|
||||
<td class="px-4 py-3 text-white/60">Art. 6(1)(f) — Legitimate interests</td>
|
||||
</tr>
|
||||
<tr class="bg-white/[0.02]">
|
||||
<td class="px-4 py-3 text-white/80">Third-party advertising cookies</td>
|
||||
<td class="px-4 py-3 text-white/60">Art. 6(1)(a) — <strong class="text-white/90">Consent</strong> (via cookie banner)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-white/80">Compliance with legal obligations</td>
|
||||
<td class="px-4 py-3 text-white/60">Art. 6(1)(c) — Legal obligation</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- 3 --}}
|
||||
<section id="cookies">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">03</span>
|
||||
Cookies & Tracking
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-4">
|
||||
Skinbase uses cookies — small text files stored in your browser — to deliver a reliable,
|
||||
personalised experience. No cookies are linked to sensitive personal data.
|
||||
</p>
|
||||
<div class="overflow-hidden rounded-lg border border-white/[0.08]">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-white/[0.05]">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-white/40">Cookie</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-white/40">Purpose</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-white/40">Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/[0.05]">
|
||||
<tr class="bg-white/[0.02]">
|
||||
<td class="px-4 py-3 text-white/80 font-mono text-xs">skinbase_session</td>
|
||||
<td class="px-4 py-3 text-white/60">Authentication session identifier</td>
|
||||
<td class="px-4 py-3 text-white/50">Browser session</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-white/80 font-mono text-xs">XSRF-TOKEN</td>
|
||||
<td class="px-4 py-3 text-white/60">Cross-site request forgery protection</td>
|
||||
<td class="px-4 py-3 text-white/50">Browser session</td>
|
||||
</tr>
|
||||
<tr class="bg-white/[0.02]">
|
||||
<td class="px-4 py-3 text-white/80 font-mono text-xs">remember_web_*</td>
|
||||
<td class="px-4 py-3 text-white/60">"Remember me" persistent login</td>
|
||||
<td class="px-4 py-3 text-white/50">30 days</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-white/80 font-mono text-xs">__gads, ar_debug,<br>DSID, IDE, NID</td>
|
||||
<td class="px-4 py-3 text-white/60">Google AdSense — interest-based ad targeting & frequency capping. Only loaded after you accept cookies. (See Section 9)</td>
|
||||
<td class="px-4 py-3 text-white/50">Up to 13 months</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="mt-3 text-sm text-white/50">
|
||||
You can disable cookies in your browser settings. Doing so may prevent some features
|
||||
(such as staying logged in) from working correctly.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 4 --}}
|
||||
<section id="sharing">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">04</span>
|
||||
Sharing of Information
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed">
|
||||
We do not sell or rent your personal data. We may share information only in the following
|
||||
limited circumstances:
|
||||
</p>
|
||||
<ul class="mt-3 list-disc list-inside space-y-1.5 text-sm text-white/70 pl-2">
|
||||
<li><strong class="text-white/90">Legal requirements</strong> — if required by law, court order, or governmental authority.</li>
|
||||
<li><strong class="text-white/90">Protection of rights</strong> — to enforce our policies, prevent fraud, or protect the safety of our users or the public.</li>
|
||||
<li><strong class="text-white/90">Service providers</strong> — trusted third-party vendors (e.g. hosting, email delivery, analytics) who are contractually bound to handle data only as instructed by us.</li>
|
||||
<li><strong class="text-white/90">Business transfers</strong> — in the event of a merger, acquisition, or sale of assets, you will be notified via email and/or a prominent notice on the site.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{{-- 5 --}}
|
||||
<section id="user-content">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">05</span>
|
||||
User-Generated Content
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed">
|
||||
Artworks, comments, forum posts, and other content you upload or publish on Skinbase are
|
||||
publicly visible. Do not include personal information (phone numbers, home addresses, etc.)
|
||||
in public content. You retain ownership of your original work; by uploading you grant
|
||||
Skinbase a non-exclusive licence to display and distribute it as part of the service.
|
||||
You may delete your own content at any time from your dashboard.
|
||||
</p>
|
||||
<p class="mt-3 text-sm text-white/50">
|
||||
Content found to infringe copyright or violate our rules will be removed.
|
||||
To report a submission, please <a href="/bug-report" class="text-sky-400 hover:underline">contact a staff member</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 6 --}}
|
||||
<section id="data-retention">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">06</span>
|
||||
Data Retention
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed">
|
||||
We retain your account data for as long as your account is active. If you delete your
|
||||
account, we will remove or anonymise your personal data within <strong class="text-white/90">30 days</strong>,
|
||||
except where we are required to retain it for legal or fraud-prevention purposes.
|
||||
Anonymised aggregate statistics (e.g. download counts) may be retained indefinitely.
|
||||
Server log files containing IP addresses are rotated and deleted after <strong class="text-white/90">90 days</strong>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 7 --}}
|
||||
<section id="security">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">07</span>
|
||||
Security
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed">
|
||||
We implement industry-standard measures to protect your information, including:
|
||||
</p>
|
||||
<ul class="mt-3 list-disc list-inside space-y-1.5 text-sm text-white/70 pl-2">
|
||||
<li>HTTPS (TLS) encryption for all data in transit.</li>
|
||||
<li>Bcrypt hashing for all stored passwords — we never store passwords in plain text.</li>
|
||||
<li>CSRF protection on all state-changing requests.</li>
|
||||
<li>Rate limiting and account lockouts to resist brute-force attacks.</li>
|
||||
</ul>
|
||||
<p class="mt-3 text-sm text-white/50">
|
||||
No method of transmission over the Internet is 100% secure. If you believe your account
|
||||
has been compromised, please <a href="/bug-report" class="text-sky-400 hover:underline">contact us immediately</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 8 --}}
|
||||
<section id="your-rights">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">08</span>
|
||||
Your Rights
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-3">
|
||||
Depending on where you live, you may have certain rights over your personal data:
|
||||
</p>
|
||||
<div class="grid sm:grid-cols-2 gap-3">
|
||||
@foreach ([
|
||||
['Access', 'Request a copy of the personal data we hold about you.'],
|
||||
['Rectification', 'Correct inaccurate or incomplete data via your account settings.'],
|
||||
['Erasure', 'Request deletion of your account and associated personal data.'],
|
||||
['Portability', 'Receive your data in a structured, machine-readable format.'],
|
||||
['Restriction', 'Ask us to limit how we process your data in certain circumstances.'],
|
||||
['Objection', 'Object to processing based on legitimate interests or for direct marketing.'],
|
||||
] as [$right, $desc])
|
||||
<div class="rounded-lg border border-white/[0.07] bg-white/[0.03] px-4 py-3">
|
||||
<p class="text-sm font-semibold text-white mb-0.5">{{ $right }}</p>
|
||||
<p class="text-xs text-white/50">{{ $desc }}</p>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-white/50">
|
||||
To exercise any of these rights, please <a href="/bug-report" class="text-sky-400 hover:underline">contact us</a>.
|
||||
We will respond within 30 days. You also have the right to lodge a complaint with your
|
||||
local data protection authority.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 9 --}}
|
||||
<section id="advertising">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">09</span>
|
||||
Advertising
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-3">
|
||||
Skinbase uses <strong class="text-white/90">Google AdSense</strong> (operated by Google LLC,
|
||||
1600 Amphitheatre Parkway, Mountain View, CA 94043, USA) to display advertisements. Google AdSense
|
||||
may use cookies and web beacons to collect information about your browsing activity in order to
|
||||
serve interest-based (personalised) ads.
|
||||
</p>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-3">
|
||||
<strong class="text-white/90">Consent required.</strong> Google AdSense cookies are only loaded
|
||||
after you click <em>Accept all</em> in the cookie consent banner. If you choose
|
||||
<em>Essential only</em>, no advertising cookies will be placed.
|
||||
You can withdraw consent at any time by clicking <strong class="text-white/90">Cookie Preferences</strong>
|
||||
in the footer.
|
||||
</p>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-3">
|
||||
Data collected by Google AdSense (such as browser type, pages visited, and ad interactions) is
|
||||
processed by Google under
|
||||
<a href="https://policies.google.com/privacy" class="text-sky-400 hover:underline" target="_blank" rel="noopener noreferrer">Google's Privacy Policy</a>.
|
||||
Skinbase does not share any personally identifiable information with Google AdSense beyond what is
|
||||
automatically collected through the ad script.
|
||||
</p>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-3">
|
||||
Google's use of advertising cookies can be managed at
|
||||
<a href="https://www.google.com/settings/ads" class="text-sky-400 hover:underline" target="_blank" rel="noopener noreferrer">google.com/settings/ads</a>,
|
||||
or you may opt out of personalised advertising through the
|
||||
<a href="https://optout.aboutads.info/" class="text-sky-400 hover:underline" target="_blank" rel="noopener noreferrer">Digital Advertising Alliance opt-out</a>.
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-white/50">
|
||||
Registered members may see reduced advertising frequency depending on their account status.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 10 --}}
|
||||
<section id="third-party-links">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">10</span>
|
||||
Third-Party Links
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed">
|
||||
Skinbase may contain links to external websites. We are not responsible for the privacy
|
||||
practices or content of those sites and encourage you to review their privacy policies
|
||||
before disclosing any personal information.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 11 --}}
|
||||
<section id="children">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">11</span>
|
||||
Children's Privacy
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed">
|
||||
Skinbase is a general-audience website. In compliance with the Children's Online Privacy
|
||||
Protection Act (COPPA) we do not knowingly collect personal information from children
|
||||
under the age of <strong class="text-white/90">13</strong>. If we become aware that a
|
||||
child under 13 has registered, we will promptly delete their account and data.
|
||||
If you believe a child has provided us with personal information, please
|
||||
<a href="/bug-report" class="text-sky-400 hover:underline">contact us</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 12 --}}
|
||||
<section id="changes">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">12</span>
|
||||
Changes to This Policy
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed">
|
||||
We may update this Privacy Policy from time to time. When we do, we will revise the
|
||||
"Last updated" date at the top of this page. For material changes we will notify
|
||||
registered members by email and/or by a prominent notice on the site. We encourage you
|
||||
to review this policy periodically. Continued use of Skinbase after changes are posted
|
||||
constitutes your acceptance of the revised policy.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 13 --}}
|
||||
<section id="contact">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">13</span>
|
||||
Contact Us
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed">
|
||||
If you have any questions, concerns, or requests regarding this Privacy Policy or our
|
||||
data practices, please reach out via our
|
||||
<a href="/bug-report" class="text-sky-400 hover:underline">contact form</a> or by
|
||||
sending a private message to any <a href="/staff" class="text-sky-400 hover:underline">staff member</a>.
|
||||
We aim to respond to all privacy-related enquiries within <strong class="text-white/90">10 business days</strong>.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 rounded-lg border border-sky-500/20 bg-sky-500/5 px-5 py-4 text-sm text-sky-300">
|
||||
<p class="font-semibold mb-1">Data Controller</p>
|
||||
<p class="text-sky-300/70">
|
||||
Skinbase.org — operated by the Skinbase team.<br>
|
||||
Contact: <a href="/bug-report" class="underline hover:text-sky-200">via contact form</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
63
resources/views/web/rss-feeds.blade.php
Normal file
63
resources/views/web/rss-feeds.blade.php
Normal file
@@ -0,0 +1,63 @@
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@section('page-content')
|
||||
|
||||
<div class="max-w-2xl space-y-10">
|
||||
|
||||
{{-- Feed list --}}
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-white mb-4">Available Feeds</h2>
|
||||
<ul class="divide-y divide-neutral-800 rounded-lg border border-neutral-800 overflow-hidden">
|
||||
@foreach ($feeds as $key => $feed)
|
||||
<li class="flex items-center gap-4 px-5 py-4 bg-nova-900/50 hover:bg-nova-800/60 transition-colors">
|
||||
<svg class="h-6 w-6 flex-shrink-0 text-orange-400" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M6.18 15.64a2.18 2.18 0 012.18 2.18C8.36 19.01 7.38 20 6.18 20C4.98 20 4 19.01 4 17.82a2.18 2.18 0 012.18-2.18M4 4.44A15.56 15.56 0 0119.56 20h-2.83A12.73 12.73 0 004 7.27V4.44m0 5.66a9.9 9.9 0 019.9 9.9h-2.83A7.07 7.07 0 004 12.93V10.1z"/>
|
||||
</svg>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-white">{{ $feed['title'] }}</p>
|
||||
<p class="text-xs text-neutral-500 truncate">{{ url($feed['url']) }}</p>
|
||||
</div>
|
||||
<a href="{{ $feed['url'] }}"
|
||||
class="flex-shrink-0 rounded-md border border-neutral-700 px-3 py-1.5 text-xs text-neutral-400 hover:border-orange-500 hover:text-orange-400 transition-colors">
|
||||
Subscribe
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{{-- About RSS --}}
|
||||
<div class="prose prose-invert prose-sm max-w-none">
|
||||
<h2>About RSS</h2>
|
||||
<p>
|
||||
RSS is a family of web feed formats used to publish frequently updated digital content,
|
||||
such as blogs, news feeds, or upload streams. By subscribing to an RSS feed you can
|
||||
follow Skinbase updates in your favourite feed reader without needing to visit the site.
|
||||
</p>
|
||||
<h3>How to subscribe</h3>
|
||||
<p>
|
||||
Copy one of the feed URLs above and paste it into your feed reader (e.g. Feedly, Inoreader,
|
||||
or any app that supports RSS 2.0). The reader will automatically check for new content and
|
||||
notify you of updates.
|
||||
</p>
|
||||
<h3>Feed formats</h3>
|
||||
<ul>
|
||||
<li>Really Simple Syndication (RSS 2.0)</li>
|
||||
<li>Rich Site Summary (RSS 0.91, RSS 1.0)</li>
|
||||
<li>RDF Site Summary (RSS 0.9 and 1.0)</li>
|
||||
</ul>
|
||||
<p>
|
||||
RSS delivers its information as an XML file. Our feeds include title, description,
|
||||
author, publication date, and a media thumbnail for each item.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@push('head')
|
||||
@foreach ($feeds as $key => $feed)
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ $feed['title'] }} — Skinbase" href="{{ url($feed['url']) }}">
|
||||
@endforeach
|
||||
@endpush
|
||||
|
||||
@endsection
|
||||
251
resources/views/web/rules.blade.php
Normal file
251
resources/views/web/rules.blade.php
Normal file
@@ -0,0 +1,251 @@
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@section('page-content')
|
||||
|
||||
<div class="max-w-3xl">
|
||||
|
||||
<p class="text-sm text-white/40 mb-1">Last updated: <time datetime="2026-03-01">March 1, 2026</time></p>
|
||||
<p class="text-white/60 text-sm leading-relaxed mb-8">
|
||||
Skinbase is a creative community built on respect and trust. These rules apply to all members
|
||||
and all content. By registering or uploading you agree to follow them. They are intentionally
|
||||
kept minimal so that the most important ones are easy to remember — <strong class="text-white">be respectful,
|
||||
upload only what you own, and have fun.</strong>
|
||||
</p>
|
||||
|
||||
{{-- TOC --}}
|
||||
<nav class="mb-10 rounded-xl border border-white/[0.08] bg-white/[0.03] px-6 py-5">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-widest text-white/40 mb-3">Contents</h2>
|
||||
<ol class="space-y-1.5 text-sm text-sky-400">
|
||||
<li><a href="#community-conduct" class="hover:text-sky-300 hover:underline transition-colors">1. Community Conduct</a></li>
|
||||
<li><a href="#ownership" class="hover:text-sky-300 hover:underline transition-colors">2. Ownership & Copyright</a></li>
|
||||
<li><a href="#licence" class="hover:text-sky-300 hover:underline transition-colors">3. Licence to Skinbase</a></li>
|
||||
<li><a href="#submission-quality" class="hover:text-sky-300 hover:underline transition-colors">4. Submission Quality</a></li>
|
||||
<li><a href="#prohibited-content" class="hover:text-sky-300 hover:underline transition-colors">5. Prohibited Content</a></li>
|
||||
<li><a href="#ripping" class="hover:text-sky-300 hover:underline transition-colors">6. Ripping (Copyright Theft)</a></li>
|
||||
<li><a href="#accounts" class="hover:text-sky-300 hover:underline transition-colors">7. Accounts & Identity</a></li>
|
||||
<li><a href="#moderation" class="hover:text-sky-300 hover:underline transition-colors">8. Moderation & Enforcement</a></li>
|
||||
<li><a href="#appeals" class="hover:text-sky-300 hover:underline transition-colors">9. Appeals</a></li>
|
||||
<li><a href="#liability" class="hover:text-sky-300 hover:underline transition-colors">10. Liability</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="space-y-10">
|
||||
|
||||
{{-- 1 --}}
|
||||
<section id="community-conduct">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">01</span>
|
||||
Community Conduct
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-3">
|
||||
Skinbase is a friendly, general-audience community. Treat every member and guest as you
|
||||
would wish to be treated yourself.
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-sm text-white/70 pl-2">
|
||||
<li>Be respectful in comments, messages, forum posts, and all other interactions.</li>
|
||||
<li>Constructive criticism is welcome; personal attacks, harassment, or bullying are not.</li>
|
||||
<li>No hate speech, discrimination, or content targeting individuals based on race, ethnicity, religion, gender, sexual orientation, disability, or nationality.</li>
|
||||
<li>No spam — this includes repetitive comments, self-promotion outside designated areas, and unsolicited advertising in private messages.</li>
|
||||
<li>Keep drama off the site. Disputes should be resolved respectfully or escalated to a <a href="/staff" class="text-sky-400 hover:underline">staff member</a>.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{{-- 2 --}}
|
||||
<section id="ownership">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">02</span>
|
||||
Ownership & Copyright
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-3">
|
||||
You retain ownership of everything you create and upload. By submitting, you confirm that:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-sm text-white/70 pl-2">
|
||||
<li>The work is entirely your own creation, <strong class="text-white/90">or</strong> you have explicit written permission from the original author for any third-party assets used.</li>
|
||||
<li>If third-party assets are included, proof of permission must be included in the zip file.</li>
|
||||
<li>The submission does not violate any trademark, copyright, or other intellectual property right.</li>
|
||||
</ul>
|
||||
<p class="mt-3 text-sm text-white/50">
|
||||
Uploads found to infringe copyright will be removed. Repeat infringers will have their
|
||||
accounts terminated. To report a suspected infringement, use our
|
||||
<a href="/bug-report" class="text-sky-400 hover:underline">contact form</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 3 --}}
|
||||
<section id="licence">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">03</span>
|
||||
Licence to Skinbase
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed">
|
||||
By uploading your work you grant Skinbase a <strong class="text-white/90">non-exclusive, royalty-free licence</strong>
|
||||
to display, distribute, and promote your content as part of the service — for example,
|
||||
displaying it in galleries, featuring it on the homepage, or including it in promotional
|
||||
material for Skinbase. This licence exists only to allow the site to function and does
|
||||
<strong class="text-white/90">not</strong> transfer ownership.
|
||||
</p>
|
||||
<p class="mt-3 text-sm text-white/50">
|
||||
The site is free — you don't pay to store your work, and we don't charge others to
|
||||
download it. You may delete your uploads at any time from your dashboard.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 4 --}}
|
||||
<section id="submission-quality">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">04</span>
|
||||
Submission Quality
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-3">
|
||||
Every submission represents the Skinbase community. Please put care into what you publish:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-sm text-white/70 pl-2">
|
||||
<li><strong class="text-white/90">Test before uploading.</strong> Incomplete or broken zip files will be removed.</li>
|
||||
<li><strong class="text-white/90">Full-size screenshots only.</strong> Our server auto-generates thumbnails — do not pre-scale your preview image.</li>
|
||||
<li><strong class="text-white/90">Accurate categorisation.</strong> Choose the correct content type and category to help others find your work.</li>
|
||||
<li><strong class="text-white/90">Meaningful title & description.</strong> Titles like "skin1" or "untitled" are discouraged; a short description helps your work get discovered.</li>
|
||||
<li><strong class="text-white/90">Appropriate tags.</strong> Add relevant tags, but do not keyword-stuff.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{{-- 5 --}}
|
||||
<section id="prohibited-content">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">05</span>
|
||||
Prohibited Content
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-3">
|
||||
The following will be removed immediately and may result in account suspension or permanent termination:
|
||||
</p>
|
||||
<div class="grid sm:grid-cols-2 gap-3">
|
||||
@foreach ([
|
||||
['Pornography & explicit nudity', 'Frontal nudity is not accepted. Exceptional artistic work with incidental nudity may be considered on a case-by-case basis.'],
|
||||
['Hate & discriminatory content', 'Content that demeans or attacks people based on protected characteristics.'],
|
||||
['Violence & gore', 'Graphic depictions of real-world violence or gratuitous gore.'],
|
||||
['Malware & harmful files', 'Any executable or zip that contains malware, spyware, or harmful scripts.'],
|
||||
['Personal information', 'Posting another person\'s private data (doxxing) without consent.'],
|
||||
['Illegal content', 'Anything that violates applicable law, including DMCA violations.'],
|
||||
] as [$title, $desc])
|
||||
<div class="rounded-lg border border-white/[0.07] bg-white/[0.02] px-4 py-3">
|
||||
<p class="text-sm font-semibold text-white mb-0.5">{!! $title !!}</p>
|
||||
<p class="text-xs text-white/50">{{ $desc }}</p>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- 6 --}}
|
||||
<section id="ripping">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">06</span>
|
||||
Ripping (Copyright Theft)
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed">
|
||||
"Ripping" means uploading another artist's work — in whole or in part — without their
|
||||
explicit permission. This includes extracting assets from commercial software, games, or
|
||||
other skins and re-releasing them as your own. Ripped submissions will be removed and the
|
||||
uploader's account will be reviewed. Repeat offenders will be permanently banned.
|
||||
</p>
|
||||
<div class="mt-4 rounded-lg border border-amber-500/20 bg-amber-500/5 px-5 py-4 text-sm text-amber-300">
|
||||
<p class="font-semibold mb-1">Photo-based skins</p>
|
||||
<p class="text-amber-300/70">
|
||||
Using photographs found on the internet without the photographer's consent constitutes
|
||||
copyright infringement, even if the skin itself is original artwork.
|
||||
Only use photos you took yourself, or images with a licence that explicitly permits
|
||||
use in derivative works (e.g. CC0 or a compatible Creative Commons licence).
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- 7 --}}
|
||||
<section id="accounts">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">07</span>
|
||||
Accounts & Identity
|
||||
</h2>
|
||||
<ul class="list-disc list-inside space-y-2 text-sm text-white/70 pl-2">
|
||||
<li>One account per person. Duplicate accounts created to evade a suspension or ban will be terminated.</li>
|
||||
<li>Do not impersonate staff, other members, or real individuals.</li>
|
||||
<li>Keep your contact email address up to date — it is used for important account notifications.</li>
|
||||
<li>You are responsible for all activity that occurs under your account. Keep your password secure.</li>
|
||||
<li>Accounts that have been inactive for more than 3 years and contain no uploads may be reclaimed for the username pool.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{{-- 8 --}}
|
||||
<section id="moderation">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">08</span>
|
||||
Moderation & Enforcement
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed mb-3">
|
||||
Skinbase <a href="/staff" class="text-sky-400 hover:underline">staff</a> may take any of
|
||||
the following actions in response to rule violations:
|
||||
</p>
|
||||
<div class="grid sm:grid-cols-3 gap-3">
|
||||
@foreach ([
|
||||
['Warning', 'A private message from staff explaining the violation.'],
|
||||
['Content removal', 'Removal of the offending upload, comment, or post.'],
|
||||
['Temporary suspension', 'Account access restricted for a defined period.'],
|
||||
['Permanent ban', 'Account terminated for severe or repeated violations.'],
|
||||
['IP block', 'Used in cases of persistent abuse or ban evasion.'],
|
||||
['Legal referral', 'For serious illegal activity, authorities may be notified.'],
|
||||
] as [$action, $desc])
|
||||
<div class="rounded-lg border border-white/[0.07] bg-white/[0.03] px-4 py-3">
|
||||
<p class="text-sm font-semibold text-white mb-0.5">{{ $action }}</p>
|
||||
<p class="text-xs text-white/50">{{ $desc }}</p>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-white/50">
|
||||
Skinbase reserves the right to remove any content or terminate any account at any time,
|
||||
with or without prior notice, at staff discretion.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 9 --}}
|
||||
<section id="appeals">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">09</span>
|
||||
Appeals
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed">
|
||||
If you believe a moderation action was made in error, you may appeal by contacting a
|
||||
senior staff member via the <a href="/bug-report" class="text-sky-400 hover:underline">contact form</a>
|
||||
or by sending a private message to an <a href="/staff" class="text-sky-400 hover:underline">admin</a>.
|
||||
Please include your username, the content or account involved, and a clear explanation
|
||||
of why you believe the decision was incorrect. We aim to review all appeals within
|
||||
<strong class="text-white/90">5 business days</strong>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 10 --}}
|
||||
<section id="liability">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-4">
|
||||
<span class="text-sky-400 font-mono text-base">10</span>
|
||||
Liability
|
||||
</h2>
|
||||
<p class="text-white/70 text-sm leading-relaxed">
|
||||
Skinbase is provided "as is". We make no warranties regarding uptime, data integrity,
|
||||
or fitness for a particular purpose. We are not responsible for user-generated content —
|
||||
when you upload something, the legal and moral responsibility lies with you. We will
|
||||
act promptly on valid takedown requests and reports of illegal content, but we cannot
|
||||
pre-screen every submission.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
{{-- Footer note --}}
|
||||
<div class="mt-12 rounded-xl border border-white/[0.07] bg-white/[0.02] px-6 py-5 text-sm text-white/50">
|
||||
<p>
|
||||
Questions about these rules? Send a message to any
|
||||
<a href="/staff" class="text-sky-400 hover:underline">staff member</a>
|
||||
or use our <a href="/bug-report" class="text-sky-400 hover:underline">contact form</a>.
|
||||
We're here to help — not to catch you out.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
83
resources/views/web/staff.blade.php
Normal file
83
resources/views/web/staff.blade.php
Normal file
@@ -0,0 +1,83 @@
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@section('page-content')
|
||||
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-sm text-white/40 mb-1">Last updated: <time datetime="2026-03-01">March 1, 2026</time></p>
|
||||
<p class="text-neutral-300 text-sm leading-relaxed mb-6">
|
||||
Our volunteer staff help keep Skinbase running — from moderation and technical maintenance to community support.
|
||||
If you need assistance, reach out to any team member listed below or use the <a href="/contact" class="text-sky-400 hover:underline">contact form</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@if ($staffByRole->isEmpty())
|
||||
<div class="max-w-md rounded-lg border border-neutral-800 bg-nova-900/50 px-8 py-10 text-center">
|
||||
<svg class="mx-auto mb-3 h-10 w-10 text-neutral-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0"/>
|
||||
</svg>
|
||||
<p class="text-neutral-400 text-sm">We're building our team. Check back soon!</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="space-y-12">
|
||||
@foreach ($roleLabels as $roleSlug => $roleLabel)
|
||||
@if ($staffByRole->has($roleSlug))
|
||||
<section>
|
||||
<h2 class="text-base font-semibold uppercase tracking-widest text-accent border-b border-neutral-800 pb-2 mb-6">
|
||||
{{ $roleLabel }}
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@foreach ($staffByRole[$roleSlug] as $member)
|
||||
@php
|
||||
$avatarUrl = $member->profile?->avatar_url;
|
||||
$profileUrl = '/@' . $member->username;
|
||||
@endphp
|
||||
<div class="flex gap-4 rounded-lg border border-neutral-800 bg-nova-900/50 p-5 hover:border-neutral-700 transition-colors">
|
||||
{{-- Avatar --}}
|
||||
<a href="{{ $profileUrl }}" class="flex-shrink-0">
|
||||
@if ($avatarUrl)
|
||||
<img src="{{ $avatarUrl }}"
|
||||
alt="{{ $member->username }}"
|
||||
class="h-16 w-16 rounded-full object-cover ring-2 ring-neutral-700">
|
||||
@else
|
||||
<div class="h-16 w-16 rounded-full bg-neutral-800 flex items-center justify-center ring-2 ring-neutral-700">
|
||||
<span class="text-xl font-semibold text-neutral-400 uppercase">
|
||||
{{ substr($member->username, 0, 1) }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</a>
|
||||
|
||||
{{-- Info --}}
|
||||
<div class="min-w-0 flex-1">
|
||||
<a href="{{ $profileUrl }}"
|
||||
class="font-semibold text-white hover:text-accent transition-colors truncate block">
|
||||
{{ $member->username }}
|
||||
</a>
|
||||
@if ($member->name && $member->name !== $member->username)
|
||||
<p class="text-xs text-neutral-500 mt-0.5 truncate">{{ $member->name }}</p>
|
||||
@endif
|
||||
<span class="mt-2 inline-block rounded-full px-2 py-0.5 text-xs font-medium
|
||||
{{ $roleSlug === 'admin' ? 'bg-accent/10 text-accent' : 'bg-neutral-800 text-neutral-400' }}">
|
||||
{{ ucfirst($roleSlug) }}
|
||||
</span>
|
||||
@if ($member->profile?->bio)
|
||||
<p class="mt-2 text-xs text-neutral-400 line-clamp-2">{{ $member->profile->bio }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Footer note: contact staff --}}
|
||||
<div class="mt-10 rounded-xl border border-white/10 bg-white/[0.03] p-4 text-sm text-neutral-400">
|
||||
Need help? Start with the <a href="/contact" class="text-sky-400 hover:underline">Contact / Apply</a> form or send a private message to any staff member.
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
@@ -1,19 +1,15 @@
|
||||
@extends('layouts.nova')
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
$hero_title = 'Tags';
|
||||
$hero_description = 'Browse all artwork tags on Skinbase.';
|
||||
$breadcrumbs = $breadcrumbs ?? collect([
|
||||
(object) ['name' => 'Explore', 'url' => '/explore'],
|
||||
(object) ['name' => 'Tags', 'url' => '/tags'],
|
||||
]);
|
||||
@endphp
|
||||
|
||||
<div class="px-6 pt-10 pb-6 md:px-10">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-widest text-white/30 mb-1">Browse</p>
|
||||
<h1 class="text-3xl font-bold text-white leading-tight flex items-center gap-3">
|
||||
<i class="fa-solid fa-tags text-sky-400 text-2xl"></i>
|
||||
Tags
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-white/50">Browse all artwork tags on Skinbase.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-6 pb-16 md:px-10">
|
||||
@section('page-content')
|
||||
@if($tags->isNotEmpty())
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach($tags as $tag)
|
||||
@@ -35,6 +31,4 @@
|
||||
<p class="text-white/40 text-sm">No tags found.</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
||||
380
resources/views/web/terms-of-service.blade.php
Normal file
380
resources/views/web/terms-of-service.blade.php
Normal file
@@ -0,0 +1,380 @@
|
||||
@extends('layouts.nova.content-layout')
|
||||
|
||||
@section('page-content')
|
||||
|
||||
<div class="max-w-3xl space-y-10">
|
||||
|
||||
{{-- Intro --}}
|
||||
<div>
|
||||
<p class="text-neutral-400 text-sm mb-2">Last updated: March 1, 2026</p>
|
||||
<p class="text-neutral-300 leading-relaxed">
|
||||
These Terms of Service ("Terms") govern your access to and use of Skinbase ("we", "us", "our",
|
||||
"the Service") at <strong class="text-white">skinbase.org</strong>. By creating an account or
|
||||
using the Service in any way, you agree to be bound by these Terms. If you do not agree, do not
|
||||
use Skinbase.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{-- Table of Contents --}}
|
||||
<nav class="rounded-xl border border-white/10 bg-white/[0.03] p-5">
|
||||
<p class="text-xs font-semibold uppercase tracking-widest text-neutral-400 mb-3">Contents</p>
|
||||
<ol class="space-y-1.5 text-sm text-sky-400">
|
||||
<li><a href="#acceptance" class="hover:underline">01 — Acceptance of Terms</a></li>
|
||||
<li><a href="#the-service" class="hover:underline">02 — The Service</a></li>
|
||||
<li><a href="#accounts" class="hover:underline">03 — Accounts & Eligibility</a></li>
|
||||
<li><a href="#content-licence" class="hover:underline">04 — Your Content & Licence Grant</a></li>
|
||||
<li><a href="#prohibited" class="hover:underline">05 — Prohibited Conduct</a></li>
|
||||
<li><a href="#copyright" class="hover:underline">06 — Copyright & DMCA</a></li>
|
||||
<li><a href="#our-ip" class="hover:underline">07 — Skinbase Intellectual Property</a></li>
|
||||
<li><a href="#disclaimers" class="hover:underline">08 — Disclaimers</a></li>
|
||||
<li><a href="#liability" class="hover:underline">09 — Limitation of Liability</a></li>
|
||||
<li><a href="#indemnification" class="hover:underline">10 — Indemnification</a></li>
|
||||
<li><a href="#termination" class="hover:underline">11 — Termination</a></li>
|
||||
<li><a href="#governing-law" class="hover:underline">12 — Governing Law</a></li>
|
||||
<li><a href="#changes" class="hover:underline">13 — Changes to These Terms</a></li>
|
||||
<li><a href="#contact" class="hover:underline">14 — Contact</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
{{-- 01 --}}
|
||||
<section id="acceptance">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">01</span>
|
||||
Acceptance of Terms
|
||||
</h2>
|
||||
<p class="text-sm text-neutral-400 leading-relaxed">
|
||||
By accessing or using Skinbase — whether by browsing the site, registering an account, uploading
|
||||
content, or any other interaction — you confirm that you have read, understood, and agree to
|
||||
these Terms and our <a href="/privacy-policy" class="text-sky-400 hover:underline">Privacy Policy</a>,
|
||||
which is incorporated into these Terms by reference. If you are using Skinbase on behalf of an
|
||||
organisation, you represent that you have authority to bind that organisation to these Terms.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 02 --}}
|
||||
<section id="the-service">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">02</span>
|
||||
The Service
|
||||
</h2>
|
||||
<p class="text-sm text-neutral-400 leading-relaxed mb-4">
|
||||
Skinbase is a community platform for sharing and discovering desktop customisation artwork —
|
||||
including skins, themes, wallpapers, icons, and related resources. The Service includes the
|
||||
website, galleries, forums, messaging, comments, and any other features we provide.
|
||||
</p>
|
||||
<p class="text-sm text-neutral-400 leading-relaxed">
|
||||
We reserve the right to modify, suspend, or discontinue any part of the Service at any time
|
||||
with or without notice. We will not be liable to you or any third party for any modification,
|
||||
suspension, or discontinuation of the Service.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 03 --}}
|
||||
<section id="accounts">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">03</span>
|
||||
Accounts & Eligibility
|
||||
</h2>
|
||||
<div class="space-y-4 text-sm text-neutral-400 leading-relaxed">
|
||||
<p>
|
||||
<strong class="text-white">Age.</strong> You must be at least <strong class="text-white">13 years old</strong>
|
||||
to create a Skinbase account. If you are under 18, you represent that you have your parent's or
|
||||
guardian's permission to use the Service.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-white">Accurate information.</strong> You agree to provide accurate, current, and
|
||||
complete information when registering and to keep your account information up to date.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-white">Account security.</strong> You are responsible for maintaining the confidentiality
|
||||
of your password and for all activity that occurs under your account. Notify us immediately at
|
||||
<a href="/bug-report" class="text-sky-400 hover:underline">skinbase.org/bug-report</a> if you believe
|
||||
your account has been compromised.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-white">One account per person.</strong> You may not create multiple accounts to
|
||||
circumvent bans or restrictions, or to misrepresent your identity to other users.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-white">Account transfer.</strong> Accounts are personal and non-transferable.
|
||||
You may not sell, trade, or give away your account.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- 04 --}}
|
||||
<section id="content-licence">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">04</span>
|
||||
Your Content & Licence Grant
|
||||
</h2>
|
||||
<div class="space-y-4 text-sm text-neutral-400 leading-relaxed">
|
||||
<p>
|
||||
<strong class="text-white">Ownership.</strong> You retain ownership of any original artwork,
|
||||
skins, themes, or other creative works ("Your Content") that you upload to Skinbase. These Terms
|
||||
do not transfer any intellectual property rights to us.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-white">Licence to Skinbase.</strong> By uploading or publishing Your Content
|
||||
on Skinbase, you grant us a worldwide, non-exclusive, royalty-free, sublicensable licence to
|
||||
host, store, reproduce, display, distribute, and make Your Content available as part of the
|
||||
Service, including in thumbnails, feeds, promotional materials, and search results. This licence
|
||||
exists only for as long as Your Content remains on the Service.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-white">Licence to other users.</strong> Unless you specify otherwise in your
|
||||
upload description, Your Content may be downloaded and used by other users for personal,
|
||||
non-commercial use. You are responsible for clearly communicating any additional licence terms
|
||||
or restrictions within your upload.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-white">Representations.</strong> By submitting Your Content you represent and
|
||||
warrant that: (a) you own or have all necessary rights to the content; (b) the content does not
|
||||
infringe any third-party intellectual property, privacy, or publicity rights; and (c) the content
|
||||
complies with these Terms and our
|
||||
<a href="/rules-and-guidelines" class="text-sky-400 hover:underline">Rules & Guidelines</a>.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-white">Removal.</strong> You may delete Your Content from your account at any
|
||||
time via your dashboard. Upon deletion, the content will be removed from public view within a
|
||||
reasonable time, though cached copies may persist briefly.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- 05 --}}
|
||||
<section id="prohibited">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">05</span>
|
||||
Prohibited Conduct
|
||||
</h2>
|
||||
<p class="text-sm text-neutral-400 leading-relaxed mb-4">
|
||||
You agree not to use the Service to:
|
||||
</p>
|
||||
<ul class="space-y-2 text-sm text-neutral-400 leading-relaxed list-disc list-inside pl-2">
|
||||
<li>Upload content that infringes any copyright, trademark, patent, trade secret, or other proprietary right.</li>
|
||||
<li>Upload photographs or photoskins using images you do not own or have permission to use (see our <a href="/rules-and-guidelines" class="text-sky-400 hover:underline">Rules & Guidelines</a>).</li>
|
||||
<li>Harass, threaten, bully, stalk, or intimidate any person.</li>
|
||||
<li>Post content that is defamatory, obscene, pornographic, hateful, or promotes violence or illegal activity.</li>
|
||||
<li>Impersonate any person or entity, or falsely claim affiliation with any person, entity, or Skinbase staff.</li>
|
||||
<li>Distribute spam, chain letters, unsolicited commercial messages, or phishing content.</li>
|
||||
<li>Attempt to gain unauthorised access to any part of the Service, other accounts, or our systems.</li>
|
||||
<li>Use automated tools (bots, scrapers, crawlers) to access the Service without prior written permission.</li>
|
||||
<li>Interfere with or disrupt the integrity or performance of the Service or the data contained therein.</li>
|
||||
<li>Collect or harvest personal information about other users without their consent.</li>
|
||||
<li>Use the Service for any unlawful purpose or in violation of applicable laws or regulations.</li>
|
||||
</ul>
|
||||
<p class="mt-4 text-sm text-neutral-500">
|
||||
Violations may result in content removal, account suspension, or a permanent ban. Serious violations
|
||||
may be reported to relevant authorities.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 06 --}}
|
||||
<section id="copyright">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">06</span>
|
||||
Copyright & DMCA
|
||||
</h2>
|
||||
<div class="space-y-4 text-sm text-neutral-400 leading-relaxed">
|
||||
<p>
|
||||
Skinbase respects intellectual property rights. We respond to valid notices of copyright
|
||||
infringement in accordance with applicable law, including the Digital Millennium Copyright Act (DMCA).
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-white">To report infringement:</strong> If you believe your copyrighted work has
|
||||
been copied and is accessible on the Service in a way that constitutes infringement, please contact
|
||||
a <a href="/staff" class="text-sky-400 hover:underline">staff member</a> or use our
|
||||
<a href="/bug-report" class="text-sky-400 hover:underline">contact form</a>. Your notice must include:
|
||||
</p>
|
||||
<ul class="list-disc list-inside pl-2 space-y-1.5">
|
||||
<li>A description of the copyrighted work you claim has been infringed.</li>
|
||||
<li>A description of where the infringing material is located on Skinbase (with URL).</li>
|
||||
<li>Your contact information (name, email address).</li>
|
||||
<li>A statement that you have a good-faith belief that the use is not authorised.</li>
|
||||
<li>A statement, under penalty of perjury, that the information in your notice is accurate and that you are the copyright owner or authorised to act on their behalf.</li>
|
||||
</ul>
|
||||
<p>
|
||||
<strong class="text-white">Counter-notices:</strong> If your content was removed in error, you may
|
||||
submit a counter-notice to a staff member including your identification details, description of the
|
||||
removed content, and a statement under penalty of perjury that you have a good-faith belief the
|
||||
content was removed in error.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-white">Repeat infringers:</strong> We will terminate the accounts of users who
|
||||
are determined to be repeat infringers.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- 07 --}}
|
||||
<section id="our-ip">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">07</span>
|
||||
Skinbase Intellectual Property
|
||||
</h2>
|
||||
<p class="text-sm text-neutral-400 leading-relaxed">
|
||||
The Skinbase name, logo, website design, software, and all Skinbase-produced content are owned by
|
||||
Skinbase and are protected by copyright, trademark, and other intellectual property laws. Nothing in
|
||||
these Terms grants you any right to use the Skinbase name, logo, or branding without our prior written
|
||||
consent. You may not copy, modify, distribute, sell, or lease any part of our Service or included
|
||||
software, nor may you reverse-engineer or attempt to extract the source code of the Service.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 08 --}}
|
||||
<section id="disclaimers">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">08</span>
|
||||
Disclaimers
|
||||
</h2>
|
||||
<div class="rounded-xl border border-amber-500/20 bg-amber-500/5 px-5 py-4 mb-4">
|
||||
<p class="text-sm text-amber-300/90 leading-relaxed">
|
||||
THE SERVICE IS PROVIDED ON AN "AS IS" AND "AS AVAILABLE" BASIS WITHOUT WARRANTIES OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND UNINTERRUPTED OR ERROR-FREE OPERATION.
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-3 text-sm text-neutral-400 leading-relaxed">
|
||||
<p>
|
||||
We do not warrant that the Service will be uninterrupted, secure, or free of errors, viruses,
|
||||
or other harmful components. We do not endorse any user-submitted content and are not responsible
|
||||
for its accuracy, legality, or appropriateness.
|
||||
</p>
|
||||
<p>
|
||||
Downloaded files are provided by third-party users. You download and install any content at your own
|
||||
risk. Always scan downloaded files with up-to-date antivirus software.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- 09 --}}
|
||||
<section id="liability">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">09</span>
|
||||
Limitation of Liability
|
||||
</h2>
|
||||
<div class="rounded-xl border border-amber-500/20 bg-amber-500/5 px-5 py-4 mb-4">
|
||||
<p class="text-sm text-amber-300/90 leading-relaxed">
|
||||
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, SKINBASE AND ITS OPERATORS, STAFF, AND
|
||||
CONTRIBUTORS SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR
|
||||
PUNITIVE DAMAGES — INCLUDING BUT NOT LIMITED TO LOSS OF DATA, LOSS OF PROFITS, OR LOSS OF
|
||||
GOODWILL — ARISING OUT OF OR IN CONNECTION WITH THESE TERMS OR YOUR USE OF THE SERVICE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-sm text-neutral-400 leading-relaxed">
|
||||
Our total liability to you for any claim arising out of or relating to these Terms or the Service
|
||||
shall not exceed the greater of (a) the amount you paid us in the twelve months prior to the claim,
|
||||
or (b) USD $50. Some jurisdictions do not allow limitations on implied warranties or exclusion of
|
||||
incidental/consequential damages, so the above limitations may not apply to you.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 10 --}}
|
||||
<section id="indemnification">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">10</span>
|
||||
Indemnification
|
||||
</h2>
|
||||
<p class="text-sm text-neutral-400 leading-relaxed">
|
||||
You agree to indemnify, defend, and hold harmless Skinbase, its operators, staff, and contributors
|
||||
from and against any claims, liabilities, damages, losses, and expenses (including reasonable legal
|
||||
fees) arising out of or in any way connected with: (a) your access to or use of the Service;
|
||||
(b) Your Content; (c) your violation of these Terms; or (d) your violation of any rights of another
|
||||
person or entity.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 11 --}}
|
||||
<section id="termination">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">11</span>
|
||||
Termination
|
||||
</h2>
|
||||
<div class="space-y-3 text-sm text-neutral-400 leading-relaxed">
|
||||
<p>
|
||||
<strong class="text-white">By you.</strong> You may close your account at any time by contacting
|
||||
a <a href="/staff" class="text-sky-400 hover:underline">staff member</a>. Account deletion requests
|
||||
are processed within 30 days.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-white">By us.</strong> We may suspend or terminate your account immediately
|
||||
and without notice if we determine, in our sole discretion, that you have violated these Terms,
|
||||
the <a href="/rules-and-guidelines" class="text-sky-400 hover:underline">Rules & Guidelines</a>,
|
||||
or applicable law. We may also terminate accounts that have been inactive for an extended period,
|
||||
with prior notice where practicable.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-white">Effect of termination.</strong> Upon termination, your right to access
|
||||
the Service ceases immediately. Publicly uploaded content may remain on the Service unless you
|
||||
separately request its removal. Sections of these Terms that by their nature should survive
|
||||
termination (including Sections 4, 6, 7, 8, 9, 10, and 12) shall survive.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- 12 --}}
|
||||
<section id="governing-law">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">12</span>
|
||||
Governing Law
|
||||
</h2>
|
||||
<p class="text-sm text-neutral-400 leading-relaxed">
|
||||
These Terms are governed by and construed in accordance with applicable law. Any dispute arising
|
||||
under or in connection with these Terms that cannot be resolved informally shall be submitted to
|
||||
the exclusive jurisdiction of the competent courts in the applicable jurisdiction. Nothing in this
|
||||
section limits your rights under mandatory consumer-protection or data-protection laws of your
|
||||
country of residence.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 13 --}}
|
||||
<section id="changes">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">13</span>
|
||||
Changes to These Terms
|
||||
</h2>
|
||||
<p class="text-sm text-neutral-400 leading-relaxed">
|
||||
We may update these Terms from time to time. When we make material changes, we will revise the
|
||||
"Last updated" date at the top of this page and, where the changes are significant, notify
|
||||
registered members by email and/or a prominent notice on the site. Your continued use of the
|
||||
Service after any changes take effect constitutes your acceptance of the revised Terms. If you do
|
||||
not agree to the revised Terms, you must stop using the Service.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{-- 14 --}}
|
||||
<section id="contact">
|
||||
<h2 class="flex items-center gap-2 text-xl font-bold text-white border-b border-white/10 pb-3 mb-6">
|
||||
<span class="text-sky-400 font-mono text-base">14</span>
|
||||
Contact
|
||||
</h2>
|
||||
<p class="text-sm text-neutral-400 leading-relaxed">
|
||||
If you have questions about these Terms, please contact us via our
|
||||
<a href="/bug-report" class="text-sky-400 hover:underline">contact form</a> or by sending a
|
||||
private message to any <a href="/staff" class="text-sky-400 hover:underline">staff member</a>.
|
||||
We aim to respond to all legal enquiries within <strong class="text-white">10 business days</strong>.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 rounded-lg border border-sky-500/20 bg-sky-500/5 px-5 py-4 text-sm text-sky-300">
|
||||
<p class="font-semibold mb-1">Skinbase.org</p>
|
||||
<p class="text-sky-300/70">
|
||||
Operated by the Skinbase team.<br>
|
||||
Contact: <a href="/bug-report" class="underline hover:text-sky-200">via contact form</a> |
|
||||
<a href="/staff" class="underline hover:text-sky-200">Staff page</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- Footer note --}}
|
||||
<div class="rounded-xl border border-white/10 bg-white/[0.03] p-5 text-sm text-neutral-400 leading-relaxed">
|
||||
These Terms of Service should be read alongside our
|
||||
<a href="/privacy-policy" class="text-sky-400 hover:underline">Privacy Policy</a> and
|
||||
<a href="/rules-and-guidelines" class="text-sky-400 hover:underline">Rules & Guidelines</a>,
|
||||
which together form the complete agreement between you and Skinbase.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
Reference in New Issue
Block a user