login update

This commit is contained in:
2026-03-05 11:24:37 +01:00
parent 5a33ca55a1
commit f6772f673b
67 changed files with 10640 additions and 116 deletions

View File

@@ -0,0 +1,98 @@
{{--
Author stories page /stories/author/{username}
Uses ContentLayout.
--}}
@extends('layouts.nova.content-layout')
@php
$authorDisplayName = $author->user?->username ?? $author->name;
$hero_title = 'Stories by ' . $authorDisplayName;
$hero_description = 'All stories and interviews by ' . $authorDisplayName . ' on Skinbase.';
@endphp
@section('page-content')
{{-- Author spotlight --}}
<div class="flex items-center gap-5 rounded-xl border border-white/[0.06] bg-white/[0.02] p-6 mb-10">
@if($author->avatar_url)
<img src="{{ $author->avatar_url }}" alt="{{ $author->name }}"
class="w-16 h-16 rounded-full object-cover border-2 border-white/10 flex-shrink-0" />
@else
<div class="w-16 h-16 rounded-full bg-nova-700 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-user text-xl text-white/30"></i>
</div>
@endif
<div class="min-w-0">
<h2 class="text-lg font-semibold text-white">{{ $author->name }}</h2>
@if($author->bio)
<p class="mt-1 text-sm text-white/50 line-clamp-2">{{ $author->bio }}</p>
@endif
@if($author->user)
<a href="{{ $author->profile_url }}" class="mt-2 inline-flex items-center gap-1 text-xs text-sky-400 hover:text-sky-300 transition-colors">
View profile <i class="fa-solid fa-arrow-right text-[10px]"></i>
</a>
@endif
</div>
</div>
{{-- Stories grid --}}
@if($stories->isNotEmpty())
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@foreach($stories as $story)
<a href="{{ $story->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($story->cover_url)
<div class="aspect-video bg-nova-800 overflow-hidden">
<img src="{{ $story->cover_url }}" alt="{{ $story->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/20 to-indigo-900/20 flex items-center justify-center">
<i class="fa-solid fa-feather-pointed text-3xl text-white/15"></i>
</div>
@endif
<div class="p-5">
@if($story->tags->isNotEmpty())
<div class="flex flex-wrap gap-1.5 mb-3">
@foreach($story->tags->take(3) as $tag)
<span class="rounded-full px-2 py-0.5 text-[11px] font-medium bg-sky-500/10 text-sky-400 border border-sky-500/20">
#{{ $tag->name }}
</span>
@endforeach
</div>
@endif
<h2 class="text-base font-semibold text-white group-hover:text-sky-300 transition-colors line-clamp-2 leading-snug">
{{ $story->title }}
</h2>
@if($story->excerpt)
<p class="mt-2 text-sm text-white/45 line-clamp-2">{{ $story->excerpt }}</p>
@endif
<div class="mt-4 flex items-center gap-2 text-xs text-white/30">
@if($story->published_at)
<time datetime="{{ $story->published_at->toIso8601String() }}">
{{ $story->published_at->format('M j, Y') }}
</time>
<span>·</span>
@endif
<span>{{ $story->reading_time }} min read</span>
</div>
</div>
</a>
@endforeach
</div>
<div class="mt-10 flex justify-center">
{{ $stories->withQueryString()->links() }}
</div>
@else
<div class="rounded-xl border border-white/[0.06] bg-white/[0.02] px-8 py-16 text-center">
<i class="fa-solid fa-feather-pointed text-4xl text-white/20 mb-4 block"></i>
<p class="text-white/40 text-sm">No published stories from this author yet.</p>
<a href="/stories" class="mt-4 inline-block text-sm text-sky-400 hover:text-sky-300 transition-colors">
Browse all stories
</a>
</div>
@endif
@endsection

View File

@@ -0,0 +1,155 @@
{{--
Stories index /stories
Uses ContentLayout.
--}}
@extends('layouts.nova.content-layout')
@php
$hero_title = 'Skinbase Stories';
$hero_description = 'Artist interviews, community spotlights, tutorials and announcements.';
@endphp
@push('head')
{{-- WebSite / Blog structured data --}}
<script type="application/ld+json">
{!! json_encode([
'@context' => 'https://schema.org',
'@type' => 'Blog',
'name' => 'Skinbase Stories',
'description' => 'Artist interviews, community spotlights, tutorials and announcements from Skinbase.',
'url' => url('/stories'),
'publisher' => ['@type' => 'Organization', 'name' => 'Skinbase', 'url' => url('/')],
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) !!}
</script>
@endpush
@section('page-content')
{{-- Featured story hero --}}
@if($featured)
<a href="{{ $featured->url }}"
class="group block rounded-2xl border border-white/[0.06] bg-white/[0.02] overflow-hidden hover:bg-white/[0.04] transition-colors mb-10">
<div class="md:flex">
@if($featured->cover_url)
<div class="md:w-1/2 aspect-video md:aspect-auto overflow-hidden bg-nova-900">
<img src="{{ $featured->cover_url }}" alt="{{ $featured->title }}"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
loading="eager" />
</div>
@else
<div class="md:w-1/2 aspect-video md:aspect-auto bg-gradient-to-br from-sky-900/40 to-purple-900/40 flex items-center justify-center">
<i class="fa-solid fa-star text-4xl text-white/20"></i>
</div>
@endif
<div class="md:w-1/2 p-8 flex flex-col justify-center">
<div class="mb-3">
<span class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium bg-yellow-400/10 text-yellow-300 border border-yellow-400/20">
<i class="fa-solid fa-star text-[10px]"></i> Featured
</span>
</div>
<h2 class="text-2xl font-bold text-white group-hover:text-sky-300 transition-colors leading-snug">
{{ $featured->title }}
</h2>
@if($featured->excerpt)
<p class="mt-3 text-sm text-white/50 line-clamp-3">{{ $featured->excerpt }}</p>
@endif
<div class="mt-5 flex items-center gap-3 text-xs text-white/30">
@if($featured->author)
<span class="flex items-center gap-1.5">
@if($featured->author->avatar_url)
<img src="{{ $featured->author->avatar_url }}" alt="{{ $featured->author->name }}"
class="w-5 h-5 rounded-full object-cover" />
@endif
{{ $featured->author->name }}
</span>
<span>·</span>
@endif
@if($featured->published_at)
<time datetime="{{ $featured->published_at->toIso8601String() }}">
{{ $featured->published_at->format('M j, Y') }}
</time>
<span>·</span>
@endif
<span>{{ $featured->reading_time }} min read</span>
</div>
</div>
</div>
</a>
@endif
{{-- Stories grid --}}
@if($stories->isNotEmpty())
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@foreach($stories as $story)
@if($featured && $story->id === $featured->id)
@continue
@endif
<a href="{{ $story->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($story->cover_url)
<div class="aspect-video bg-nova-800 overflow-hidden">
<img src="{{ $story->cover_url }}" alt="{{ $story->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/20 to-indigo-900/20 flex items-center justify-center">
<i class="fa-solid fa-feather-pointed text-3xl text-white/15"></i>
</div>
@endif
<div class="p-5">
{{-- Tags --}}
@if($story->tags->isNotEmpty())
<div class="flex flex-wrap gap-1.5 mb-3">
@foreach($story->tags->take(3) as $tag)
<span class="rounded-full px-2 py-0.5 text-[11px] font-medium bg-sky-500/10 text-sky-400 border border-sky-500/20">
#{{ $tag->name }}
</span>
@endforeach
</div>
@endif
<h2 class="text-base font-semibold text-white group-hover:text-sky-300 transition-colors line-clamp-2 leading-snug">
{{ $story->title }}
</h2>
@if($story->excerpt)
<p class="mt-2 text-sm text-white/45 line-clamp-2">{{ $story->excerpt }}</p>
@endif
<div class="mt-4 flex items-center gap-2 text-xs text-white/30">
@if($story->author)
<span class="flex items-center gap-1.5">
@if($story->author->avatar_url)
<img src="{{ $story->author->avatar_url }}" alt="{{ $story->author->name }}"
class="w-4 h-4 rounded-full object-cover" />
@endif
{{ $story->author->name }}
</span>
<span>·</span>
@endif
@if($story->published_at)
<time datetime="{{ $story->published_at->toIso8601String() }}">
{{ $story->published_at->format('M j, Y') }}
</time>
<span>·</span>
@endif
<span>{{ $story->reading_time }} min read</span>
</div>
</div>
</a>
@endforeach
</div>
<div class="mt-10 flex justify-center">
{{ $stories->withQueryString()->links() }}
</div>
@else
<div class="rounded-xl border border-white/[0.06] bg-white/[0.02] px-8 py-16 text-center">
<i class="fa-solid fa-feather-pointed text-4xl text-white/20 mb-4 block"></i>
<p class="text-white/40 text-sm">No stories published yet. Check back soon!</p>
</div>
@endif
@endsection

View File

@@ -0,0 +1,229 @@
{{--
Single story page /stories/{slug}
Uses ContentLayout.
Includes: Hero, Article content, Author box, Related stories, Share buttons.
--}}
@extends('layouts.nova.content-layout')
@php
$hero_title = $story->title;
@endphp
@push('head')
{{-- OpenGraph --}}
<meta property="og:type" content="article" />
<meta property="og:title" content="{{ $story->title }}" />
<meta property="og:description" content="{{ $story->meta_excerpt }}" />
@if($story->cover_url)
<meta property="og:image" content="{{ $story->cover_url }}" />
@endif
<meta property="og:url" content="{{ $story->url }}" />
<meta property="og:site_name" content="Skinbase" />
@if($story->published_at)
<meta property="article:published_time" content="{{ $story->published_at->toIso8601String() }}" />
@endif
@if($story->updated_at)
<meta property="article:modified_time" content="{{ $story->updated_at->toIso8601String() }}" />
@endif
{{-- Twitter / X card --}}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="{{ $story->title }}" />
<meta name="twitter:description" content="{{ $story->meta_excerpt }}" />
@if($story->cover_url)
<meta name="twitter:image" content="{{ $story->cover_url }}" />
@endif
{{-- Article structured data (schema.org) --}}
<script type="application/ld+json">
{!! json_encode(array_filter([
'@context' => 'https://schema.org',
'@type' => 'Article',
'headline' => $story->title,
'description' => $story->meta_excerpt,
'image' => $story->cover_url,
'datePublished' => $story->published_at?->toIso8601String(),
'dateModified' => $story->updated_at?->toIso8601String(),
'mainEntityOfPage' => $story->url,
'author' => $story->author ? [
'@type' => 'Person',
'name' => $story->author->name,
] : [
'@type' => 'Organization',
'name' => 'Skinbase',
],
'publisher' => [
'@type' => 'Organization',
'name' => 'Skinbase',
'url' => url('/'),
],
]), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) !!}
</script>
@endpush
@section('page-content')
<div class="max-w-3xl mx-auto">
{{-- ── HERO ──────────────────────────────────────────────────────────────── --}}
@if($story->cover_url)
<div class="rounded-2xl overflow-hidden mb-8 aspect-video">
<img src="{{ $story->cover_url }}" alt="{{ $story->title }}"
class="w-full h-full object-cover"
loading="eager" />
</div>
@endif
{{-- Meta bar --}}
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-white/40 mb-6">
@if($story->author)
<a href="{{ $story->author->profile_url }}"
class="flex items-center gap-2 hover:text-white/60 transition-colors">
@if($story->author->avatar_url)
<img src="{{ $story->author->avatar_url }}" alt="{{ $story->author->name }}"
class="w-6 h-6 rounded-full object-cover" />
@endif
<span>{{ $story->author->name }}</span>
</a>
<span>·</span>
@endif
@if($story->published_at)
<time datetime="{{ $story->published_at->toIso8601String() }}">
{{ $story->published_at->format('F j, Y') }}
</time>
<span>·</span>
@endif
<span>{{ $story->reading_time }} min read</span>
<span>·</span>
<span><i class="fa-regular fa-eye mr-1 text-xs"></i>{{ number_format($story->views) }}</span>
</div>
{{-- Tags --}}
@if($story->tags->isNotEmpty())
<div class="flex flex-wrap gap-2 mb-8">
@foreach($story->tags as $tag)
<a href="{{ $tag->url }}"
class="rounded-full px-3 py-1 text-xs font-medium bg-sky-500/10 text-sky-400 border border-sky-500/20 hover:bg-sky-500/20 transition-colors">
#{{ $tag->name }}
</a>
@endforeach
</div>
@endif
{{-- ── ARTICLE CONTENT ──────────────────────────────────────────────────── --}}
<article class="prose prose-invert prose-headings:text-white prose-a:text-sky-400 hover:prose-a:text-sky-300 prose-p:text-white/70 prose-strong:text-white prose-blockquote:border-sky-500 prose-blockquote:text-white/60 max-w-none">
@if($story->content)
{!! $story->content !!}
@else
<p class="text-white/40 italic">Content not available.</p>
@endif
</article>
{{-- ── SHARE BUTTONS ────────────────────────────────────────────────────── --}}
<div class="mt-12 pt-8 border-t border-white/[0.06]">
<p class="text-sm text-white/40 mb-4">Share this story</p>
<div class="flex flex-wrap gap-3">
<a href="https://twitter.com/intent/tweet?text={{ urlencode($story->title) }}&url={{ urlencode($story->url) }}"
target="_blank" rel="noopener"
class="inline-flex items-center gap-2 rounded-lg border border-white/[0.08] bg-white/[0.03] px-4 py-2 text-sm text-white/60 hover:bg-white/[0.07] hover:text-white transition-colors">
<i class="fa-brands fa-x-twitter text-xs"></i> Share on X
</a>
<a href="https://www.reddit.com/submit?title={{ urlencode($story->title) }}&url={{ urlencode($story->url) }}"
target="_blank" rel="noopener"
class="inline-flex items-center gap-2 rounded-lg border border-white/[0.08] bg-white/[0.03] px-4 py-2 text-sm text-white/60 hover:bg-white/[0.07] hover:text-white transition-colors">
<i class="fa-brands fa-reddit text-xs"></i> Reddit
</a>
<button type="button"
onclick="navigator.clipboard.writeText('{{ $story->url }}').then(() => { this.textContent = '✓ Copied!'; setTimeout(() => { this.innerHTML = '<i class=\'fa-regular fa-link text-xs\'></i> Copy link'; }, 2000); })"
class="inline-flex items-center gap-2 rounded-lg border border-white/[0.08] bg-white/[0.03] px-4 py-2 text-sm text-white/60 hover:bg-white/[0.07] hover:text-white transition-colors">
<i class="fa-regular fa-link text-xs"></i> Copy link
</button>
</div>
</div>
{{-- ── AUTHOR BOX ───────────────────────────────────────────────────────── --}}
@if($story->author)
<div class="mt-12 rounded-2xl border border-white/[0.06] bg-white/[0.02] p-6">
<div class="flex items-start gap-5">
@if($story->author->avatar_url)
<img src="{{ $story->author->avatar_url }}" alt="{{ $story->author->name }}"
class="w-16 h-16 rounded-full object-cover border border-white/10 flex-shrink-0" />
@else
<div class="w-16 h-16 rounded-full bg-nova-700 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-user text-xl text-white/30"></i>
</div>
@endif
<div class="min-w-0 flex-1">
<p class="text-xs uppercase tracking-widest text-white/30 mb-1">About the author</p>
<h3 class="text-lg font-semibold text-white">{{ $story->author->name }}</h3>
@if($story->author->bio)
<p class="mt-2 text-sm text-white/55">{{ $story->author->bio }}</p>
@endif
<div class="mt-4 flex flex-wrap gap-3">
@if($story->author->user)
<a href="{{ $story->author->profile_url }}"
class="inline-flex items-center gap-1.5 text-sm text-sky-400 hover:text-sky-300 transition-colors">
More from this artist <i class="fa-solid fa-arrow-right text-xs"></i>
</a>
@endif
<a href="/stories/author/{{ $story->author->user?->username ?? urlencode($story->author->name) }}"
class="inline-flex items-center gap-1.5 text-sm text-white/40 hover:text-white/60 transition-colors">
All stories
</a>
</div>
</div>
</div>
</div>
@endif
{{-- ── RELATED STORIES ─────────────────────────────────────────────────── --}}
@if($related->isNotEmpty())
<div class="mt-12">
<h2 class="text-lg font-semibold text-white mb-6">Related Stories</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
@foreach($related as $rel)
<a href="{{ $rel->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($rel->cover_url)
<div class="aspect-video overflow-hidden bg-nova-800">
<img src="{{ $rel->cover_url }}" alt="{{ $rel->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/20 to-indigo-900/20 flex items-center justify-center">
<i class="fa-solid fa-feather-pointed text-2xl text-white/15"></i>
</div>
@endif
<div class="p-4">
<h3 class="text-sm font-medium text-white group-hover:text-sky-300 transition-colors line-clamp-2 leading-snug">
{{ $rel->title }}
</h3>
<div class="mt-2 flex items-center gap-2 text-xs text-white/30">
@if($rel->published_at)
<time datetime="{{ $rel->published_at->toIso8601String() }}">
{{ $rel->published_at->format('M j, Y') }}
</time>
<span>·</span>
@endif
<span>{{ $rel->reading_time }} min read</span>
</div>
</div>
</a>
@endforeach
</div>
</div>
@endif
{{-- Back link --}}
<div class="mt-12 pt-8 border-t border-white/[0.06]">
<a href="/stories" 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 Stories
</a>
</div>
</div>
@endsection

View File

@@ -0,0 +1,68 @@
{{--
Tag stories page /stories/tag/{tag}
Uses ContentLayout.
--}}
@extends('layouts.nova.content-layout')
@php
$hero_title = '#' . $storyTag->name;
$hero_description = 'Stories tagged with "' . $storyTag->name . '" on Skinbase.';
@endphp
@section('page-content')
@if($stories->isNotEmpty())
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@foreach($stories as $story)
<a href="{{ $story->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($story->cover_url)
<div class="aspect-video bg-nova-800 overflow-hidden">
<img src="{{ $story->cover_url }}" alt="{{ $story->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/20 to-indigo-900/20 flex items-center justify-center">
<i class="fa-solid fa-feather-pointed text-3xl text-white/15"></i>
</div>
@endif
<div class="p-5">
<h2 class="text-base font-semibold text-white group-hover:text-sky-300 transition-colors line-clamp-2 leading-snug">
{{ $story->title }}
</h2>
@if($story->excerpt)
<p class="mt-2 text-sm text-white/45 line-clamp-2">{{ $story->excerpt }}</p>
@endif
<div class="mt-4 flex items-center gap-2 text-xs text-white/30">
@if($story->author)
<span>{{ $story->author->name }}</span>
<span>·</span>
@endif
@if($story->published_at)
<time datetime="{{ $story->published_at->toIso8601String() }}">
{{ $story->published_at->format('M j, Y') }}
</time>
<span>·</span>
@endif
<span>{{ $story->reading_time }} min read</span>
</div>
</div>
</a>
@endforeach
</div>
<div class="mt-10 flex justify-center">
{{ $stories->withQueryString()->links() }}
</div>
@else
<div class="rounded-xl border border-white/[0.06] bg-white/[0.02] px-8 py-16 text-center">
<i class="fa-solid fa-tag text-4xl text-white/20 mb-4 block"></i>
<p class="text-white/40 text-sm">No stories found for this tag.</p>
<a href="/stories" class="mt-4 inline-block text-sm text-sky-400 hover:text-sky-300 transition-colors">
Browse all stories
</a>
</div>
@endif
@endsection