Complete Hubbly theme conversion: all pages rewritten with CSS variable theming

- Converted all views from Dusk components (x-content.content-card, x-form.*) to inline Hubbly style
- All pages use consistent card pattern: rounded-lg, gradient headers, color-mix borders
- Added Hubbly-style homepage with 2-column layout, login card, swiper news carousel
- Rewrote navigation with Alpine.js dropdowns, CSS variable colors, Hubbly assets
- Updated profile page with Hubbly cards, fixed data bugs (friend/guild relationships)
- Rewrote register page to match Hubbly layout: banner header, avatar preview with Frank GIF, 2-column form, avatar carousel selector, border-4 inputs
- Rewrote login, settings, help center, radio, applications, utility pages
- All colors use CSS variables controlled by Filament theme editor
- Added Hubbly assets: banner, Frank GIF, navigation icons, online badge
- Removed all dependencies on x-content.* and x-form.* components
This commit is contained in:
root
2026-06-27 17:01:02 +02:00
parent 61bb70ac1d
commit c53a5a8a2c
68 changed files with 4708 additions and 4608 deletions
@@ -1,187 +1,179 @@
<x-app-layout>
@push('title', $article->title)
<div class="col-span-12 rounded space-y-3 md:col-span-3">
<div
class="relative mt-6 h-24 w-full overflow-hidden rounded border bg-white shadow-sm dark:border-gray-900 dark:bg-gray-800 md:mt-0">
<div
class="absolute top-1 right-1 rounded bg-white px-2 text-sm font-semibold dark:bg-gray-700 dark:text-gray-100">
{{ $article->user && !$article->user->hidden_staff ? $article->user->permission->rank_name ?? 'Member' : 'Member' }}
<div class="col-span-12 lg:col-span-9 space-y-4">
<div class="rounded-lg overflow-hidden" style="background-color: var(--color-surface); border: 1px solid color-mix(in srgb, var(--color-text-muted) 15%, transparent);">
<div class="relative h-56 md:h-72 w-full overflow-hidden" style="background: url(/storage/{{ $article->image }}) center; background-size: cover;">
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent"></div>
<div class="absolute bottom-0 left-0 right-0 p-6">
<h1 class="text-2xl md:text-3xl font-bold text-white mb-2">{{ $article->title }}</h1>
<p class="text-sm text-white/80 mb-3">{{ $article->short_story }}</p>
<div class="flex items-center gap-3">
@if ($article->user)
<a href="{{ route('profile.show', $article->user) }}">
<img style="image-rendering: pixelated;" class="w-10 h-10 rounded-lg hover:scale-105 transition-transform"
src="{{ setting('avatar_imager') }}{{ $article->user->look }}&direction=2&head_direction=3&gesture=sml&action=wav" alt="">
</a>
<div>
<a href="{{ route('profile.show', $article->user) }}" class="font-semibold text-white hover:underline text-sm">
{{ $article->user->username }}
</a>
<p class="text-xs text-white/70">{{ $article->created_at->format('d F Y') }}</p>
</div>
@else
<div class="text-white/70 text-sm">{{ __('Unknown author') }}</div>
@endif
</div>
</div>
</div>
<div class="p-6">
<div id="article-content" class="prose prose-sm max-w-none dark:prose-invert" style="color: var(--color-text);">
{{ \Stevebauman\Purify\Facades\Purify::clean($article->full_story) }}
</div>
<div class="flex flex-wrap gap-2 mt-6 pt-4" style="border-top: 1px solid color-mix(in srgb, var(--color-text-muted) 15%, transparent);">
@forelse ($article->tags as $tag)
<span style="background-color: {{ $tag->background_color ?? '#000000' }}; color: {{ $tag->background_color ? '#fff' : (strlen($tag->background_color ?? '') > 0 ? '#fff' : '#fff') }}"
class="text-xs font-medium rounded-lg px-3 py-1">
{{ $tag->name }}
</span>
@empty
<span class="text-xs font-medium rounded-lg px-3 py-1" style="background-color: #000000; color: white;">
{{ __('No tags found.') }}
</span>
@endforelse
</div>
@include('community.partials.article-reactions')
</div>
</div>
@if ($article->can_comment)
@if (auth()->check() && !$article->userHasReachedArticleCommentLimit())
<div class="rounded-lg overflow-hidden" style="background-color: var(--color-surface); border: 1px solid color-mix(in srgb, var(--color-text-muted) 15%, transparent);">
<div class="relative w-full h-12" style="background: linear-gradient(140deg, var(--color-primary) 0%, color-mix(in srgb, var(--color-primary) 80%, black) 100%);">
<div class="flex items-center h-full px-4">
<h2 class="text-white font-bold text-lg">{{ __('Post a comment') }}</h2>
</div>
</div>
<div class="p-4">
<p class="text-sm mb-3" style="color: var(--color-text-muted);">{{ __('Post a comment on the article, to let us know what you think about it') }}</p>
<form action="{{ route('article.comment.store', $article) }}" method="POST">
@csrf
<textarea name="comment"
class="focus:ring-0 border-2 rounded-lg w-full min-h-[100px] max-h-[100px] @error('comment') border-red-600 ring-red-500 @enderror"
style="background-color: var(--color-background); color: var(--color-text); border-color: color-mix(in srgb, var(--color-text-muted) 15%, transparent);"></textarea>
<button type="submit" class="mt-2 rounded-lg px-4 py-2 text-sm font-semibold transition-all duration-200 hover:opacity-90"
style="background-color: var(--color-primary); color: var(--button-text-color);">
{{ __('Post comment') }}
</button>
</form>
</div>
</div>
@endif
<div class="rounded-lg overflow-hidden" style="background-color: var(--color-surface); border: 1px solid color-mix(in srgb, var(--color-text-muted) 15%, transparent);">
<div class="relative w-full h-12" style="background: linear-gradient(140deg, var(--color-primary) 0%, color-mix(in srgb, var(--color-primary) 80%, black) 100%);">
<div class="flex items-center h-full px-4">
<h2 class="text-white font-bold text-lg">{{ __('Comments') }}</h2>
</div>
</div>
<div class="p-4">
<p class="text-sm mb-3" style="color: var(--color-text-muted);">{{ __('Below you will see all the comments, written on this article') }}</p>
<div class="space-y-3">
@forelse ($article->comments as $comment)
<div class="rounded-lg p-4 flex items-start gap-3" style="background-color: var(--color-background); border: 1px solid color-mix(in srgb, var(--color-text-muted) 15%, transparent);">
<a href="{{ route('profile.show', $comment->user) }}" class="shrink-0">
<img style="image-rendering: pixelated;" class="w-12 h-12 rounded-lg hover:scale-105 transition-transform"
src="{{ setting('avatar_imager') }}{{ $comment->user->look }}&direction=2&head_direction=3&gesture=sml&action=wav" alt="">
</a>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between gap-2">
<a href="{{ route('profile.show', $comment->user) }}" class="font-semibold text-sm hover:underline" style="color: var(--color-primary);">
{{ $comment->user->username }}
</a>
<div class="flex items-center gap-2">
<span class="text-xs" style="color: var(--color-text-muted);">{{ $comment->created_at->diffForHumans() }}</span>
@if ($comment->canBeDeleted())
<form action="{{ route('article.comment.destroy', $comment) }}" method="POST" class="inline">
@method('DELETE')
@csrf
<button type="submit" class="hover:scale-105 transition-transform">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 w-4" style="color: var(--color-text-muted);">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
</button>
</form>
@endif
</div>
</div>
<p class="mt-1 text-sm" style="color: var(--color-text);">{{ $comment->comment }}</p>
</div>
</div>
@empty
<div class="text-center py-8">
<p style="color: var(--color-text-muted);">{{ __('No comments yet. Be the first to comment!') }}</p>
</div>
@endforelse
</div>
</div>
</div>
@endif
</div>
<div class="h-[65%] w-full staff-bg"
style="background: rgba(0, 0, 0, 0.5) url({{ asset(sprintf('assets/images/%s', $article->user ? $article->user->permission->staff_background ?? 'staff-bg.png' : 'staff-bg.png')) }});">
</div>
<div class="col-span-12 lg:col-span-3 space-y-4">
<div class="rounded-lg overflow-hidden" style="background-color: var(--color-surface); border: 1px solid color-mix(in srgb, var(--color-text-muted) 15%, transparent);">
<div class="relative w-full h-12" style="background: linear-gradient(140deg, var(--color-primary) 0%, color-mix(in srgb, var(--color-primary) 80%, black) 100%);">
<div class="flex items-center justify-center h-full">
<h2 class="text-white font-bold text-lg">{{ __('Other articles') }}</h2>
</div>
</div>
<div class="p-4">
<div class="flex flex-col gap-y-2">
@forelse($otherArticles as $art)
<a href="{{ route('article.show', $art->slug) }}"
style="background: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url(/storage/{{ $art->image }}) center; background-size: cover;"
class="w-full rounded-lg h-14 transition ease-in-out duration-200 hover:scale-[103%] text-white flex justify-center items-center font-bold text-sm">
{{ Str::limit($art->title, 25) }}
</a>
@empty
<p class="text-sm" style="color: var(--color-text-muted);">
{{ __('There is currently no other articles') }}
</p>
@endforelse
</div>
</div>
</div>
<a href="{{ route('profile.show', $article->user ?? \App\Models\User::first()) }}" class="absolute top-4 left-1 ">
<img style="image-rendering: pixelated;" class="transition duration-300 ease-in-out hover:scale-105"
src="{{ setting('avatar_imager') }}{{ $article->user?->look }}&direction=2&head_direction=3&gesture=sml&action=wav"
alt="">
</a>
<p class="text-2xl font-semibold ml-[70px] text-white -mt-[35px] ">
{{ $article->user->username ?? setting('hotel_name') }}
</p>
<div class="flex w-full items-center justify-between px-4">
<p class="ml-[57px] text-sm mt-[10px] font-semibold text-gray-500 ">
{{ $article->user->motto ?? setting('start_motto') }}
</p>
@if($article->user)
<div class="w-4 h-4 rounded-full mt-2 {{ $article->user->online ? 'bg-green-600' : 'bg-red-600' }}">
</div>
@endif
</div>
</div>
<x-content.content-card icon="article-icon" classes="border dark:bg-gray-800 dark:border-gray-900">
<x-slot:title>
{{ __('Other articles') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Our most recent articles') }}
</x-slot:under-title>
<div class="flex flex-col gap-y-2">
@forelse($otherArticles as $art)
<a href="{{ route('article.show', $art->slug) }}"
style="background: rgba(0, 0, 0, 0.5) url({{ $art->image }}) center;"
class="w-full rounded h-12 bg-blue-200 transition ease-in-out duration-200 hover:scale-[103%] text-white flex justify-center items-center font-bold recent-articles">
{{ Str::limit($art->title, 20) }}
</a>
@empty
<p class="dark:text-gray-400">
{{ __('There is currently no other articles') }}
</p>
@endforelse
</div>
</x-content.content-card>
</div>
<div class="col-span-12 space-y-4 md:col-span-9">
<div
class="relative flex flex-col gap-y-8 overflow-hidden rounded bg-white p-3 shadow-sm dark:bg-gray-800 dark:text-gray-300">
<div class="relative flex h-24 flex-col items-center justify-center gap-y-1 overflow-hidden rounded px-2"
style="background: url(/storage/{{ $article->image }}) center; background-size: cover; color: var(--color-text);">
<div class="absolute h-full w-full bg-black/50"></div>
<p class="relative w-full truncate text-center text-xl font-semibold lg:text-2xl xl:text-3xl " style="color: white;">
{{ $article->title }}</p>
<p class="relative w-full truncate text-center " style="color: rgba(255,255,255,0.9);">{{ $article->short_story }}</p>
</div>
<div class="px-2" id="article-content">
{{ \Stevebauman\Purify\Facades\Purify::clean($article->full_story) }}
</div>
<div class="w-full h-10 lg:h-1/2 py-1 flex gap-1 items-center justify-start flex-wrap">
@forelse ($article->tags as $tag)
<span @class([
"text-xs font-medium rounded-lg px-2",
"text-slate-600" => $tag->background_color,
"text-white" => $tag->background_color
]) style="background-color: {{ $tag->background_color }}">{{ $tag->name }}</span>
@empty
<span @class([
"text-xs font-medium rounded-lg px-2",
"text-slate-600",
"text-white"
]) style="background-color: #000000">{{ __('No tags found.') }}</span>
</span>
@endforelse
</div>
@include('community.partials.article-reactions')
</div>
@if ($article->can_comment)
@if (auth()->check() && !$article->userHasReachedArticleCommentLimit())
<x-content.content-card icon="hotel-icon" classes="border dark:border-gray-900">
<x-slot:title>
{{ __('Post a comment') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Post a comment on the article, to let us know what you think about it') }}
</x-slot:under-title>
<div class="px-2 text-sm dark:text-gray-200">
<form action="{{ route('article.comment.store', $article) }}" method="POST">
@csrf
<textarea name="comment"
class="focus:ring-0 border-2 border-gray-200 rounded dark:bg-gray-800 dark:border-gray-700 dark:text-gray-200 focus:border-[var(--color-primary)] w-full min-h-[100px] max-h-[100px] @error('comment') border-red-600 ring-red-500 @enderror"></textarea>
<x-form.primary-button classes="mt-2">
{{ __('Post comment') }}
</x-form.primary-button>
</form>
</div>
</x-content.content-card>
@endif
<x-content.content-card icon="hotel-icon" classes="border dark:border-gray-900">
<x-slot:title>
{{ __('Comments') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Below you will see all the comments, written on this article') }}
</x-slot:under-title>
<div class="px-1 dark:text-gray-200 space-y-[13px]">
@foreach ($article->comments as $comment)
<div
class="relative w-full rounded bg-[#f5f5f5] dark:bg-gray-700 p-4 h-[90px] overflow-hidden flex items-center shadow-sm">
<a href="{{ route('profile.show', $comment->user) }}"
class="absolute top-2 left-1 ">
<img style="image-rendering: pixelated;"
class="transition duration-300 ease-in-out hover:scale-105"
src="{{ setting('avatar_imager') }}{{ $comment->user->look }}&direction=2&head_direction=3&gesture=sml&action=wav"
alt="">
</a>
<div class="flex justify-between ml-[60px] w-full">
<div class="text-sm">
<a href="{{ route('profile.show', $comment->user) }}"
class="font-semibold text-[#89cdf0] dark:text-blue-300 hover:underline">
{{ $comment->user->username }}
</a>
<p class="block dark:text-gray-200">
{{ $comment->comment }}
</p>
</div>
<div class="flex gap-x-2">
<p class="text-xs dark:text-gray-200">
{{ $comment->created_at->diffForHumans() }}
</p>
@if ($comment->canBeDeleted())
<form action="{{ route('article.comment.destroy', $comment) }}" method="POST"
class="cursor-pointer transition duration-200 ease-in-out hover:scale-105">
@method('DELETE')
@csrf
<button type="submit">
<svg xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="h-4 w-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
</button>
</form>
@endif
</div>
</div>
</div>
@endforeach
</div>
</x-content.content-card>
@endif
@if($article->user)
<div class="rounded-lg overflow-hidden" style="background-color: var(--color-surface); border: 1px solid color-mix(in srgb, var(--color-text-muted) 15%, transparent);">
<div class="relative w-full h-12" style="background: linear-gradient(140deg, var(--color-primary) 0%, color-mix(in srgb, var(--color-primary) 80%, black) 100%);">
<div class="flex items-center justify-center h-full">
<h2 class="text-white font-bold text-lg">{{ __('Author') }}</h2>
</div>
</div>
<div class="p-4 text-center">
<a href="{{ route('profile.show', $article->user) }}">
<img style="image-rendering: pixelated;" class="mx-auto w-16 h-16 rounded-lg hover:scale-105 transition-transform"
src="{{ setting('avatar_imager') }}{{ $article->user->look }}&direction=2&head_direction=3&gesture=sml&action=wav" alt="">
</a>
<a href="{{ route('profile.show', $article->user) }}" class="block mt-2 font-semibold text-sm hover:underline" style="color: var(--color-primary);">
{{ $article->user->username }}
</a>
<p class="text-xs" style="color: var(--color-text-muted);">{{ $article->user->motto ?? setting('start_motto') }}</p>
<div class="flex justify-center mt-2">
<div class="w-3 h-3 rounded-full {{ $article->user->online ? 'bg-green-500' : 'bg-red-500' }}"></div>
<span class="text-xs ml-1" style="color: var(--color-text-muted);">{{ $article->user->online ? __('Online') : __('Offline') }}</span>
</div>
<span class="inline-block mt-2 rounded px-2 py-0.5 text-xs font-semibold" style="background-color: color-mix(in srgb, var(--color-primary) 15%, transparent); color: var(--color-primary);">
{{ $article->user && !$article->user->hidden_staff ? $article->user->permission->rank_name ?? 'Member' : 'Member' }}
</span>
</div>
</div>
@endif
</div>
</x-app-layout>