Initial commit

This commit is contained in:
root
2026-05-09 17:28:23 +02:00
commit 9d73f82529
5575 changed files with 281989 additions and 0 deletions
+336
View File
@@ -0,0 +1,336 @@
@import "tailwindcss";
@plugin "@tailwindcss/forms";
@plugin "@tailwindcss/typography";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--font-sans: "Nunito", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--breakpoint-sm: 40rem;
--breakpoint-md: 48rem;
--breakpoint-lg: 64rem;
--breakpoint-xl: 80rem;
--breakpoint-2xl: 96rem;
}
@layer components {
.dropdown-item {
@apply w-full text-left px-2 py-1 transition duration-200 ease-in-out text-sm hover:bg-[#8770b2];
}
}
:focus {
outline: none;
}
body {
overflow-x: hidden;
}
#app {
min-height: calc(100vh - 3.5rem);
}
img {
-webkit-user-drag: none;
-khtml-user-drag: none;
-moz-user-drag: none;
-o-user-drag: none;
user-drag: none;
pointer-events: none;
}
.site-bg {
position: absolute;
width: 100%;
height: 400px;
background: url("/public/assets/images/dusk/background_image.png") no-repeat;
background-size: cover;
-webkit-mask-image: linear-gradient(to top, transparent, black);
mask-image: linear-gradient(to top, transparent, black);
background-color: #957cc3;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(46, 45, 45, 0.7); /* Black overlay with 60% opacity */
}
}
.nav-header {
background-color: #171a23;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
.icon {
height: 40px;
width: 40px;
filter: grayscale(1);
transition: filter 300ms ease-in-out;
}
a:hover .icon {
filter: grayscale(0);
}
.dropdown-parent:hover .icon {
filter: grayscale(0);
}
.dropdown-parent:active .icon {
filter: grayscale(0);
}
a.active, div.active {
color: #ac93da;
.dropdown-children {
color: #FFFFFF;
}
.icon {
filter: grayscale(0);
}
}
}
.photo-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.1);
z-index: 1;
}
.sub-header {
color: #FFF;
height: 40px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.main-content {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.custom-shadow {
box-shadow: 0 7px 16px rgba(0, 0, 0, 0.3), 0 1px 0 rgba(0, 0, 0, 0.3);
}
.swiper-pagination {
top: 3px;
z-index: 15 !important;
height: 10px;
}
.read-more-link {
z-index: 99999; /* Adjust as needed */
position: relative;
}
.swiper-button-prev {
z-index: 15 !important;
}
.swiper-button-next {
z-index: 15 !important;
}
.hotel-icon {
background: url("/public/assets/images/icons/feeds.png") no-repeat center;
}
.chat-icon {
background: url("/public/assets/images/icons/chat.png") no-repeat center;
}
.article-icon {
background: url("/public/assets/images/icons/article.gif") no-repeat center;
}
.lighthouse-icon {
background: url("/public/assets/images/icons/lighthouse.png") no-repeat center;
}
.currency-icon {
background: url("/public/assets/images/dusk/store_icon.png") no-repeat center;
}
.catalog-icon {
background: url("/public/assets/images/icons/catalog.png") no-repeat center;
}
.inventory-icon {
background: url("/public/assets/images/icons/inventory.png") no-repeat center;
}
.duo-chat-icon {
background: url("/public/assets/images/icons/due-chat.png") no-repeat center;
}
.friends-icon {
background: #b17f85 url("/public/assets/images/icons/friends.png") no-repeat center;
}
.nav-credit-icon {
background: #e9b124 url("/public/assets/images/icons/currency/credits.png") no-repeat center;
outline: 1px solid #b26d18;
}
.nav-ducket-icon {
background: #c44aac url("/public/assets/images/icons/currency/duckets.png") no-repeat center;
outline: 1px solid #812378;
}
.nav-diamond-icon {
background: #caf1f3 url("/public/assets/images/icons/currency/diamonds.png") no-repeat center;
outline: 1px solid #6caff4;
}
.swal2-toast {
background-color: #333 !important;
color: #fff;
}
.article-image {
display: block;
width: 100%;
height: 100px;
background-size: cover;
background-position: right;
margin: 0 0 20px;
transition: .3s;
}
.leaderboard-position {
color: transparent;
height: 25px;
width: 25px;
&.first {
background-image: url("/public/assets/images/leaderboards/trophy-gold.png");
}
&.second {
background-image: url("/public/assets/images/leaderboards/trophy-silver.png");
}
&.third {
background-image: url("/public/assets/images/leaderboards/trophy-bronze.png");
}
}
.leaderboard-background {
background-image: url("/public/assets/images/dusk/leaderboard_circle_image.png");
}
.staff-bg {
background-blend-mode: multiply;
}
/* Nitro disconnect overlay */
#disconnected {
display: none;
}
.atom-align-left {
float: left;
margin: 0 10px 10px 0;
}
.atom-align-right {
float: right;
margin: 0 0 10px 10px;
}
.atom-align-center {
display: block;
margin: 0 auto;
text-align: center;
}
.atom-align-center > * {
text-align: center;
}
#article-content a {
color: #53b2f8;
}
.badge-drawer-button {
background-image: linear-gradient(to bottom, #f2f2f3 51%, #d9d8d8 49%);
box-shadow: inset 0 0 0px 2px #d9d9d9;
padding: 10px;
&:hover {
background: linear-gradient(to bottom, #ffffff 50%, #EBEBEB 50%);
box-shadow: inset 0 0 0px 2px #ffffff;
}
.toggled {
background: #f2f2f3;
}
}
html.dark .badge-drawer-button {
background-image: linear-gradient(to bottom, #141414 51%, #101010 49%);
box-shadow: inset 0 0 0px 2px #242424;
padding: 10px;
&:hover {
background: linear-gradient(to bottom, #171717 50%, #111111 50%);
box-shadow: inset 0 0 0px 2px #363636;
}
.toggled {
background: #f2f2f3;
}
}
.badge-drawer-palette {
background-position: center;
background-repeat: no-repeat;
color: #000;
height: 40px;
cursor: pointer;
border-radius: 0.25rem;
align-items: center;
justify-content: center;
gap: 0.5rem;
display: flex;
border-width: 3px;
border-color: #b6bec5;
position: relative;
overflow: hidden;
max-width: 38px;
width: 100%;
&::after {
content: "";
z-index: 2;
position: absolute;
top: 0;
left: 0;
right: 0;
height: 50%;
background-color: #fff;
opacity: .1;
}
}
+40
View File
@@ -0,0 +1,40 @@
import "./bootstrap";
import "./external/flowbite";
import "swiper/css";
import "swiper/css/navigation";
import "swiper/css/pagination";
import Alpine from "alpinejs";
import Focus from "@alpinejs/focus";
import ArticleReactions from "./components/ArticleReactions.js";
import Swiper from "swiper";
import { Navigation, Pagination } from "swiper/modules";
ArticleReactions.init();
Alpine.plugin(Focus);
Alpine.start();
Swiper.use([Navigation, Pagination]);
// Swiper Initialization
document.addEventListener("DOMContentLoaded", function () {
const swiper = new Swiper(".swiper", {
// Your Swiper options here
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
pagination: {
el: ".swiper-pagination",
},
});
});
console.log(
"%cAtom CMS%c\n\nAtom CMS is a CMS for made for the community to enjoy. You can join our wonderful community at https://discord.gg/rX3aShUHdg\n\n",
"color: #14619c; -webkit-text-stroke: 2px black; font-size: 32px; font-weight: bold;",
"",
);
+31
View File
@@ -0,0 +1,31 @@
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
import axios from "axios";
window.axios = axios;
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo';
// import Pusher from 'pusher-js';
// window.Pusher = Pusher;
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: import.meta.env.VITE_PUSHER_APP_KEY,
// wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_CLUSTER}.pusher.com`,
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
// enabledTransports: ['ws', 'wss'],
// });
+131
View File
@@ -0,0 +1,131 @@
import Alpine from "alpinejs";
const ArticleReactions = {
init() {
document.addEventListener("alpine:init", () => this.startComponent());
},
startComponent() {
Alpine.data(
"reactions",
(myReactions = [], articleReactions = [], url = "") => ({
url,
myReactions,
articleReactions,
allReactions: [],
isAuthenticated: false,
init() {
this.treatArticleReactions();
this.allReactions = window.App.defaultReactions;
this.isAuthenticated = window.App.isAuthenticated;
this.dispatchFlowbiteEvent();
},
treatArticleReactions() {
let articleReactions = this.articleReactions;
this.articleReactions = [];
Object.entries(articleReactions).forEach((reactionData) => {
let reactionName = reactionData[0],
reactions = Object.values(reactionData[1]);
this.articleReactions.push({
id: this.generateVirtualReactionId(reactionName),
name: reactionName,
count: reactions.length,
users: reactions.map((reaction) => reaction.user?.username ?? ""),
});
});
},
toggleReaction(reaction) {
if (!this.url.length || !this.isAuthenticated) return;
axios.post(this.url, { reaction }).then((response) => {
if (!response.data.success) return;
if (!response.data.added) {
this.removeReaction(reaction, response.data.username);
return;
}
this.addReaction(reaction, response.data.username);
});
},
addReaction(name, username) {
this.myReactions.push(name);
let existingReaction = this.getReactionDataFromName(name);
if (existingReaction) {
existingReaction.count++;
existingReaction.users.push(username);
return;
}
this.articleReactions.push({
id: this.generateVirtualReactionId(name),
name,
count: 1,
users: [username],
});
this.dispatchFlowbiteEvent();
},
removeReaction(name, username) {
this.myReactions.splice(this.myReactions.indexOf(name), 1);
let reactionData = this.getReactionDataFromName(name);
if (reactionData.count > 1) {
reactionData.count--;
reactionData.users.splice(reactionData.users.indexOf(username), 1);
return;
}
this.$nextTick(() => {
this.articleReactions.splice(
this.articleReactions.indexOf(reactionData),
1,
);
});
},
generateVirtualReactionId(name) {
return name + Math.floor(Math.random() * 1000);
},
canAddReactionFromModal(name) {
return !this.userHasReaction(name) && !this.articleHasReaction(name);
},
userHasReaction(reaction) {
return this.myReactions.includes(reaction.name);
},
articleHasReaction(name) {
return typeof this.getReactionDataFromName(name) !== "undefined";
},
getReactionDataFromName(name) {
return this.articleReactions.find(
(reaction) => reaction.name === name,
);
},
dispatchFlowbiteEvent() {
this.$nextTick(() =>
document.dispatchEvent(new CustomEvent("reactions:loaded")),
);
},
}),
);
},
};
export { ArticleReactions as default };
+5332
View File
File diff suppressed because it is too large Load Diff
+111
View File
@@ -0,0 +1,111 @@
<x-app-layout>
@push('title', __('Welcome to the best hotel on the web!'))
<div class="col-span-12 md:col-span-6 min-h-[250px] bg-gray-900/50 rounded-xl flex flex-col py-6 px-8 text-white">
<h2 class="text-2xl">{{ __('Login') }}</h2>
<form action="{{ route('login') }}" method="POST">
@csrf
<div class="relative w-full overflow-hidden text-black">
<input id="username-input" type="text" placeholder="{{ __('Enter your username') }}" name="username" class="relative py-2 rounded-md mt-3 w-full">
<img id="user-avatar" class="absolute right-0 -top-4" src="{{ asset('/assets/images/dusk/ghost.png') }}" alt="">
</div>
<input type="password" placeholder="{{ __('Enter your password') }}" name="password" class="relative py-2 rounded-md mt-3 text-black w-full">
<x-site-captchas />
<div class="mt-4 flex gap-4">
<button type="submit" class="py-2 px-4 text-white bg-yellow-500 border-2 border-yellow-300 w-full rounded-md transition duration-300 ease-in-out hover:scale-[102%]">{{ __('Login') }}</button>
<a href="{{ route('register') }}" class="w-full">
<button type="button" class="py-2 px-4 text-white bg-gray-700 border-2 border-gray-600 w-full rounded-md transition duration-300 ease-in-out hover:scale-[102%]">{{ __('Register') }}</button>
</a>
</div>
</form>
</div>
{{-- Articles --}}
<div class="col-span-12 md:col-span-6 h-[250px]">
<!-- Slider main container -->
<div class="swiper h-[250px] rounded-md">
<!-- If we need pagination -->
<div class="swiper-pagination"></div>
<!-- If we need navigation buttons -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
<!-- Additional required wrapper -->
<div class="swiper-wrapper" style="z-index: 14;">
@foreach($articles as $article)
<x-article-card :article="$article" />
@endforeach
</div>
</div>
</div>
<div class="col-span-12 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
@foreach($photos as $photo)
<a href="{{ $photo->url }}" data-fancybox="gallery" class="cursor-pointer relative transition duration-300 ease-in-out hover:scale-[102%]">
<div class="photo-overlay"></div>
<img class="h-[250px] w-full object-cover object-center rounded-md custom-shadow" src="{{ $photo->url }}" alt="">
<div class="absolute right-2 bottom-2 bg-black/70 p-2 rounded-md text-white flex gap-x-2 z-[5]">
<img class="self-center" src="{{ asset('/assets/images/dusk/author_camera_icon.png') }}" alt="">
<small>
{{ $photo->user->username }}
</small>
</div>
</a>
@endforeach
</div>
<script>
function debounce(func, wait) {
let timeout;
return function() {
const context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
const avatar = document.getElementById('user-avatar');
const usernameInput = document.getElementById('username-input');
const updateAvatar = debounce(async () => {
const username = usernameInput.value;
if (!username) return;
try {
const response = await fetch(`/api/user/${username}`);
if (!response.ok) {
console.error('Failed to fetch avatar');
return;
}
const data = await response.json();
if (!data.data.look) {
avatar.src = "/assets/images/dusk/ghost.png";
return;
}
avatar.src = '{{ setting('avatar_imager') }}' + '/' + data.data.look + '&direction=4&action=wav&head_direction=3';
} catch (error) {
console.error('An error occurred:', error);
}
}, 200);
usernameInput.addEventListener('keyup', updateAvatar);
</script>
</x-app-layout>
@@ -0,0 +1,53 @@
<x-app-layout>
@push('title', __('Two factor'))
<div class="col-span-12 flex flex-col gap-y-3 md:col-span-3">
<x-user.settings.settings-navigation />
</div>
<div class="col-span-12 flex flex-col gap-y-3 md:col-span-9">
<x-content.content-card icon="hotel-icon" classes="border dark:border-gray-900">
<x-slot:title>
{{ __('Confirm your password') }}
</x-slot:title>
<x-slot:under-title>
{{ __('You must confirm your password to continue') }}
</x-slot:under-title>
<form method="POST" action="/user/confirm-password">
@csrf
<!-- Password -->
<div class="flex flex-col gap-y-2 mb-3">
<div>
<x-form.label for="password">
{{ __('Password') }}
<x-slot:info>
{{ __('You must confirm your current password before being able to toggle 2FA.') }}
</x-slot:info>
</x-form.label>
<x-form.input name="password" type="password"
placeholder="{{ __('Enter your current password') }}" />
</div>
</div>
@if (setting('google_recaptcha_enabled'))
<div class="mt-4 g-recaptcha" data-sitekey="{{ config('habbo.site.recaptcha_site_key') }}"></div>
@endif
@if (setting('cloudflare_turnstile_enabled'))
<x-turnstile />
@endif
<div class="mt-4">
<x-form.primary-button>
{{ __('Confirm password') }}
</x-form.primary-button>
</div>
</form>
</x-content.content-card>
</div>
</x-app-layout>
@@ -0,0 +1,40 @@
<x-app-layout>
@push('title', __('Forgot password'))
<div class="col-span-12">
<x-content.content-card icon="hotel-icon" classes="max-w-[640px] mx-auto">
<x-slot:title>
{{ __('Did you forget your password?') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
</x-slot:under-title>
<form method="POST" action="{{ route('forgot.password.post') }}">
@csrf
<!-- Email Address -->
<div>
<div class="flex flex-col gap-y-2">
<x-form.label for="mail">
{{ __('Email') }}
<x-slot:info>
{{ __('Enter your email') }}
</x-slot:info>
</x-form.label>
</div>
<x-form.input name="mail" type="email" placeholder="{{ __('Enter your email') }}"/>
</div>
<div class="mt-4">
<x-form.primary-button>
{{ __('Email Password Reset Link') }}
</x-form.primary-button>
</div>
</form>
</x-content.content-card>
</div>
</x-app-layout>
+50
View File
@@ -0,0 +1,50 @@
<x-app-layout>
@push('title', __('Reset Password'))
<div class="col-span-12">
<x-content.content-card icon="hotel-icon" classes="max-w-[640px] mx-auto">
<x-slot:title>
{{ __('Reset Password') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Choose a new password for your Account.') }}
</x-slot:under-title>
<form method="POST" action="{{ route('reset.password.post', $token) }}">
@csrf
<!-- Password -->
<div class="bg-[#efefef] rounded-md p-3 flex flex-col gap-y-6 dark:bg-gray-900">
<div>
<x-form.label for="password">
{{ __('Password') }}
<x-slot:info>
{{ __('Your password must contain atleast 8 characters. Make sure to use a unique & secure password.') }}
</x-slot:info>
</x-form.label>
<x-form.input name="password" type="password" placeholder="{{ __('Choose a secure password') }}"/>
</div>
<hr class="dark:border-gray-700">
<!-- Confirm Password -->
<div>
<x-form.label for="password_confirmation">
{{ __('Repeat Password') }}
</x-form.label>
<x-form.input name="password_confirmation" type="password" placeholder="{{ __('Repeat your chosen password') }}"/>
</div>
</div>
<div class="mt-4">
<x-form.primary-button>
{{ __('Reset Password') }}
</x-form.primary-button>
</div>
</form>
</x-content.content-card>
</div>
</x-app-layout>
+188
View File
@@ -0,0 +1,188 @@
<x-app-layout>
@push('title', __('Welcome to the best hotel on the web!'))
<div class="col-span-12 md:col-span-6 bg-gray-900/50 rounded-xl flex flex-col py-6 px-8 text-white self-start">
<h2 class="text-2xl">{{ __('Create a new account') }}</h2>
<form action="{{ route('register') }}" method="POST">
@csrf
<div class="grid grid-cols-12 gap-3 mt-4">
<div class="relative w-full overflow-hidden text-black col-span-12">
<input id="username-input" type="text" placeholder="Enter your username" name="username" value="{{ old('username') }}" class="relative py-2 rounded-md w-full" required>
</div>
<div class="relative w-full overflow-hidden text-black col-span-12">
<input id="username-input" type="email" placeholder="Enter your e-mail" name="mail" value="{{ old('mail') }}" class="relative py-2 rounded-md w-full" required>
</div>
<div class="col-span-12">
<input type="password" placeholder="Enter your password" name="password" class="relative py-2 rounded-md text-black w-full" required>
</div>
<div class="col-span-12">
<input type="password" placeholder="Confirm your password" name="password_confirmation" class="relative py-2 rounded-md text-black w-full" required>
</div>
@if (setting('requires_beta_code'))
<div class="col-span-12">
<input type="text" placeholder="Beta code" name="beta_code" class="relative py-2 rounded-md text-black w-full" required>
</div>
@endif
<div class="col-span-12 mt-2">
<label class="text-sm text-white mb-1 block">{{ __('Choose avatar') }}</label>
<div class="grid grid-cols-5 gap-1">
@php
$femaleFigures = array_slice(array_filter(array_map('trim', explode(',', setting('register_female_figures', 'hr-100-45.hd-180-1.ch-215-62.lg-270-62.sh-290-62,hr-130-45.hd-180-2.ch-220-62.lg-270-62.sh-305-62,hr-140-45.hd-180-3.ch-225-62.lg-270-62.sh-310-62,hr-165-45.hd-180-1.ch-210-62.lg-270-62.sh-290-62,hr-190-45.hd-180-2.ch-215-62.lg-270-62.sh-305-62')))), 0, 5);
$maleFigures = array_slice(array_filter(array_map('trim', explode(',', setting('register_male_figures', 'hr-100-61.hd-180-1.ch-210-66.lg-270-110.sh-305-62,hr-105-61.hd-190-2.ch-215-66.lg-280-110.sh-310-62,hr-110-61.hd-200-3.ch-220-66.lg-290-110.sh-320-62,hr-115-61.hd-185-1.ch-210-66.lg-275-110.sh-315-62,hr-120-61.hd-180-2.ch-205-66.lg-285-110.sh-305-62')))), 0, 5);
$allFigures = array_merge($femaleFigures, $maleFigures);
@endphp
@foreach($allFigures as $index => $figure)
<label class="cursor-pointer relative group">
<input type="radio" name="look" value="{{ $figure }}"
class="peer sr-only" {{ $index === 5 ? 'checked' : '' }}>
<div class="p-0.5 rounded border-2 border-gray-600
peer-checked:border-yellow-400 peer-checked:bg-yellow-400/20
hover:border-gray-500 transition-all duration-300
peer-checked:scale-110 peer-checked:shadow-lg peer-checked:shadow-yellow-400/30"
onmouseover="this.querySelector('img').src='https://www.habbo.nl/habbo-imaging/avatarimage?figure={{ $figure }}&size=s&action=wav'"
onmouseout="if(!this.previousElementSibling.checked) this.querySelector('img').src='https://www.habbo.nl/habbo-imaging/avatarimage?figure={{ $figure }}&size=s'">
<img src="https://www.habbo.nl/habbo-imaging/avatarimage?figure={{ $figure }}&size=s"
class="w-full h-auto transition-transform duration-300" alt="Avatar">
</div>
</label>
@endforeach
</div>
</div>
</div>
<div class="flex items-center gap-x-3 mt-2">
<input id="terms" type="checkbox" name="terms"
class="mt-1 rounded ring-0 focus:ring-0">
<a href="{{ route('help-center.rules.index') }}" target="_blank"
class="mt-1 text-sm font-semibold text-white hover:text-gray-900 hover:underline hover:text-gray-200">
{{ __('I accept the :hotel terms & rules.', ['hotel' => setting('hotel_name')]) }}
</a>
</div>
{{-- Used to determine the refer --}}
<input type="hidden" name="referral_code" value="{{ $referral_code }}">
<x-site-captchas />
<div class="mt-4 grid grid-cols-2 gap-3">
<button type="submit" class="py-2 px-4 text-white bg-yellow-500 border-2 border-yellow-300 w-full rounded-md transition duration-300 ease-in-out hover:scale-[102%]">{{ __('Register') }}</button>
<a href="{{ route('login') }}" class="w-full">
<button type="button" class="py-2 px-4 text-white bg-gray-700 border-2 border-gray-600 w-full rounded-md transition duration-300 ease-in-out hover:scale-[102%]">{{ __('Back to login') }}</button>
</a>
</div>
</form>
</div>
{{-- Articles --}}
<div class="col-span-12 md:col-span-6 ">
<!-- Slider main container -->
<div class="swiper h-[250px] rounded-md">
<!-- If we need pagination -->
<div class="swiper-pagination"></div>
<!-- If we need navigation buttons -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
<!-- Additional required wrapper -->
<div class="swiper-wrapper" style="z-index: 14;">
@foreach($articles as $article)
<div class="swiper-slide relative article-image" style="background-image: url({{ $article->image }})">
<div class="absolute h-[90px] w-full left-0 bottom-0 bg-[#171a23]/95 text-white py-2 px-4">
<h2 class="text-3xl font-bold">
{{ $article->title }}
</h2>
<div class="flex justify-between items-center">
<div class="py-1 px-2 rounded-md bg-black/60 text-sm mt-2 flex gap-1 items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
</svg>
{{ $article->user->username }}
</div>
<a href="{{ route('article.show', $article->slug) }}" class="text-sm read-more-link hover:underline">
Read more
</a>
</div>
</div>
</div>
@endforeach
</div>
</div>
<div class="col-span-12 grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
@foreach($photos as $photo)
<a href="{{ $photo->url }}" data-fancybox="gallery" class="cursor-pointer relative transition duration-300 ease-in-out hover:scale-[102%]">
<div class="photo-overlay"></div>
<img class="h-[250px] w-full object-cover object-center rounded-md custom-shadow" src="{{ $photo->url }}" alt="">
<div class="absolute right-2 bottom-2 bg-black/70 p-2 rounded-md text-white flex gap-x-2 z-[5]">
<img class="self-center" src="{{ asset('/assets/images/dusk/author_camera_icon.png') }}" alt="">
<small>
{{ $photo->user->username }}
</small>
</div>
</a>
@endforeach
</div>
</div>
<script>
function debounce(func, wait) {
let timeout;
return function() {
const context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
const avatar = document.getElementById('user-avatar');
const usernameInput = document.getElementById('username-input');
const updateAvatar = debounce(async () => {
const username = usernameInput.value;
if (!username) return;
try {
const response = await fetch(`/api/user/${username}`);
if (!response.ok) {
console.error('Failed to fetch avatar');
return;
}
const data = await response.json();
if (!data.data.look) {
avatar.src = "/assets/images/dusk/ghost.png";
return;
}
avatar.src = '{{ setting('avatar_imager') }}' + '/' + data.data.look + '&direction=4&action=wav&head_direction=3';
} catch (error) {
console.error('An error occurred:', error);
}
}, 200);
usernameInput.addEventListener('keyup', updateAvatar);
</script>
<script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.umd.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.css" />
</x-app-layout>
@@ -0,0 +1,57 @@
<x-app-layout>
@push('title', __('Two-Factor Authentication'))
<div class="col-span-12 flex flex-col gap-y-3 md:col-span-9 md:col-start-4">
<x-content.content-card icon="hotel-icon" classes="border dark:border-gray-900">
<x-slot:title>
{{ __('Two-Factor Authentication') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Please enter your two-factor authentication code or one of your recovery codes.') }}
</x-slot:under-title>
@if (session('error'))
<div class="alert alert-danger">
{{ session('error') }}
</div>
@endif
<form method="POST" action="{{ route('two-factor.login') }}">
@csrf
<!-- Two-Factor Code -->
<div>
<x-form.label for="code">
{{ __('Code') }}
<x-slot:info>
{{ __('Enter the two-factor authentication code from your authenticator app.') }}
</x-slot:info>
</x-form.label>
<x-form.input id="code" name="code" type="text" placeholder="{{ __('Code') }}" class="mb-3" />
</div>
<!-- Recovery Code -->
<div class="mt-4">
<x-form.label for="recovery_code">
{{ __('Recovery Code') }}
<x-slot:info>
{{ __('Enter one of your recovery codes if you cannot access your authenticator app.') }}
</x-slot:info>
</x-form.label>
<x-form.input id="recovery_code" name="recovery_code" type="text" placeholder="{{ __('Recovery Code') }}" class="mb-3" />
</div>
@if (setting('google_recaptcha_enabled'))
<div class="g-recaptcha" data-sitekey="{{ config('habbo.site.recaptcha_site_key') }}"></div>
@endif
@if (setting('cloudflare_turnstile_enabled'))
<x-turnstile />
@endif
<x-form.secondary-button type="submit" class="mt-4">
{{ __('Verify') }}
</x-form.secondary-button>
</form>
</x-content.content-card>
</div>
</x-app-layout>
+35
View File
@@ -0,0 +1,35 @@
<x-guest-layout>
<div class="flex flex-col items-center min-h-screen pt-6 bg-gray-100 sm:justify-center sm:pt-0">
<div class="w-full px-6 py-4 mt-6 overflow-hidden bg-white shadow-md sm:max-w-md sm:rounded-lg">
<div class="mb-4 text-sm text-gray-600">
{{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
</div>
@if (session('status') == 'verification-link-sent')
<div class="mb-4 text-sm font-medium text-green-600">
{{ __('A new verification link has been sent to the email address you provided during registration.') }}
</div>
@endif
<div class="flex items-center justify-between mt-4">
<form method="POST" action="{{ route('verification.send') }}">
@csrf
<div>
<button type="submit" class="inline-flex items-center px-4 py-2 ml-4 text-xs font-semibold tracking-widest text-white uppercase transition duration-150 ease-in-out bg-gray-800 border border-transparent rounded-md hover:bg-gray-700 active:bg-gray-900 focus:outline-hidden focus:border-gray-900 focus:ring-3 ring-gray-300 disabled:opacity-25">
{{ __('Resend Verification Email') }}
</button>
</div>
</form>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="text-sm text-gray-600 underline hover:text-gray-900">
{{ __('Log Out') }}
</button>
</form>
</div>
</div>
</div>
</x-guest-layout>
+88
View File
@@ -0,0 +1,88 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>{{ setting('hotel_name') }} - {{ __('Client') }}</title>
<script src="{{ asset('assets/js/jquery-latest.js') }}" type="text/javascript"></script>
<script src="{{ asset('assets/js/jquery-ui.js') }}" type="text/javascript"></script>
<script src="{{ asset('assets/js/flashclient.js') }}"></script>
<script type="text/javascript" src="{{ asset('assets/js/swfobject.js') }}"></script>
<link rel="stylesheet" href="{{ asset('assets/css/clientnew.css') }}" type="text/css">
<link rel="stylesheet" href="{{ asset('assets/css/no-flash.css') }}" type="text/css">
<script type="text/javascript">
var flashvars = {
"connection.info.host": "{{ config('habbo.flash.host') }}",
"connection.info.port": "{{ config('habbo.flash.port') }}",
"site.url": "",
"url.prefix": "",
"client.reload.url": "",
"client.fatal.error.url": "",
"client.connection.failed.url": "",
"logout.url": "",
"client.starting": "Please wait! Habbo is starting up.",
"client.starting.revolving": "For science, you monster\/Loading funny message\u2026please wait.\/Would you like fries with that?\/Follow the yellow duck.\/Time is just an illusion.\/Are we there yet?!\/I like your t-shirt.\/Look left. Look right. Blink twice. Ta da!\/It\'s not you, it\'s me.\/Shhh! I\'m trying to think here.\/Loading pixel universe.",
"client.notify.cross.domain": "1",
"client.allow.cross.domain": "1",
"flash.client.origin": "popup",
"processlog.enabled": "0",
"sso.ticket": "{{ $sso }}",
"productdata.load.url": "{{ sprintf('%s/%s/%s', config('habbo.site.site_url'), config('habbo.flash.swf_base_path'), config('habbo.flash.external_productdata')) }}",
"furnidata.load.url": "{{ sprintf('%s/%s/%s', config('habbo.site.site_url'), config('habbo.flash.swf_base_path'), config('habbo.flash.external_furnidata')) }}",
"external.texts.txt": "{{ sprintf('%s/%s/%s', config('habbo.site.site_url'), config('habbo.flash.swf_base_path'), config('habbo.flash.external_texts')) }}",
"external.variables.txt": "{{ sprintf('%s/%s/%s', config('habbo.site.site_url'), config('habbo.flash.swf_base_path'), config('habbo.flash.external_variables')) }}",
"external.figurepartlist.txt": "{{ sprintf('%s/%s/%s', config('habbo.site.site_url'), config('habbo.flash.swf_base_path'), config('habbo.flash.external_figuredata')) }}",
"flash.dynamic.avatar.download.configuration": "{{ sprintf('%s/%s/%s', config('habbo.site.site_url'), config('habbo.flash.swf_base_path'), config('habbo.flash.external_figuremap')) }}",
"external.override.texts.txt": "{{ sprintf('%s/%s/%s', config('habbo.site.site_url'), config('habbo.flash.swf_base_path'), config('habbo.flash.external_override_texts')) }}",
"external.override.variables.txt": "{{ sprintf('%s/%s/%s', config('habbo.site.site_url'), config('habbo.flash.swf_base_path'), config('habbo.flash.external_override_variables')) }}",
"flash.client.url": "{{ sprintf('%s/%s/%s', config('habbo.site.site_url'), config('habbo.flash.swf_base_path'), config('habbo.flash.production_folder')) }}/",
};
window.FlashExternalInterface.disconnect = function() {
window.location.href = "{{ route('me.show') }}";
};
var params = {
"base": "{{ sprintf('%s/%s/%s', config('habbo.site.site_url'), config('habbo.flash.swf_base_path'), config('habbo.flash.production_folder')) }}/",
"allowScriptAccess": "always",
"menu": "false",
"wmode": "opaque"
};
swfobject.embedSWF(
'{{ sprintf('%s/%s/%s/%s', config('habbo.site.site_url'), config('habbo.flash.swf_base_path'), config('habbo.flash.production_folder'), config('habbo.flash.habbo_swf')) }}',
'client', '100%', '100%', '11.1.0', '{{ asset('assets/js/expressInstall.swf') }}', flashvars, params, null,
null);
</script>
</head>
<body>
<div id="client">
<habbo-client-error>
<div class="client-error__background-frank">
<div class="client-error__text-contents">
<h1 class="client-error__title">{{ __('You are nearly in Habbo!') }}</h1>
<p>{{ __('Click the yellow Hotel button below, then click on run flash` when prompted to. See you in the Hotel!') }}
</p>
</div>
<div class="client-error__hotel-button-div">
<a href="https://www.adobe.com/go/getflashplayer" target="_blank" rel="noopener noreferrer"
class="hotel-button">
<span class="hotel-button__text">{{ __('Get flash') }}</span>
</a>
</div>
</div>
</habbo-client-error>
</div>
</body>
</html>
+532
View File
@@ -0,0 +1,532 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ setting('hotel_name') }} - Nitro</title>
<link href="https://fonts.googleapis.com/css2?family=Ubuntu+Condensed&display=swap" rel="stylesheet">
@vite(['resources/themes/' . setting('theme') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.js'], 'build')
@if(setting('button_enabled') == '1')
<style>
.cms-button {
background-color: {{ setting('toolbar_primary_color', setting('button_primary_color', '#eeb425')) }} !important;
background: {{ setting('toolbar_primary_color', setting('button_primary_color', '#eeb425')) }} !important;
color: {{ setting('toolbar_text_color', setting('button_text_color', '#1a1a2e')) }} !important;
border: {{ setting('button_border_width', '2') }}px solid {{ setting('toolbar_border_color', setting('button_border_color', '#cf9d15')) }} !important;
border-radius: {{ setting('button_border_radius', '8') }}px !important;
transition: {{ setting('button_transition', 'all') }} {{ setting('button_transition_duration', '300') }}ms !important;
}
.cms-button:hover {
background-color: {{ setting('toolbar_hover_color', setting('button_hover_color', '#cf9d15')) }} !important;
background: {{ setting('toolbar_hover_color', setting('button_hover_color', '#cf9d15')) }} !important;
transform: scale({{ setting('button_hover_scale', '1.05') }});
}
.toolbar-btn.cms-button {
padding: 8px !important;
display: flex;
align-items: center;
justify-content: center;
background-color: {{ setting('button_primary_color', '#eeb425') }} !important;
background: {{ setting('button_primary_color', '#eeb425') }} !important;
}
.toolbar-btn.cms-button svg {
stroke: {{ setting('button_text_color', '#1a1a2e') }} !important;
}
.toolbar-btn.cms-button:hover {
background-color: {{ setting('button_hover_color', '#cf9d15') }} !important;
background: {{ setting('button_hover_color', '#cf9d15') }} !important;
}
</style>
@endif
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Toolbar Container */
.toolbar {
position: fixed;
top: 16px;
left: 16px;
z-index: 9999;
display: flex;
align-items: center;
gap: 8px;
}
/* Standard Button Style - ALL buttons same size */
.toolbar-btn {
width: 40px;
height: 40px;
border: 2px solid {{ setting('toolbar_border_color', setting('button_border_color', '#cf9d15')) }};
border-radius: 6px;
background: linear-gradient(135deg, {{ setting('toolbar_primary_color', setting('button_primary_color', '#eeb425')) }} 0%, {{ setting('toolbar_hover_color', setting('button_hover_color', '#cf9d15')) }} 100%);
color: {{ setting('toolbar_text_color', setting('button_text_color', '#1a1a2e')) }};
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
transition: transform 0.2s, box-shadow 0.2s;
}
.toolbar-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5);
}
/* Radio Bar - Same height as buttons */
.radio-container {
width: 40px;
height: 40px;
border: 2px solid {{ setting('toolbar_border_color', setting('button_border_color', '#cf9d15')) }};
border-radius: 6px;
background: linear-gradient(135deg, {{ setting('toolbar_primary_color', setting('button_primary_color', '#eeb425')) }} 0%, {{ setting('toolbar_hover_color', setting('button_hover_color', '#cf9d15')) }} 100%);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
overflow: hidden;
transition: width 0.3s ease;
display: flex;
align-items: center;
}
.radio-container.expanded {
width: 320px;
}
.radio-toggle {
width: 36px;
height: 36px;
min-width: 36px;
border: none;
background: transparent;
color: {{ setting('button_text_color', '#1a1a2e') }};
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.radio-status-dot {
position: absolute;
top: 6px;
right: 6px;
width: 6px;
height: 6px;
border-radius: 50%;
background: #10b981;
animation: pulse 2s infinite;
}
.radio-status-dot.live {
background: #ef4444;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.radio-content {
display: flex;
align-items: center;
gap: 8px;
padding: 0 8px;
opacity: 0;
transition: opacity 0.2s;
white-space: nowrap;
}
.radio-container.expanded .radio-content {
opacity: 1;
}
.radio-divider {
width: 1px;
height: 24px;
background: rgba(26, 26, 46, 0.3);
}
.radio-info {
display: flex;
align-items: center;
gap: 6px;
}
.radio-dj-avatar {
width: 24px;
height: 24px;
border-radius: 4px;
border: 1px solid rgba(26, 26, 46, 0.3);
background: rgba(26, 26, 46, 0.1);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
flex-shrink: 0;
}
.radio-dj-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.radio-dj-name {
font-size: 10px;
font-weight: 700;
color: #1a1a2e;
text-transform: uppercase;
}
.radio-song {
font-size: 9px;
color: #1a1a2e;
opacity: 0.85;
}
.radio-control-btn {
width: 28px;
height: 28px;
border: none;
border-radius: 50%;
background: #1a1a2e;
color: #eeb425;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.radio-volume {
width: 50px;
height: 4px;
background: rgba(26, 26, 46, 0.3);
border-radius: 2px;
appearance: none;
cursor: pointer;
}
.radio-volume::-webkit-slider-thumb {
appearance: none;
width: 10px;
height: 10px;
border-radius: 50%;
background: #1a1a2e;
cursor: pointer;
}
.radio-listeners {
display: flex;
align-items: center;
gap: 3px;
color: #1a1a2e;
font-size: 10px;
font-weight: 600;
}
.hidden {
display: none !important;
}
</style>
</head>
<body class="overflow-hidden" id="nitro-client">
{{-- Toolbar --}}
<div class="toolbar">
{{-- Home Button --}}
<a href="{{ route('me.show') }}" class="toolbar-btn cms-button" data-turbolinks="false" title="Home">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
</a>
{{-- Reload Button --}}
<button class="toolbar-btn cms-button" onclick="location.reload()" title="Herladen">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="23 4 23 10 17 10"></polyline>
<polyline points="1 20 1 14 7 14"></polyline>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
</svg>
</button>
{{-- Fullscreen Button --}}
<button class="toolbar-btn cms-button" onclick="toggleFullscreen()" title="Fullscreen">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path>
</svg>
</button>
{{-- Online Count Button --}}
<button class="toolbar-btn cms-button" title="Online gebruikers">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
<span id="onlineCount" style="margin-left: 4px; font-size: 11px; font-weight: 700;">0</span>
</button>
{{-- Radio Player --}}
<div id="radioContainer" class="radio-container">
<button class="radio-toggle" onclick="toggleRadioBar()" title="Radio">
<div class="radio-status-dot" id="radioStatus"></div>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 18V5l12-2v13"></path>
<circle cx="6" cy="18" r="3"></circle>
<circle cx="18" cy="16" r="3"></circle>
</svg>
</button>
<div class="radio-content">
<div class="radio-divider"></div>
<div class="radio-info">
<div class="radio-dj-avatar" id="radioDjAvatar">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</div>
<div style="display: flex; flex-direction: column;">
<span class="radio-dj-name" id="radioDj">{{ __('radio.music') }}</span>
<span class="radio-song" id="radioSong">{{ __('radio.loading') }}</span>
</div>
</div>
<div class="radio-divider"></div>
<button class="radio-control-btn" onclick="toggleRadioPlay()" title="Play/Pause">
<svg id="playIcon" width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
<polygon points="5 3 19 12 5 21 5 3"/>
</svg>
<svg id="pauseIcon" width="12" height="12" viewBox="0 0 24 24" fill="currentColor" class="hidden">
<rect x="6" y="4" width="4" height="16"/>
<rect x="14" y="4" width="4" height="16"/>
</svg>
</button>
<div class="radio-divider"></div>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#1a1a2e" stroke-width="2">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
</svg>
<input type="range" class="radio-volume" id="radioVolume" min="0" max="100" value="50" onchange="setRadioVolume(this.value)">
<div class="radio-divider"></div>
<div class="radio-listeners">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#1a1a2e" stroke-width="2">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
<span id="radioListeners">0</span>
</div>
</div>
</div>
</div>
{{-- Nitro Client --}}
@php
$nitroUrl = sprintf('%s/index.html?sso=%s', setting('nitro_path'), $sso);
$toolbarParams = [];
// Toolbar with fallback to button colors (don't URL encode - # is valid in URLs)
$toolbarParams[] = 'toolbar_primary=' . (setting('toolbar_primary_color') ?: setting('button_primary_color', '#eeb425'));
$toolbarParams[] = 'toolbar_hover=' . (setting('toolbar_hover_color') ?: setting('button_hover_color', '#cf9d15'));
$toolbarParams[] = 'toolbar_border=' . (setting('toolbar_border_color') ?: setting('button_border_color', '#cf9d15'));
$toolbarParams[] = 'toolbar_text=' . (setting('toolbar_text_color') ?: setting('button_text_color', '#1a1a2e'));
if(count($toolbarParams)) $nitroUrl .= '&' . implode('&', $toolbarParams);
@endphp
<iframe id="nitro" src="{{ $nitroUrl }}"
class="absolute top-0 left-0 m-0 h-full w-full overflow-hidden border-none p-0"></iframe>
{{-- Disconnected Message --}}
<div id="disconnected" class="hidden" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 10000;">
<div style="position: absolute; width: 100%; height: 100%; background: rgba(0,0,0,0.7);"></div>
<div style="position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; gap: 16px;">
<h2 style="font-size: 24px; color: white;">{{ __('Verbinding verbroken') }}</h2>
<div style="display: flex; gap: 16px;">
<button onclick="location.reload()" style="padding: 12px 24px; background: #eeb425; border: 2px solid #cf9d15; border-radius: 6px; color: #1a1a2e; font-weight: 700; cursor: pointer;">
{{ __('Herladen') }}
</button>
<a href="{{ route('me.show') }}" style="padding: 12px 24px; background: transparent; border: 2px solid #eeb425; border-radius: 6px; color: #eeb425; text-decoration: none; font-weight: 700;">
{{ __('Terug') }}
</a>
</div>
</div>
</div>
<audio id="radioAudio" preload="none"></audio>
<script>
// Fullscreen
function toggleFullscreen() {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
document.documentElement.requestFullscreen();
}
}
// Online count
function updateOnlineCount() {
fetch("{{ route('api.online-count') }}")
.then(r => r.json())
.then(data => {
document.getElementById('onlineCount').textContent = data.data?.onlineCount || 0;
})
.catch(() => {});
}
setInterval(updateOnlineCount, 15000);
updateOnlineCount();
// Radio
let radioPlaying = false;
let radioStreamUrl = null;
let radioExpanded = false;
const radioAudio = document.getElementById('radioAudio');
function toggleRadioBar() {
const container = document.getElementById('radioContainer');
radioExpanded = !radioExpanded;
if (radioExpanded) {
container.classList.add('expanded');
if (!radioStreamUrl) loadRadio();
} else {
container.classList.remove('expanded');
}
}
function loadRadio() {
fetch("{{ route('api.radio.config') }}")
.then(r => r.json())
.then(data => {
if (data.enabled && data.stream_url) {
radioStreamUrl = data.stream_url;
radioAudio.src = radioStreamUrl;
document.getElementById('radioSong').textContent = 'Gereed';
fetch("{{ route('api.settings.radio.auto-play') }}")
.then(r => r.json())
.then(settings => {
if (settings.auto_play) toggleRadioPlay();
});
} else {
document.getElementById('radioSong').textContent = 'Offline';
}
})
.catch(() => {
document.getElementById('radioSong').textContent = 'Error';
});
}
function toggleRadioPlay() {
if (!radioStreamUrl) {
loadRadio();
return;
}
const playIcon = document.getElementById('playIcon');
const pauseIcon = document.getElementById('pauseIcon');
if (radioPlaying) {
radioAudio.pause();
playIcon.classList.remove('hidden');
pauseIcon.classList.add('hidden');
radioPlaying = false;
} else {
radioAudio.volume = document.getElementById('radioVolume').value / 100;
radioAudio.play()
.then(() => {
playIcon.classList.add('hidden');
pauseIcon.classList.remove('hidden');
radioPlaying = true;
})
.catch(() => {
document.getElementById('radioSong').textContent = 'Fout';
});
}
}
function setRadioVolume(val) {
radioAudio.volume = val / 100;
localStorage.setItem('radioVol', val);
}
const savedVol = localStorage.getItem('radioVol');
if (savedVol) {
document.getElementById('radioVolume').value = savedVol;
}
// Update radio info
function updateRadioInfo() {
if (!radioStreamUrl) return;
fetch("{{ route('api.radio.config') }}")
.then(r => r.json())
.then(data => {
const defaultLook = '{{ setting("radio_default_avatar_figure", "hr-893-45.hd-180-2.ch-210-62.lg-285-62.sh-295-62.ha-1012-62.wa-2007-0") }}';
const defaultName = '{{ setting("radio_default_dj_name", "Frank") }}';
const avatarUrl = "{{ setting('avatar_imager') }}" + (data.dj?.look || defaultLook) + '&headonly=1';
const avatarEl = document.getElementById('radioDjAvatar');
if (data.dj?.look) {
avatarEl.innerHTML = '<img src="' + avatarUrl + '" alt="DJ">';
} else {
avatarEl.innerHTML = '<img src="' + avatarUrl + '" alt="' + defaultName + '">';
}
const dj = data.dj ? (typeof data.dj === 'object' ? data.dj.username : data.dj) : defaultName;
document.getElementById('radioDj').textContent = dj;
const status = document.getElementById('radioStatus');
if (data.dj) {
status.classList.add('live');
} else {
status.classList.remove('live');
}
})
.catch(() => {});
fetch("{{ route('api.radio.now-playing') }}")
.then(r => r.json())
.then(data => {
const song = data.song || data.title || (data.now_playing?.title) || 'Live';
document.getElementById('radioSong').textContent = song;
})
.catch(() => {});
fetch("{{ route('api.radio.listeners') }}")
.then(r => r.json())
.then(data => {
document.getElementById('radioListeners').textContent = data.count || '0';
})
.catch(() => {});
}
setInterval(updateRadioInfo, 10000);
// Init
window.addEventListener('DOMContentLoaded', () => {
loadRadio();
updateRadioInfo();
});
</script>
<script src="{{ asset('assets/js/atom.js') }}"></script>
</body>
</html>
+141
View File
@@ -0,0 +1,141 @@
<x-app-layout>
@push('title', $article->title)
<div class="col-span-12 rounded space-y-3 md:col-span-3">
<x-community.staff-card :user="$article->user" />
<x-content.content-card icon="article-icon">
<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 p-3 shadow-sm bg-gray-800 text-gray-100">
<div class="relative flex h-24 flex-col items-center justify-center gap-y-1 overflow-hidden rounded px-2 text-white"
style="background: url({{ asset('storage/' . $article->image ) }}) center; background-size: cover;">
<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">
{{ $article->title }}</p>
<p class="relative w-full truncate text-center">{{ $article->short_story }}</p>
</div>
<div class="px-2" id="article-content">
{!! $article->full_story !!}
</div>
@include('community.partials.article-reactions')
</div>
@if ($article->can_comment)
@if (auth()->check() && !$article->userHasReachedArticleCommentLimit())
<x-content.content-card icon="hotel-icon">
<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="text-sm dark:text-gray-200">
<form action="{{ route('article.comment.store', $article) }}" method="POST">
@csrf
<textarea name="comment"
class="focus:ring-0 rounded focus:border-[#eeb425] w-full min-h-[100px] max-h-[100px] bg-gray-800 @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
@if(count($article->comments))
<x-content.content-card icon="hotel-icon">
<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-[#21242e] p-4 h-[90px] overflow-hidden flex items-center shadow-sm">
<a href="{{ route('profile.show', $comment->user) }}"
class="absolute top-2 left-1 drop-shadow">
<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
@endif
</div>
</x-app-layout>
+28
View File
@@ -0,0 +1,28 @@
<x-app-layout>
@push('title', __('Articles'))
<div class="col-span-12">
<x-page-header>
<x-slot:icon>
<img src="{{ asset('/assets/images/dusk/news_icon.png') }}" alt="">
</x-slot:icon>
News
</x-page-header>
<div class=" grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 mt-4">
@forelse($articles as $article)
<div class="h-[250px]">
<x-article-card :article="$article" />
</div>
@empty
<x-filler-article-card />
@endforelse
</div>
<div class="mt-4">
{{ $articles->links() }}
</div>
</div>
</x-app-layout>
@@ -0,0 +1,76 @@
<x-modals.modal-wrapper>
<div class="select-none"
x-data='reactions(@json($myReactions), @json($articleReactions), "{{ route('article.toggle-reaction', $article->slug) }}")'>
<div class="mt-4 flex w-full flex-wrap gap-2 rounded-lg p-2 bg-gray-900">
<div x-show="isAuthenticated"
class="px-2 hover:scale-110 transition-all font-semibold h-8 flex items-center justify-center border-2 text-xs border-yellow-400 cursor-pointer bg-[#eeb425] text-white rounded-lg"
x-on:click="open = true">
{{ __('Add') }}
</div>
<template x-for="articleReaction in articleReactions">
<div>
<div class="flex h-8 w-12 items-center justify-center gap-2 rounded-lg border-2 text-sm font-bold border-gray-800 hover:bg-gray-700 cursor-pointer"
:class="{
bg-gray-800 border-gray-700': userHasReaction(
articleReaction),
'cursor-pointer hover:scale-110 transition-all hover:bg-gray-700': isAuthenticated
}"
@click="toggleReaction(articleReaction.name)" :data-popover-target="articleReaction.id">
<img :src="'/assets/images/icons/reactions/' + articleReaction.name + '.png'"
:alt="articleReaction.name">
<span x-text="articleReaction.count"></span>
</div>
<div data-popover :id="articleReaction.id" role="tooltip"
class="invisible absolute z-10 inline-block w-64 rounded-lg border text-sm font-light opacity-0 shadow-xs transition-opacity duration-300 border-gray-600 bg-gray-800 text-gray-400">
<div
class="rounded-t-lg border-b px-3 py-2 border-gray-600 bg-gray-700">
<div
class="flex w-full items-center justify-center font-semibold text-white">
{{ __('Reactions with') }} <img
:src="'/assets/images/icons/reactions/' + articleReaction.name + '.png'"
class="ml-1" :alt="articleReaction.name">
</div>
</div>
<div class="overflow-y-auto px-3 py-2" style="max-height: 200px">
<template x-for="user in articleReaction.users">
<p class="w-full text-center" x-text="user"></p>
</template>
</div>
<div data-popper-arrow></div>
</div>
</div>
</template>
</div>
<div x-show="isAuthenticated">
<x-modals.regular-modal>
<x-slot name="title">
<h2 class="text-2xl">
{{ __('Insert Reaction') }}
</h2>
</x-slot>
<div class="flex w-full flex-wrap justify-center gap-3 p-2">
<template x-for="defaultReaction in allReactions">
<div class="cursor-pointer rounded-lg border-2 px-3 py-2 hover:bg-gray-700 hover:border-g border-gray-800"
x-show="canAddReactionFromModal(defaultReaction)" @click="toggleReaction(defaultReaction)">
<img :src="'/assets/images/icons/reactions/' + defaultReaction + '.png'"
:alt="defaultReaction">
</div>
</template>
</div>
</x-modals.regular-modal>
</div>
</div>
</x-modals.modal-wrapper>
@push('scripts')
<script>
window.App = {
defaultReactions: @json(config('habbo.reactions')),
isAuthenticated: @json(auth()->check())
}
</script>
@endpush
+19
View File
@@ -0,0 +1,19 @@
<x-app-layout>
@push('title', __('Photos'))
<div class="col-span-12 space-y-6">
<x-page-header>
<x-slot:icon>
<img src="{{ asset('/assets/images/dusk/camera_icon.png') }}" alt="">
</x-slot:icon>
Photos
</x-page-header>
<div class="col-span-12 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
@foreach ($photos as $photo)
<x-photo :photo="$photo" />
@endforeach
</div>
</div>
</x-app-layout>
@@ -0,0 +1,63 @@
<x-app-layout>
@push('title', __('Staff'))
<div class="col-span-12 lg:col-span-9 lg:w-[96%]">
<x-content.staff-content-section :badge="$position->permission->badge" :color="$position->permission->staff_color">
<x-slot:title>
{{ __('You are applying for :position', ['position' => $position->permission->rank_name]) }}
</x-slot:title>
<x-slot:under-title>
{{ __('Please fill out the fields below to apply for :position', ['position' => $position->permission->rank_name]) }}
</x-slot:under-title>
<form class="flex flex-col gap-y-3" action="{{ route('staff-applications.store', $position) }}" method="POST">
@csrf
<div>
<x-form.label for="username" disabled>
{{ __('Username') }}
</x-form.label>
<x-form.input classes="bg-red-200" name="username" value="{{ auth()->user()->username }}"
:readonly="true" />
</div>
<div>
<x-form.label for="username" disabled>
{{ __('About you') }}
</x-form.label>
<textarea name="content"
class="focus:ring-0 border-4 border-gray-200 rounded dark:bg-gray-800 dark:border-gray-700 dark:text-gray-200 focus:border-[#eeb425] w-full min-h-[180px]"></textarea>
</div>
@if (setting('google_recaptcha_enabled'))
<div class="g-recaptcha" data-sitekey="{{ config('habbo.site.recaptcha_site_key') }}"></div>
@endif
<x-form.primary-button>
{{ __('Apply for :position', ['position' => $position->permission->rank_name]) }}
</x-form.primary-button>
</form>
</x-content.staff-content-section>
</div>
<div class="col-span-12 lg:col-span-3 lg:w-[110%] space-y-4 lg:-ml-[32px]">
<x-content.content-card icon="hotel-icon" classes="border dark:border-gray-900">
<x-slot:title>
{{ __('Applying for :position', ['position' => $position->permission->rank_name]) }}
</x-slot:title>
<x-slot:under-title>
{{ __('Read before applying') }}
</x-slot:under-title>
<div class="px-2 text-sm space-y-4 dark:text-gray-200">
<p>
{{ __('Please field out all the fields to apply for :position. Remember when applying for a position here at :hotel you must be fully transparent and honest. If found out the information provided is false or incorrect you might risk losing your position if hired.', ['position' => $position->permission->rank_name, 'hotel' => setting('hotel_name')]) }}
</p>
</div>
</x-content.content-card>
</div>
</x-app-layout>
@@ -0,0 +1,73 @@
<x-app-layout>
@push('title', __('Staff'))
<div class="col-span-12 lg:col-span-9 lg:w-[96%]">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-2">
@forelse($positions as $position)
@continue(!$position->permission) {{-- skip if the rank relation is missing --}}
<x-content.staff-content-section :badge="$position->permission->badge" :color="$position->permission->staff_color">
<x-slot:title>
{{ $position->permission->rank_name }}
</x-slot:title>
<x-slot:under-title>
{{ $position->permission->job_description }}
</x-slot:under-title>
<div class="text-center dark:text-gray-400">
<div class="mb-4 text-sm">
{!! $position->description !!}
</div>
<div class="mb-4 text-sm font-semibold">
{{ __('Application Deadline :date', [
'date' => $position->apply_to ? $position->apply_to->format('F j, Y, g:i A') : __('No deadline set')
]) }}
</div>
</div>
<div class="flex justify-between">
@if (auth()->check() && auth()->user()->hasAppliedForPosition($position->permission->id))
<x-form.danger-button>
{{ __('You have already applied for :position', ['position' => $position->permission->rank_name]) }}
</x-form.danger-button>
@else
<a href="{{ route('staff-applications.show', $position) }}" class="w-full">
<x-form.secondary-button>
{{ __('Apply for :position', ['position' => $position->permission->rank_name]) }}
</x-form.secondary-button>
</a>
@endif
</div>
</x-content.staff-content-section>
@empty
<x-content.content-card icon="lighthouse-icon" classes="border dark:border-gray-900 col-span-full">
<x-slot:title>
{{ __('No positions open') }}
</x-slot:title>
<x-slot:under-title>
{{ __('There is currently no positions open') }}
</x-slot:under-title>
<div class="px-2 text-sm space-y-4 dark:text-gray-200">
<p>
{{ __('Please come back at a later time to check if we have any positions open by then! Thank you for your interest.', ['hotel' => setting('hotel_name')]) }}
</p>
</div>
</x-content.content-card>
@endforelse
</div>
</div>
<div class="col-span-12 lg:col-span-3 lg:w-[110%] space-y-4 lg:-ml-[32px]">
<x-content.content-card icon="chat-icon" classes="border dark:border-gray-900">
<x-slot:title>
{{ __('Apply for :hotel staff', ['hotel' => setting('hotel_name')]) }}
</x-slot:title>
<x-slot:under-title>
{{ __('Select position to get started', ['hotel' => setting('hotel_name')]) }}
</x-slot:under-title>
<div class="px-2 text-sm space-y-4 dark:text-gray-200">
<p>
{{ __('Here at :hotel we open up for staff applications every now and then. Sometimes you will find this page empty other times it might be filled with positions, if you ever come across a position you feel you would fit perfectly into, then do not hesitate to apply for it.', ['hotel' => setting('hotel_name')]) }}
</p>
</div>
</x-content.content-card>
</div>
</x-app-layout>
@@ -0,0 +1,56 @@
<x-app-layout>
@push('title', __('Staff'))
<div class="col-span-12 lg:col-span-9 lg:w-[96%]">
<x-content.staff-content-section :badge="$position->permission->badge" :color="$position->permission->staff_color">
<x-slot:title>
{{ __('You are applying for :position', ['position' => $position->permission->rank_name]) }}
</x-slot:title>
<x-slot:under-title>
{{ __('Please fill out the fields below to apply for :position', ['position' => $position->permission->rank_name]) }}
</x-slot:under-title>
<form class="flex flex-col gap-y-3" action="{{ route('staff-applications.store', $position) }}" method="POST">
@csrf
<div>
<x-form.label for="username" disabled>{{ __('Username') }}</x-form.label>
<x-form.input name="username" value="{{ auth()->user()->username }}" :readonly="true" />
</div>
<div>
<x-form.label for="content" disabled>{{ __('About you') }}</x-form.label>
<textarea name="content" class="focus:ring-0 border-4 border-gray-200 rounded dark:bg-gray-800 dark:border-gray-700 dark:text-gray-200 focus:border-[#eeb425] w-full min-h-[180px]">{{ old('content') }}</textarea>
@error('content')
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
@if (setting('google_recaptcha_enabled'))
<div class="g-recaptcha" data-sitekey="{{ config('habbo.site.recaptcha_site_key') }}"></div>
@endif
@if (setting('cloudflare_turnstile_enabled'))
<x-turnstile />
@endif
<x-form.primary-button>
{{ __('Apply for :position', ['position' => $position->permission->rank_name]) }}
</x-form.primary-button>
</form>
</x-content.staff-content-section>
</div>
<div class="col-span-12 lg:col-span-3 lg:w-[110%] space-y-4 lg:-ml-[32px]">
<x-content.content-card icon="hotel-icon" classes="border dark:border-gray-900">
<x-slot:title>{{ __('Applying for :position', ['position' => $position->permission->rank_name]) }}</x-slot:title>
<x-slot:under-title>{{ __('Read before applying') }}</x-slot:under-title>
<div class="px-2 text-sm space-y-4 dark:text-gray-200">
<p>
{{ __('Please fill out all the fields to apply for :position. Be honest and transparent. Providing incorrect information may lead to removal if hired.', ['position' => $position->permission->rank_name]) }}
</p>
</div>
</x-content.content-card>
</div>
</x-app-layout>
+86
View File
@@ -0,0 +1,86 @@
<x-app-layout>
@push('title', __('Staff'))
<div class="col-span-12 lg:col-span-9 lg:w-[96%]">
<div class="flex flex-col gap-y-8">
@foreach ($employees as $employee)
<div class="bg-[#1a1d26] rounded-xl border border-gray-800 overflow-hidden">
<div class="bg-gradient-to-r from-[#252a36] to-[#1e222d] px-6 py-4 border-b border-gray-800">
<div class="flex items-center gap-3">
@if($employee->badge)
<img src="{{ asset(setting('badges_path') . $employee->badge . '.gif') }}"
alt="{{ $employee->rank_name }}"
class="w-10 h-10">
@endif
<div>
<h2 class="text-xl font-bold text-white">{{ $employee->rank_name }}</h2>
@if($employee->job_description)
<p class="text-sm text-gray-400">{{ $employee->job_description }}</p>
@endif
</div>
</div>
</div>
<div class="p-6">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
@foreach ($employee->users as $staff)
<x-community.staff-card :user="$staff" />
@endforeach
</div>
@if (count($employee->users) === 0)
<div class="text-center py-8 text-gray-500">
{{ __('We currently have no staff in this position') }}
</div>
@endif
</div>
</div>
@endforeach
</div>
</div>
<div class="col-span-12 lg:col-span-3 lg:w-[110%] space-y-4 lg:-ml-[32px]">
<x-content.content-card icon="chat-icon" classes="border border-gray-800">
<x-slot:title>
{{ __(':hotel staff', ['hotel' => setting('hotel_name')]) }}
</x-slot:title>
<x-slot:under-title>
{{ __('About the :hotel staff', ['hotel' => setting('hotel_name')]) }}
</x-slot:under-title>
<div class="px-2 text-sm space-y-4 text-gray-300">
<p>
{{ __('The :hotel staff team is one big happy family, each staff member has a different role and duties to fulfill.', ['hotel' => setting('hotel_name')]) }}
</p>
<p>
{{ __('Most of our team usually consists of players that have been around :hotel for quite a while, but this does not mean we only recruit old & known players, we recruit those who shine out to us!', ['hotel' => setting('hotel_name')]) }}
</p>
</div>
</x-content.content-card>
<x-content.content-card icon="chat-icon" classes="border border-gray-800">
<x-slot:title>
{{ __('Apply for staff') }}
</x-slot:title>
<x-slot:under-title>
{{ __('How to join the staff team', ['hotel' => setting('hotel_name')]) }}
</x-slot:under-title>
<div class="px-2 text-sm space-y-4 text-gray-300">
<p>
{{ __('Every now and then staff applications may open up. Once they do we always make sure to post a news article explaining the process - So make sure you keep an eye out for those in you are interested in joining the :hotel staff team.', ['hotel' => setting('hotel_name')]) }}
</p>
<p>
{!! __(
'You can occasionally also look at the :startTag Staff application page :endTag which will show you all of our current open positions.',
['startTag' => '<a href="/community/staff-applications" class="underline">', 'endTag' => '</a>'],
) !!}
</p>
</div>
</x-content.content-card>
</div>
</x-app-layout>
@@ -0,0 +1,105 @@
<x-app-layout>
@push('title', __('Staff'))
<div class="col-span-12 lg:col-span-9 lg:w-[96%]">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-2">
@forelse($positions as $position)
@continue(!$position->team)
@php
$status = auth()->check()
? ($userAppStatuses[$position->team->id] ?? null) // 'pending'|'approved'|'rejected'|null
: null;
$statusLabel = $status ? ucfirst($status) : null;
$statusColorClasses = match ($status) {
'approved' => 'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300 border-green-200 dark:border-green-800',
'pending' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-300 border-yellow-200 dark:border-yellow-800',
'rejected' => 'bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300 border-red-200 dark:border-red-800',
default => '',
};
@endphp
<x-content.staff-content-section :badge="$position->team->badge" :color="$position->team->staff_color">
<x-slot:title>
<span class="inline-flex items-center gap-2">
{{ $position->team->rank_name }}
@if ($statusLabel)
<span class="inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium {{ $statusColorClasses }}">
{{ $statusLabel }}
</span>
@endif
</span>
</x-slot:title>
<x-slot:under-title>
{{ $position->team?->job_description }}
</x-slot:under-title>
<div class="text-center dark:text-gray-400">
<div class="mb-4 text-sm">
{!! $position->description !!}
</div>
<div class="mb-4 text-sm font-semibold">
{{ __('Application Deadline :date', ['date' => $position->apply_to ? $position->apply_to->format('F j, Y, g:i A') : __('No deadline set')]) }}
</div>
</div>
<div class="flex justify-between">
@auth
@if ($status)
{{-- Already applied: show a disabled button indicating status --}}
<x-form.secondary-button class="w-full justify-center" disabled>
@switch($status)
@case('pending')
{{ __('Your application is pending') }}
@break
@case('approved')
{{ __('You have been approved') }}
@break
@case('rejected')
{{ __('Your application was rejected') }}
@break
@default
{{ __('Application submitted') }}
@endswitch
</x-form.secondary-button>
@else
{{-- No application yet: show Apply --}}
<a href="{{ route('team-applications.show', $position) }}" class="w-full">
<x-form.primary-button class="w-full justify-center">
{{ __('Apply for :position', ['position' => $position->team->rank_name]) }}
</x-form.primary-button>
</a>
@endif
@else
<a href="{{ route('login') }}" class="w-full">
<x-form.secondary-button class="w-full justify-center">
{{ __('Login to apply') }}
</x-form.secondary-button>
</a>
@endauth
</div>
</x-content.staff-content-section>
@empty
<x-content.content-card icon="lighthouse-icon" classes="border dark:border-gray-900 col-span-full">
<x-slot:title>{{ __('No team positions open') }}</x-slot:title>
<x-slot:under-title>{{ __('There are currently no open team positions') }}</x-slot:under-title>
<div class="px-2 text-sm space-y-4 dark:text-gray-200">
<p>{{ __('Please come back later to check for new openings. Thank you!') }}</p>
</div>
</x-content.content-card>
@endforelse
</div>
</div>
<div class="col-span-12 lg:col-span-3 lg:w-[110%] space-y-4 lg:-ml-[32px]">
<x-content.content-card icon="chat-icon" classes="border dark:border-gray-900">
<x-slot:title>{{ __('Apply for :hotel Team', ['hotel' => setting('hotel_name')]) }}</x-slot:title>
<x-slot:under-title>{{ __('Select a team to get started') }}</x-slot:under-title>
<div class="px-2 text-sm space-y-4 dark:text-gray-200">
<p>{{ __('We open team applications periodically. If you see a team you fit, do not hesitate to apply!') }}</p>
</div>
</x-content.content-card>
</div>
</x-app-layout>
+56
View File
@@ -0,0 +1,56 @@
<x-app-layout>
@push('title', __('Staff'))
<div class="col-span-12 lg:col-span-9 lg:w-[96%]">
<x-content.staff-content-section :badge="$position->team->badge" :color="$position->team->staff_color">
<x-slot:title>
{{ __('You are applying for :position', ['position' => $position->team->rank_name]) }}
</x-slot:title>
<x-slot:under-title>
{{ __('Please fill out the fields below to apply for :position', ['position' => $position->team->rank_name]) }}
</x-slot:under-title>
<form class="flex flex-col gap-y-3" action="{{ route('team-applications.store', $position) }}" method="POST">
@csrf
<div>
<x-form.label for="username" disabled>{{ __('Username') }}</x-form.label>
<x-form.input classes="bg-gray-100" name="username" value="{{ auth()->user()->username }}" :readonly="true" />
</div>
<div>
<x-form.label for="content" disabled>{{ __('About you') }}</x-form.label>
<textarea name="content" class="focus:ring-0 border-4 border-gray-200 rounded dark:bg-gray-800 dark:border-gray-700 dark:text-gray-200 focus:border-[#eeb425] w-full min-h-[180px]">{{ old('content') }}</textarea>
@error('content')
<div class="text-red-600 text-sm mt-1">{{ $message }}</div>
@enderror
</div>
@if (setting('google_recaptcha_enabled'))
<div class="g-recaptcha" data-sitekey="{{ config('habbo.site.recaptcha_site_key') }}"></div>
@endif
@if (setting('cloudflare_turnstile_enabled'))
<x-turnstile />
@endif
<x-form.primary-button>
{{ __('Apply for :position', ['position' => $position->team->rank_name]) }}
</x-form.primary-button>
</form>
</x-content.staff-content-section>
</div>
<div class="col-span-12 lg:col-span-3 lg:w-[110%] space-y-4 lg:-ml-[32px]">
<x-content.content-card icon="hotel-icon" classes="border dark:border-gray-900">
<x-slot:title>{{ __('Applying for :position', ['position' => $position->team->rank_name]) }}</x-slot:title>
<x-slot:under-title>{{ __('Read before applying') }}</x-slot:under-title>
<div class="px-2 text-sm space-y-4 dark:text-gray-200">
<p>
{{ __('Please fill out all the fields to apply for :position. Be honest and transparent. Providing incorrect information may lead to removal if hired.', ['position' => $position->team->rank_name]) }}
</p>
</div>
</x-content.content-card>
</div>
</x-app-layout>
+100
View File
@@ -0,0 +1,100 @@
<x-app-layout>
@push('title', __('Staff'))
<div
x-data="{ q: '', hideEmpty: false }"
class="col-span-12 space-y-4"
>
{{-- Toolbar --}}
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<h1 class="text-2xl font-semibold tracking-tight">{{ __('Teams') }}</h1>
<div class="flex w-full flex-col items-stretch gap-3 sm:w-auto sm:flex-row sm:items-center">
<div class="relative sm:w-80">
<input
x-model="q"
type="text"
placeholder="{{ __('Search teams…') }}"
class="w-full rounded-xl border border-gray-200 px-4 py-2.5 pr-10 text-sm focus:border-[#eeb425] focus:outline-none focus:ring-0 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100"
/>
<svg class="pointer-events-none absolute right-3 top-1/2 h-5 w-5 -translate-y-1/2 opacity-60"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="m21 21-4.35-4.35M10 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16z" />
</svg>
</div>
<label class="inline-flex items-center gap-2 text-sm">
<input type="checkbox" x-model="hideEmpty"
class="rounded border-gray-300 text-[#eeb425] focus:ring-[#eeb425]" />
<span class="select-none">{{ __('Hide empty teams') }}</span>
</label>
</div>
</div>
{{-- Teams --}}
<div class="flex flex-col gap-y-4">
@forelse ($employees as $employee)
@php
/** @var \Illuminate\Support\Collection $users */
$users = $employee->users ?? collect();
$memberCount = $users->count();
$searchText = trim(($employee->rank_name ?? '') . ' ' . ($employee->job_description ?? ''));
@endphp
<section
x-data="{
name: @js($searchText),
hasMembers: {{ $memberCount > 0 ? 'true' : 'false' }}
}"
x-show="(name.toLowerCase().includes(q.toLowerCase())) && (!hideEmpty || hasMembers)"
x-cloak
>
<x-content.staff-content-section
:badge="$employee->badge"
:color="$employee->staff_color"
class="overflow-hidden rounded-2xl border border-gray-100 shadow-sm dark:border-gray-800"
>
{{-- Header --}}
<div class="flex items-start justify-between">
<div>
<x-slot:title>
{{ $employee->rank_name }}
</x-slot:title>
<x-slot:under-title>
{{ $employee?->job_description }}
</x-slot:under-title>
</div>
{{-- Member count chip --}}
<span class="ml-4 shrink-0 rounded-full border border-gray-200 px-3 py-1 text-xs font-medium dark:border-gray-700">
{{ $memberCount }} {{ \Illuminate\Support\Str::plural(__('member'), $memberCount) }}
</span>
</div>
{{-- Members grid --}}
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
@forelse ($users as $staff)
<x-community.staff-card :user="$staff" />
@empty
<div class="col-span-full">
<div class="rounded-xl border border-dashed border-gray-200 p-6 text-center text-sm text-gray-500 dark:border-gray-700 dark:text-gray-400">
{{ __('We currently have no staff in this team') }}
</div>
</div>
@endforelse
</div>
</x-content.staff-content-section>
</section>
@empty
<x-content.content-card icon="lighthouse-icon" classes="border dark:border-gray-900">
<x-slot:title>{{ __('No teams found') }}</x-slot:title>
<x-slot:under-title>{{ __('Please check back later.') }}</x-slot:under-title>
<div class="px-2 text-sm space-y-4 dark:text-gray-200">
<p>{{ __('There are no teams to display right now.') }}</p>
</div>
</x-content.content-card>
@endforelse
</div>
</div>
</x-app-layout>
@@ -0,0 +1,24 @@
@props(['article'])
<div class="swiper-slide relative article-image rounded-lg overflow-hidden" style="background-image: url({{ asset('storage/' . $article->image) }})">
<div class="absolute h-[90px] w-full left-0 bottom-0 bg-[#171a23]/95 text-white py-2 px-4">
<h2 class="text-xl font-bold truncate">
{{ $article->title }}
</h2>
<div class="flex justify-between items-center mt-1">
<div class="py-1 px-2 rounded-md bg-black/60 text-sm mt-2 flex gap-1 items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
</svg>
{{ $article->user->username }}
</div>
<a href="{{ route('article.show', $article->slug) }}" class="text-sm mt-1 read-more-link hover:underline">
Read more
</a>
</div>
</div>
</div>
@@ -0,0 +1,50 @@
<x-slot name="title">
<h2 class="text-2xl font-semibold">{{ __('Hello!') }}</h2>
<p class="dark:text-gray-400">
{{ __('There is currently :online users online', ['online' => cache()->remember('online_user_count', 30, fn() => DB::table('users')->where('online', '1')->count())]) }}
</p>
</x-slot>
<form class="flex flex-col gap-y-3" action="{{ route('login') }}" method="POST">
@csrf
<div>
<x-form.label for="username">
{{ __('Username') }}
</x-form.label>
<x-form.input error-bag="login" name="username" value="{{ old('username') }}" placeholder="{{ __('Username') }}"
:autofocus="true" />
</div>
<div>
<x-form.label for="password">
{{ __('Password') }}
</x-form.label>
<x-form.input error-bag="login" name="password" placeholder="{{ __('Password') }}" type="password" />
</div>
@if (setting('google_recaptcha_enabled'))
<div class="g-recaptcha" data-sitekey="{{ config('habbo.site.recaptcha_site_key') }}"></div>
@endif
@if (setting('cloudflare_turnstile_enabled'))
<x-turnstile />
@endif
<x-form.primary-button>
{{ __('Login') }}
</x-form.primary-button>
<div class="text-center text-sm font-semibold text-gray-700 dark:text-gray-400">
<a href="{{ route('forgot.password.get') }}" class="hover:underline" x-on:click="open = false">
{{ __('Did you forget your password?') }}
</a>
</div>
<div class="text-center text-sm font-semibold text-gray-700 dark:text-gray-400">
<a href="{{ route('register') }}" class="hover:underline" x-on:click="open = false">
{{ __('Dont have an account? Join now!') }}
</a>
</div>
</form>
@@ -0,0 +1,6 @@
@props(['classes' => ''])
<div
class="text-white rounded bg-[#eeb425] hover:bg-[#e3aa1e] border-[2px] border-[#cf9d15] transition ease-in-out text-center rounded py-1 px-2 text-sm text-white cursor-pointer {{ $classes }}">
{{ $slot }}
</div>
@@ -0,0 +1,34 @@
@props(['user'])
<div class="relative group bg-[#1a1d26] rounded-xl overflow-hidden border border-gray-800 hover:border-gray-600 transition-all duration-300 hover:shadow-lg hover:shadow-black/20">
<div class="absolute inset-0 bg-gradient-to-br from-[#1e222d] to-[#15171f]"></div>
<div class="relative p-5 flex items-center gap-4">
<div class="relative">
<div class="w-20 h-20 rounded-full overflow-hidden border-2 {{ $user->online ? 'border-green-500' : 'border-gray-600' }} shadow-lg">
<img style="image-rendering: pixelated;"
src="{{ setting('avatar_imager') }}{{ $user->look }}&direction=2&head_direction=3&gesture=sml&action=wav&size=l"
alt="{{ $user->username }}"
class="w-full h-full object-cover">
</div>
<div class="absolute -bottom-1 -right-1 w-5 h-5 rounded-full border-2 border-[#1a1d26] {{ $user->online ? 'bg-green-500' : 'bg-gray-500' }}"></div>
</div>
<div class="flex-1 min-w-0">
<a href="{{ route('profile.show', $user->username) }}" class="block">
<h3 class="text-lg font-bold text-white truncate hover:text-blue-400 transition-colors">
{{ $user->username }}
</h3>
</a>
<p class="text-sm text-gray-400 truncate mt-1">
{{ $user->motto ?: 'No motto set' }}
</p>
<div class="flex items-center gap-2 mt-2">
<span class="inline-flex items-center gap-1 text-xs {{ $user->online ? 'text-green-400' : 'text-gray-500' }}">
<span class="w-2 h-2 rounded-full {{ $user->online ? 'bg-green-400' : 'bg-gray-500' }}"></span>
{{ $user->online ? 'Online' : 'Offline' }}
</span>
</div>
</div>
</div>
</div>
@@ -0,0 +1,22 @@
@props(['icon' => '', 'classes' => ''])
<div class="w-full flex flex-col gap-y-4 rounded-lg overflow-hidden bg-[#2b303c] pb-4 shadow-sm text-gray-100 {{ $classes }}">
<div class="flex gap-x-2 bg-[#21242e] p-3">
@if (!empty($icon))
<div class="max-w-12.5 max-h-12.5 min-w-12.5 min-h-12.5 rounded-full relative flex items-center justify-center {{ $icon }}"></div>
@endif
<div class="flex flex-col justify-center text-sm">
<p class="font-semibold text-gray-100">{{ $title }}</p>
@if(isset($underTitle))
<p class="text-gray-300">{{ $underTitle }}</p>
@endif
</div>
</div>
<section class="h-full flex flex-col px-4">
{{ $slot }}
</section>
</div>
@@ -0,0 +1,19 @@
@props(['icon', 'classes' => ''])
<div class="w-full flex flex-col gap-y-4 p-3 rounded-lg overflow-hidden {{ $classes }}">
<div class="flex gap-x-2">
<div
class="max-w-[50px] max-h-[50px] min-w-[50px] min-h-[50px] rounded-full {{ $icon }} relative flex items-center justify-center">
</div>
<div class="flex flex-col">
<p class="font-semibold text-black dark:text-gray-200">{{ $title }}</p>
@if(isset($underTitle))
<p class="dark:text-gray-500">{{ $underTitle }}</p>
@endif
</div>
</div>
{{ $slot }}
</div>
@@ -0,0 +1,22 @@
@props(['iconUrl' => '', 'color' => '', 'classes' => ''])
<div class="w-full flex flex-col gap-y-4 rounded overflow-hidden bg-[#2b303c] pb-3 shadow-sm text-gray-100 {{ $classes }}">
<div class="flex gap-x-2 bg-[#21242e] p-3 text-gray-100">
@if (!empty($iconUrl))
<div style="background-image: url({{ $iconUrl }}); background-color: {{ $color }}; background-repeat: no-repeat; background-position: center;" class="max-w-[50px] max-h-[50px] min-w-[50px] min-h-[50px] rounded-full relative flex items-center justify-center "></div>
@endif
<div class="flex flex-col justify-center text-sm w-full">
<div class="w-full text-[16px]">{{ $title }}</div>
@if(isset($underTitle))
<p class="text-gray-300">{{ $underTitle }}</p>
@endif
</div>
</div>
<section class="h-full flex flex-col px-3">
{{ $slot }}
</section>
</div>
@@ -0,0 +1,22 @@
@props(['badge' => '', 'color' => '#327fa8'])
<div class="w-full flex flex-col gap-y-4 rounded-lg overflow-hidden bg-[#2b303c] pb-4 shadow-sm text-gray-100">
<div class="flex gap-x-2 bg-[#21242e] p-3">
<div class="max-w-12.5 max-h-12.5 min-w-12.5 min-h-12.5 rounded-full relative flex items-center justify-center"
style="background-color: {{ $color }}">
<img src="{{ asset(sprintf('%s/%s.gif', setting('badges_path'), $badge)) }}" alt="">
</div>
<div class="flex flex-col justify-center text-sm">
<p class="font-semibold text-gray-300">{{ $title }}</p>
@if(isset($underTitle))
<p class="text-gray-500">{{ $underTitle }}</p>
@endif
</div>
</div>
<section class="px-3">
{{ $slot }}
</section>
</div>
+13
View File
@@ -0,0 +1,13 @@
@props(['icon'])
<div class="gap-x-3 flex">
<div class="h-[25px] w-[25px] rounded-full {{ $icon }} outline-offset-[3px]"></div>
<div class="dark:text-gray-400 flex gap-x-2">
<span class="font-semibold dark:text-white">
{{ $currency }}
</span>
{{ $slot }}
</div>
</div>
@@ -0,0 +1,21 @@
<div class="h-[210px] dark:bg-gray-900 rounded w-full bg-white shadow-sm relative overflow-hidden transition ease-in-out duration-200">
<div style="background: url('https://i.imgur.com/uGLDOUu.png');" class="article-image">
</div>
<div class="mt-4 px-4">
<p class="font-semibold text-lg truncate dark:text-gray-200">
{{ __('No published articles') }}
</p>
<div class="flex items-center gap-x-2">
<div
class="mt-3 flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<img src="{{ setting('avatar_imager') }}&headonly=1" alt="">
</div>
<p class="mt-4 font-semibold dark:text-gray-400">
{{ setting('hotel_name') }}
</p>
</div>
</div>
</div>
+123
View File
@@ -0,0 +1,123 @@
<footer class="w-full h-14 flex items-center justify-center bg-gray-900 text-gray-500 font-bold cursor-pointer transition duration-200 ease-in-out hover:text-gray-400" onclick="showFooter()">
&copy {{ date('Y') }} {{ setting('hotel_name') }} is a not for profit educational project & is in no way affiliated with Sulake Corporation Oy.
</footer>
<style>
.swal2-modal {
background-color: #21242e;
color: #fff;
}
.swal2-html-container {
max-height: 300px;
overflow-y: scroll;
}
</style>
<script>
function showFooter() {
const creditList = [
{
contributor: 'Kasja',
contributions: 'Designing Dusk theme, helping with general design, Fonts for the logo generator, ideas & GFX'
},
{
contributor: 'Nicollas',
contributions: 'Dark mode, Turbolinks, Performance improvements, Article reactions, User sessions, Layout improvements & PT-BR translations'
},
{
contributor: 'Dominic/Mikkel',
contributions: 'Performance improvements & User sessions'
},
{
contributor: 'EntenKoeniq',
contributions: 'Automatic language registration, rooms page, profile page fixes & Paypal shop contributions'
},
{
contributor: 'Kani',
contributions: 'Rcon System & Findretros API'
},
{
contributor: 'Beny',
contributions: 'Findretros API fixes & CF Fixes'
},
{
contributor: 'Live',
contributions: 'French translations, bugfixes & tweaks'
},
{
contributor: 'MisterDeen',
contributions: 'Custom Discord widget, bugfixes & tweaks'
},
{
contributor: 'Assholic',
contributions: 'Fonts for the logo generator'
},
{
contributor: 'DamienJolly',
contributions: 'Bugfixes'
},
{
contributor: 'Danbo',
contributions: 'Bugfixes'
},
{
contributor: 'Diddy/Josh',
contributions: 'Code readability improvements'
},
{
contributor: 'Oliver',
contributions: 'Finnish translations'
},
{
contributor: 'Damue & EntenKoeniq',
contributions: 'German translations'
},
{
contributor: 'Talion',
contributions: 'Turkish translations'
},
{
contributor: 'CentralCee, Rille & Tuborgs',
contributions: 'Swedish translations'
},
{
contributor: 'Yannick',
contributions: 'Netherland translations'
},
{
contributor: 'Gedomi',
contributions: 'Spanish translations'
},
{
contributor: 'Lorenzune',
contributions: 'Italian translations'
},
{
contributor: 'Twana',
contributions: 'Norwegian translations'
},
{
contributor: 'Plow',
contributions: 'French translations'
},
];
const formattedCredits = creditList.map(credit =>
`<strong>${credit.contributor}</strong> ${credit.contributions} <br/>`
).join('');
const creator =
'<a class="text-blue-400 underline" href="https://devbest.com/threads/atom-cms-a-multi-theme-cms.93034/" target="_blank">Object</a>';
const content =
'{{ __('Thank you for playing :hotel. We have put a lot of effort into making the hotel what it is, and we truly appreciate you being here', ['hotel' => setting('hotel_name')]) }}' + '❤️';
const drivenBy = '{{ __(':hotel is driven by Atom CMS made by:', ['hotel' => setting('hotel_name')]) }}';
Swal.fire(
'<span class="text-[26px]">{{ setting('hotel_name') }}</span>',
`<span class="text-sm">${content}<br/><br/>${drivenBy} ${creator}<br/><br/><span class="flex flex-col space-y-2">{{ __('Credits:') }}<br/>${formattedCredits}</span></span>`,
'question'
);
}
</script>
@@ -0,0 +1,6 @@
@props(['classes' => '', 'type' => 'submit'])
<button type="{{ $type }}"
class="w-full rounded bg-[var(--button-danger-color)] hover:bg-[var(--button-danger-hover-color)] text-[var(--button-danger-text-color)] border-2 border-[var(--button-danger-border-color)] transition ease-in-out duration-150 font-semibold px-6 py-2 {{ $classes }}">
{{ $slot }}
</button>
@@ -0,0 +1,14 @@
@props(['errorBag' => '', 'classes' => '', 'name', 'type' => 'text', 'value' => '', 'placeholder' => '', 'required' => true, 'autofocus' => false, 'readonly' => false])
<input
class="{{ $classes }} focus:ring-0 border-2 border-gray-700 rounded bg-[#21242e] focus:border-[#eeb425] w-full text-gray-200 @error($name, $errorBag) border-red-600 ring-red-500 @enderror"
id="{{ $name }}" type="{{ $type }}" name="{{ $name }}" value="{{ $value }}"
autocomplete="{{ $name }}" placeholder="{{ $placeholder }}" @if ($readonly) required @endif
@if ($autofocus) autofocus="{{ $name }}" @endif
@if ($readonly) readonly @endif>
@error($name, $errorBag)
<p class="mt-1 text-xs italic text-red-500">
{{ $message }}
</p>
@enderror
@@ -0,0 +1,11 @@
@props(['for', 'info' => ''])
<div class="mb-2">
<label class="block font-semibold text-gray-700 dark:text-gray-200" for="{{ $for }}">
{{ $slot }}
</label>
<p class="text-gray-500 dark:text-gray-400 text-[14px]">
{{ $info }}
</p>
</div>
@@ -0,0 +1,6 @@
@props(['classes' => '', 'type' => 'submit'])
<button type="{{ $type }}"
class="w-full rounded bg-transparent hover:bg-[var(--button-outline-hover-color)] text-[var(--button-outline-text-color)] border-2 border-[var(--button-outline-color)] transition ease-in-out duration-150 font-semibold px-6 py-2 {{ $classes }}">
{{ $slot }}
</button>
@@ -0,0 +1,6 @@
@props(['classes' => '', 'type' => 'submit'])
<button type="{{ $type }}"
class="{{ $classes }} w-full rounded bg-[#eeb425] text-white p-2 border-2 border-yellow-400 transition ease-in-out duration-200 hover:bg-[#d49f1c] font-semibold">
{{ $slot }}
</button>
@@ -0,0 +1,6 @@
@props(['classes' => '', 'type' => 'submit'])
<button type="{{ $type }}"
class="w-full rounded bg-[var(--button-secondary-color)] hover:bg-[var(--button-secondary-hover-color)] text-[var(--button-secondary-text-color)] border-2 border-[var(--button-secondary-border-color)] transition ease-in-out duration-150 font-semibold px-6 py-2 {{ $classes }}">
{{ $slot }}
</button>
@@ -0,0 +1,19 @@
@props(['name', 'content' => null])
<div class="mt-3">
<textarea name="content" id="editor">
{{ $content ?? ''}}
</textarea>
</div>
<script src="https://cdn.tiny.cloud/1/{{ setting('tinymce_api_key') }}/tinymce/7/tinymce.min.js" referrerpolicy="origin"></script>
<script>
tinymce.init({
selector: 'textarea#editor',
plugins: 'lists image',
toolbar: 'undo redo | blocks| bold italic | bullist numlist checklist | code | table'
});
</script>
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84M7.5 14.25 5.106 5.272M6 20.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm12.75 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 478 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/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="M19 9l-7 7-7-7" />
</svg>

After

Width:  |  Height:  |  Size: 218 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-6 w-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 487 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="h-5 w-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
</svg>

After

Width:  |  Height:  |  Size: 363 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-6 w-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 11.25v8.25a1.5 1.5 0 0 1-1.5 1.5H5.25a1.5 1.5 0 0 1-1.5-1.5v-8.25M12 4.875A2.625 2.625 0 1 0 9.375 7.5H12m0-2.625V7.5m0-2.625A2.625 2.625 0 1 1 14.625 7.5H12m0 0V21m-8.625-9.75h18c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125h-18c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z" />
</svg>

After

Width:  |  Height:  |  Size: 518 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5">
<path
d="M11.47 3.84a.75.75 0 011.06 0l8.69 8.69a.75.75 0 101.06-1.06l-8.689-8.69a2.25 2.25 0 00-3.182 0l-8.69 8.69a.75.75 0 001.061 1.06l8.69-8.69z" />
<path
d="M12 5.432l8.159 8.159c.03.03.06.058.091.086v6.198c0 1.035-.84 1.875-1.875 1.875H15a.75.75 0 01-.75-.75v-4.5a.75.75 0 00-.75-.75h-3a.75.75 0 00-.75.75V21a.75.75 0 01-.75.75H5.625a1.875 1.875 0 01-1.875-1.875v-6.198a2.29 2.29 0 00.091-.086L12 5.43z" />
</svg>

After

Width:  |  Height:  |  Size: 541 B

@@ -0,0 +1,4 @@
<svg {{ $attributes }} fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
</svg>

After

Width:  |  Height:  |  Size: 300 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="h-5 w-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
</svg>

After

Width:  |  Height:  |  Size: 377 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-4 w-4">
<path fill-rule="evenodd"
d="M7.5 6a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM3.751 20.105a8.25 8.25 0 0116.498 0 .75.75 0 01-.437.695A18.683 18.683 0 0112 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 01-.437-.695z"
clip-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 354 B

@@ -0,0 +1,52 @@
@props([
'title',
'icon',
'data',
'valueKey',
'valueType',
'relationship' => null,
'formatValue' => null,
])
<div class="flex flex-col gap-y-3">
<div class="flex gap-2 rounded-md py-2 px-4 bg-[#21242e]/90 text-gray-100 font-bold">
<div class="flex items-center">
<img src="{{ asset('/assets/images/icons/' . $icon) }}" alt="" class="w-4" style="image-rendering: pixelated;">
</div>
{{ $valueType }}
</div>
@foreach ($data as $index => $entry)
<div class="p-3 rounded-md flex items-center justify-between h-[60px] overflow-hidden bg-[#21242e]/90">
<div class="flex gap-2 items-center">
<div class="w-12 h-12 rounded-full overflow-hidden relative leaderboard-background">
<img class="absolute -top-2 left-0"
src="{{ setting('avatar_imager') }}{{ $relationship ? $entry->{$relationship}?->look : $entry->look }}&head_direction=3&gesture=sml"
alt=""/>
</div>
<div class="flex flex-col">
<p class="font-bold text-gray-100">
{{ $relationship ? $entry->{$relationship}?->username : $entry->username }}
</p>
<p class="text-gray-200 text-sm">
{{ $formatValue ? $formatValue($entry->{$valueKey}) : $entry->{$valueKey} }} {{ $valueType }}
</p>
</div>
</div>
<div @class([
'flex items-center justify-center',
'w-8 h-8 rounded-full bg-gray-300' => ($index + 1) > 3,
'leaderboard-position first' => ($index + 1) == 1,
'leaderboard-position second' => ($index + 1) == 2,
'leaderboard-position third' => ($index + 1) == 3,
])>
@if(($index + 1) > 3)
{{ ($index + 1) }}
@endif
</div>
</div>
@endforeach
</div>
@@ -0,0 +1,55 @@
<script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
var Toast = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 4000,
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener('mouseenter', Swal.stopTimer)
toast.addEventListener('mouseleave', Swal.resumeTimer)
}
})
</script>
@if (session()->has('message'))
<script>
Toast.fire({
icon: 'error',
title: '{{ session()->get('message') }}'
})
</script>
@endif
@if ($errors->any())
@foreach ($errors->all() as $error)
<script>
Toast.fire({
icon: 'error',
title: '{{ $error }}'
})
</script>
@endforeach
@endif
@if ($errors->login)
@foreach ($errors->login->all() as $error)
<script>
Toast.fire({
icon: 'error',
title: '{{ $error }}'
})
</script>
@endforeach
@endif
@if (session()->has('success'))
<script>
Toast.fire({
icon: 'success',
title: '{{ session()->get('success') }}'
})
</script>
@endif
@@ -0,0 +1,5 @@
@props(['classes' => ''])
<div x-data="{ open: false }" class="relative {{ $classes }}">
{{ $slot }}
</div>
@@ -0,0 +1,29 @@
<div x-show="open" style="display: none" x-on:keydown.escape.prevent.stop="open = false" role="dialog" aria-modal="true"
x-id="['modal-title']" :aria-labelledby="$id('modal-title')" class="fixed inset-0 z-50 overflow-y-auto">
<div x-show="open" x-transition x-on:click="open = false"
class="relative flex min-h-screen items-center justify-center overflow-hidden p-4">
{{-- Overlay --}}
<div x-show="open" x-transition.opacity class="fixed inset-0 bg-black/50"></div>
<div x-on:click.stop x-trap.noscroll.inert="open"
class="relative w-full max-w-xl rounded px-6 py-6 shadow-md bg-[#21242e] text-gray-200 lg:max-w-2xl lg:px-8">
<button type="button" x-on:click="open = false"
class="absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-800 dark:hover:text-white">
<svg aria-hidden="true" class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
<span class="sr-only">{{ __('Close modal') }}</span>
</button>
<div class="my-4 flex flex-col items-center" :id="$id('modal-title')">
{{ $title }}
</div>
{{ $slot }}
</div>
</div>
</div>
@@ -0,0 +1,6 @@
@props(['route' => '', 'classes' => '', 'target' => '_self'])
<a href="{{ $route }}" target="{{ $target }}" @class(['dropdown-item
', $classes])>
{{ $slot }}
</a>
@@ -0,0 +1,75 @@
@props(['icon', 'routeGroup' => '', 'classes' => '', 'childClasses' => 'min-w-[150px]', 'uppercase' => false, 'showChevron' => false, 'flexCol' => true])
<div
x-data="{
open: false,
toggle() {
if (this.open) {
return this.close()
}
this.$refs.button.focus()
this.open = true
},
close(focusAfter) {
if (! this.open) return
this.open = false
focusAfter && focusAfter.focus()
},
isTouchDevice: isTouchDevice()
}"
x-on:keydown.escape.prevent.stop="close($refs.button)"
x-on:focusin.window="! $refs.panel.contains($event.target) && close()"
x-id="['dropdown-button']"
x-on:mouseenter="isTouchDevice ? false : toggle()"
x-on:mouseleave="isTouchDevice ? false : toggle()"
x-on:click.stop="isTouchDevice ? toggle() : {}"
@class([
'relative h-auto font-semibold transition duration-300 ease-in-out z-5',
'active' => request()->is($routeGroup),
$classes,
])
>
<!-- Button -->
<button
x-ref="button"
:aria-expanded="open"
:aria-controls="$id('dropdown-button')"
type="button"
@class([
'flex gap-1 items-center transition ease-in-out hover:text-[#ac93da] dropdown-parent',
'flex-col' => $flexCol,
])
>
@if(isset($icon))
<img class="icon" src="{{ asset(sprintf('/assets/images/dusk/%s', $icon)) }}" alt="Missing icon">
@endif
{{ $slot }}
@if($showChevron)
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd"/>
</svg>
@endif
</button>
<!-- Panel -->
<div
x-ref="panel"
x-show="open"
x-transition.origin.top.left
x-on:click.outside="close($refs.button)"
:id="$id('dropdown-button')"
style="display: none;"
@class(['absolute left-0 rounded bg-[#ac93da] shadow-sm whitespace-nowrap overflow-hidden z-[100] flex flex-col py-2 items-center gap-2 dropdown-children mt-1', $childClasses])
>
{{ $children }}
</div>
</div>
@@ -0,0 +1,11 @@
<x-navigation.dropdown classes="!border-none" childClasses="w-[50px] -ml-2 flex items-center" :show-chevron="true" :flex-col="false">
{{ $slot }}
<x-slot:children>
@foreach (languages() as $lang)
<x-navigation.dropdown-child :route="route('language.select', $lang->country_code)" classes="transition ease-in-out duration-300 hover:scale-110 flex justify-center">
<img src="/assets/images/icons/flags/{{ $lang->country_code }}.png" alt="{{ $lang->country_code }}">
</x-navigation.dropdown-child>
@endforeach
</x-slot:children>
</x-navigation.dropdown>
@@ -0,0 +1,109 @@
<nav class="nav-header" x-data="{
open: false,
}" x-on:keydown.escape="open = false" x-effect="document.body.classList.toggle('menu-open', open)">
<div class="w-full min-h-[60px] text-white px-5 relative">
<button @click="open = !open" class="absolute right-5 top-5 z-50 p-2 rounded-lg transition-all duration-200 hover:bg-white/10" aria-controls="dusk-mobile-menu" :aria-expanded="open">
<span class="sr-only">{{ __('Open main menu') }}</span>
<svg x-show="!open" x-cloak xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-7 h-7">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
<svg x-show="open" x-cloak xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-7 h-7">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<div x-show="open" 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="flex flex-col text-white gap-x-14 p-4 space-y-3">
<x-navigation.dropdown route-group="help-center*" :show-chevron="true" :flex-col="false">
{{ __('Community') }}
<x-slot:children>
<x-navigation.dropdown-child :route="route('article.index')" @click="open = false">
{{ __('Articles') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('staff.index')" @click="open = false">
{{ __('Staff') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('teams.index')" @click="open = false">
{{ __('Teams') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('staff-applications.index')" @click="open = false">
{{ __('Staff applications') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('help-center.index')" @click="open = false">
{{ __('Help center') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('photos.index')" @click="open = false">
{{ __('Photos') }}
</x-navigation.dropdown-child>
</x-slot:children>
</x-navigation.dropdown>
<a href="{{ route('leaderboard.index') }}" class="transition ease-in-out hover:text-[#ac93da]" @click="open = false">
{{ __('Leaderboards') }}
</a>
<a href="{{ route('article.index') }}" class="transition ease-in-out hover:text-[#ac93da]" @click="open = false">
{{ __('News') }}
</a>
<a href="{{ route('welcome') }}" class="transition ease-in-out hover:text-[#ac93da]" @click="open = false">
{{ __('Events') }}
</a>
<a href="{{ route('shop.index') }}" class="transition ease-in-out hover:text-[#ac93da]" @click="open = false">
{{ __('Store') }}
</a>
<x-navigation.dropdown route-group="help-center*" :show-chevron="true" :flex-col="false">
{{ __('Home') }}
<x-slot:children>
@auth
<x-navigation.dropdown-child :route="route('profile.show', Auth::user()->username)" @click="open = false">
{{ __('My profile') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('settings.account.show')" @click="open = false">
{{ __('Account settings') }}
</x-navigation.dropdown-child>
<form action="{{ route('logout') }}" method="POST">
@csrf
<button type="submit" class="dropdown-item dark:text-gray-200 dark:hover:bg-gray-700 w-full text-left flex items-center gap-2 text-red-400 hover:text-red-300">
<svg xmlns="http://www.w3.org/2000/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="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
{{ __('Logout') }}
</button>
</form>
@endauth
@guest
<x-navigation.dropdown-child :route="route('login')" @click="open = false">
{{ __('Login') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('register')" @click="open = false">
{{ __('Register') }}
</x-navigation.dropdown-child>
@endguest
</x-slot:children>
</x-navigation.dropdown>
</div>
</div>
</nav>
@@ -0,0 +1,107 @@
<nav class="nav-header">
<div class="max-w-7xl w-full flex justify-between items-center h-[120px]">
<a href="/" class="transition duration-300 ease-in-out hover:scale-105">
<img src="{{ setting('cms_logo') }}" alt="">
</a>
<div class="flex text-white gap-x-14">
<x-navigation.dropdown icon="community_icon.png" route-group="help-center*" :uppercase="true">
{{ __('Community') }}
<x-slot:children>
<x-navigation.dropdown-child :route="route('staff.index')">
{{ __('Staff') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('teams.index')">
{{ __('Teams') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('team-applications.index')">
{{ __('Team applications') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('staff-applications.index')">
{{ __('Staff applications') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('values.index')">
{{ __('Rare values') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('help-center.index')">
{{ __('Help center') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('photos.index')">
{{ __('Photos') }}
</x-navigation.dropdown-child>
</x-slot:children>
</x-navigation.dropdown>
<a href="{{ route('leaderboard.index') }}" class="flex flex-col gap-1 items-center transition ease-in-out hover:text-[#ac93da]">
<img class="icon" src="{{ asset('/assets/images/dusk/leaderboard_icon.png') }}" alt="community icon">
Leaderboards
</a>
<a href="{{ route('article.index') }}" class="flex flex-col gap-1 items-center transition ease-in-out hover:text-[#ac93da]">
<img class="icon" src="{{ asset('/assets/images/dusk/news_icon.png') }}" alt="community icon">
News
</a>
{{--
<a href="#" class="flex flex-col gap-1 items-center transition ease-in-out hover:text-[#ac93da]">
<img class="icon" src="{{ asset('/assets/images/dusk/events_icon.png') }}" alt="community icon">
Events
</a>
--}}
<a href="{{ route('shop.index') }}" class="flex flex-col gap-1 items-center transition ease-in-out hover:text-[#ac93da]">
<img class="icon" src="{{ asset('/assets/images/dusk/store_icon.png') }}" alt="community icon">
Store
</a>
<x-navigation.dropdown icon="home_icon.png" route-group="user*" :uppercase="true">
{{ __('Home') }}
<x-slot:children>
@auth
<x-navigation.dropdown-child :route="route('profile.show', Auth::user()->username)">
{{ __('My profile') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('draw-badge')">
{{ __('Badge Drawer') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('settings.account.show')">
{{ __('Account settings') }}
</x-navigation.dropdown-child>
<form action="{{ route('logout') }}" method="POST">
@csrf
<button type="submit" class="dropdown-item text-red-400 hover:text-red-300 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/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="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
{{ __('Logout') }}
</button>
</form>
@endauth
@guest
<x-navigation.dropdown-child :route="route('login')">
{{ __('Login') }}
</x-navigation.dropdown-child>
<x-navigation.dropdown-child :route="route('register')">
{{ __('Register') }}
</x-navigation.dropdown-child>
@endguest
</x-slot:children>
</x-navigation.dropdown>
</div>
</div>
</nav>
@@ -0,0 +1,17 @@
@props(['icon' => '', 'classes' => '', 'subHeader'])
<div class="w-full bg-[#21242e] p-3 rounded-lg flex gap-2 items-center {{ $classes }}">
@if (!empty($icon))
{{ $icon }}
@endif
<div class="flex-col px-2">
<div class="text-lg font-bold text-gray-100 flex items-center">
{{ $slot }}
</div>
@if(isset($subHeader))
<p class="text-gray-500">{{ $subHeader }}</p>
@endif
</div>
</div>
+13
View File
@@ -0,0 +1,13 @@
@props(['photo'])
<a href="{{ $photo->url }}" data-fancybox="gallery" class="cursor-pointer relative transition duration-300 ease-in-out hover:scale-[102%]">
<div class="photo-overlay"></div>
<img class="h-[250px] w-full object-cover object-center rounded-md custom-shadow" src="{{ $photo->url }}" alt="">
<div class="absolute right-2 bottom-2 bg-black/70 p-2 rounded-md text-white flex gap-x-2 z-[5]">
<img class="self-center" src="{{ asset('/assets/images/dusk/author_camera_icon.png') }}" alt="">
<small>
{{ $photo->user->username }}
</small>
</div>
</a>
@@ -0,0 +1,43 @@
@props(['rare'])
<div class="p-3 rounded bg-gray-200 dark:bg-gray-700 flex gap-x-6 gap-4 items-center overflow-hidden">
<div class="w-8 h-8">
<div class="w-10 h-10 overflow-hidden rounded-full flex items-center justify-center bg-gray-300 dark:bg-gray-800">
<img src="{{ sprintf('%s/%s', setting('furniture_icons_path'), $rare->furniture_icon) }}" alt="">
</div>
</div>
<div class="flex flex-col w-full">
<div class="font-bold text-gray-700 dark:text-gray-200 truncate flex items-center gap-x-[5px]">
@if($rare->item_id)
<a href="{{ route('values.value', $rare) }}" class="underline">
{{ strLimit($rare->name, 15) }}
</a>
@else
{{ strLimit($rare->name, 20) }}
@endif
@if($rare->isLimitedEdition())
<img class="w-4 h-4" src="{{ asset('/assets/images/icons/ltd.png') }}" alt="">
@endif
</div>
<div class="w-full bg-yellow-400 rounded h-[35px] flex items-center mt-2">
<div class="bg-yellow-500 rounded-l w-1/3 px-4 h-full flex items-center justify-center">
<img src="{{ asset('assets/images/icons/currency/credits.png') }}" alt="">
</div>
<p class="w-full text-center truncate">
{{ $rare->credit_value ?? 0 }} {{ __('credits') }}
</p>
</div>
<div class="w-full bg-gray-500 rounded h-[35px] flex items-center mt-1">
<div class="bg-gray-600 rounded-l w-1/3 px-4 h-full flex items-center justify-center">
<img src="{{ asset('/assets/images/icons/navigation/shop.png') }}" alt="">
</div>
<p class="w-full text-center truncate">
{{ $rare->currency_value ?? 0 }} {{ $rare->currency_type === 0 ? 'Duckets' : ($rare->currency_type === 5 ? 'Diamonds' : 'Other') }}
</p>
</div>
</div>
</div>
@@ -0,0 +1,95 @@
@props(['package', 'gift' => false])
<x-modals.regular-modal>
<x-slot name="title">
<h2 class="text-2xl">
{{ __(':package contents', ['package' => $package->name]) }}
</h2>
</x-slot>
<ul class="list-disc pl-4">
@if($package->features)
@foreach($package->features as $feature)
<li class="ml-3">
{{ $feature->content }}
</li>
@endforeach
@endif
@if ($package->credits)
<li class="ml-3">{{ number_format($package->credits, 0, '.', '.') }} credits</li>
@endif
@if ($package->duckets)
<li class="ml-3">{{ number_format($package->duckets, 0, '.', '.') }} duckets</li>
@endif
@if ($package->diamonds)
<li class="ml-3">{{ number_format($package->diamonds, 0, '.', '.') }} diamonds</li>
@endif
@if ($package->rank)
<li class="ml-3">
{{ $package->rank->rank_name }} rank
</li>
@endif
</ul>
<div class="mt-6">
<p class="font-bold">
{{ __('Other features:') }}
</p>
<div class="flex flex-col gap-3 text-gray-100 mt-3 bg-[#303642] p-4 rounded-md">
@if (!empty($package->badges))
<p>
{{ __('Badge(s) included:') }}
</p>
<div class="flex flex-wrap gap-2 items-center">
@foreach (explode(';', $package->badges) as $badge)
<div
class="h-[50px] w-[50px] overflow-hidden p-2 bg-[#444d5c] rounded-md flex items-center justify-center">
<img data-tippy-content="1x {{ $badge }}"
src="/client/flash/c_images/album1584/{{$badge}}.gif" alt="{{ $badge }}"
style="image-rendering: auto;">
</div>
@endforeach
</div>
@endif
@if ($package->furniture)
<p>
{{ __('Furniture included:') }}
</p>
<div class="flex flex-col dark:text-white">
<div class="flex flex-wrap gap-2 items-center">
@foreach ($package->furniItems() as $furni)
<div
class="h-[50px] w-[50px] overflow-hidden p-2 bg-[#444d5c] rounded-md flex items-center justify-center">
<img
data-tippy-content="{{ collect(json_decode($package->furniture))->firstWhere('item_id', $furni->id)->amount }}x {{ $furni->public_name }}"
src="{{$furni->icon()}}" alt="{{ __('Missing icon') }}">
</div>
@endforeach
</div>
</div>
@endif
</div>
</div>
<div class="mt-4">
<form action="{{ route('shop.buy', $package) }}" method="POST" class="w-full">
@csrf
@if($gift)
<x-form.input name="receiver" type="text" placeholder="Enter the name of the recipient you want to gift" classes="mb-2"/>
@endif
<button type="submit"
class="w-full rounded bg-green-600 hover:bg-green-700 text-white p-2 border-2 border-green-500 transition ease-in-out duration-150 font-semibold">
{{ __('Buy for $:cost', ['cost' => $package->price()]) }}
</button>
</form>
</div>
</x-modals.regular-modal>
@@ -0,0 +1,62 @@
<x-content.shop-card color="{{ $article->color }}">
<x-slot:title>
<div class="flex justify-between w-full">
<p>
{{ $article->name }}
</p>
<span class="font-bold">
${{ $article->price() }}
</span>
</div>
</x-slot:title>
<div class="flex justify-between dark:text-white w-full">
<div class="flex flex-col items-center w-full">
<div class="flex justify-center w-full">
<div class="p-2 max-w-[65px] max-h-[65px]">
<img src="{{ $article->icon_url }}" alt="">
</div>
</div>
<div class="text-gray-100 mt-4">
{{ $article->info }}
</div>
</div>
</div>
<div class="pt-4 mt-auto flex gap-4">
<div class="w-full flex gap-2">
<x-modals.modal-wrapper>
<div x-on:click="open = true">
<x-form.primary-button classes="px-4 w-full !text-yellow-100">
<x-icons.eye />
</x-form.primary-button>
</div>
<x-shop.package-content :package="$article"/>
</x-modals.modal-wrapper>
@if($article->is_giftable)
<x-modals.modal-wrapper>
<div x-on:click="open = true">
<x-form.primary-button classes="!text-blue-100 px-4 w-full !bg-[#0b80b3] !border-[#1891c4] hover:!bg-[#096891] transition-all">
<x-icons.gift />
</x-form.primary-button>
</div>
<x-shop.package-content :package="$article" :gift="true"/>
</x-modals.modal-wrapper>
@endif
</div>
<form action="{{ route('shop.buy', $article) }}" method="POST">
@csrf
<x-form.secondary-button type="submit" classes="text-green-100 px-4">
Buy
</x-form.secondary-button>
</form>
</div>
</x-content.shop-card>
@@ -0,0 +1,20 @@
@props(['placement' => 'left', 'classes' => ''])
<div
@class([
'w-full flex mt-3',
'justify-start' => $placement === 'left',
'justify-center' => $placement === 'center',
'justify-end' => $placement === 'right',
])
class="{{ $classes }}"
>
@if (setting('google_recaptcha_enabled'))
<div class="mt-4 g-recaptcha"
data-sitekey="{{ config('habbo.site.recaptcha_site_key') }}"></div>
@endif
@if (setting('cloudflare_turnstile_enabled') === '1')
<x-turnstile />
@endif
</div>
@@ -0,0 +1,121 @@
<x-content.content-card icon="discord-icon" classes="border dark:border-gray-900">
<x-slot:title>
{{ __('Discord') }}
</x-slot:title>
<x-slot:under-title>
<span id="guildName"></span>
</x-slot:under-title>
<div class="text-sm dark:text-gray-200">
<div id="guildUsers" class="h-[129px] overflow-auto"> </div>
<a id="guildInvite" target="blank">
<x-form.secondary-button classes="mt-3">
{{ __('Join server') }}
</x-form.secondary-button>
</a>
</div>
</x-content.content-card>
@push('javascript')
<script>
window.onload = DiscordApi();
function DiscordApi() {
let init = {
method: 'GET',
mode: 'cors',
cache: 'reload'
}
//gets discord widget json from url with in settings specifed id
fetch("https://discordapp.com/api/guilds/{{ setting('discord_widget_id') }}/widget.json", init).then(
function(res) {
//if there is a problem with discord or id sends an error message in console
if (res.status != 200) {
console.error("Discord widget cant connect to discord (" + res.status + ")");
return;
}
res.json().then(function(data) {
let users = data.members;
let guildName = data.name;
//sets the subtitle of the card to the guild name
document.getElementById('guildName').innerText = guildName;
//loops over every user in json array and display them in the widget
for (let i = 0; i < data.members.length; i++) {
let container = document.createElement('div')
let leftContainer = document.createElement('div')
let imgContainer = document.createElement('div')
let img = document.createElement('img')
let status = document.createElement('div')
let rightContainer = document.createElement('div')
let name = document.createElement('p')
let motto = document.createElement('p')
//sets styleing
container.classList.add('flex', 'items-center', 'gap-x-2')
leftContainer.classList.add('relative')
imgContainer.classList.add('h-9', 'w-9', 'bg-gray-100', 'dark:bg-gray-800',
'rounded-full', 'flex', 'items-center', 'justify-center', 'overflow-hidden')
status.classList.add('absolute', 'bottom-0', 'right-0', 'w-3', 'h-3',
'rounded-full', 'border-2', 'dark:border-gray-800')
name.classList.add('font-semibold')
motto.classList.add('dark:text-gray-400')
//sets styling for exceptions
if (i === 0) {
name.classList.add('mt-1')
}
if (i !== 0) {
imgContainer.classList.add('mt-1')
name.classList.add('mt-3')
}
if (users[i].status === 'online') {
status.style.backgroundColor = "#16a34a";
}
if (users[i].status === 'idle') {
status.style.backgroundColor = "#e9b124";
}
if (users[i].status === 'dnd') {
status.style.backgroundColor = "#9c0017";
}
//adds attributes to elements
img.setAttribute('src', data.members[i].avatar_url);
if (users[i].nick === undefined) {
name.innerText = users[i].username;
} else {
name.innerText = users[i].nick;
}
if (users[i].game !== undefined) {
motto.innerText = users[i].game.name;
}
//append all elements to each other
container.appendChild(leftContainer)
leftContainer.appendChild(imgContainer)
imgContainer.appendChild(img)
leftContainer.appendChild(status)
container.appendChild(rightContainer)
rightContainer.appendChild(name)
rightContainer.appendChild(motto)
document.getElementById('guildUsers').appendChild(container)
}
//Checks if join server link is null and removes btn form webpage
if (data.instant_invite === null) {
document.getElementById('guildInvite').remove()
document.getElementById('guildUsers').style.height = "176px"
} else {
//Gives the "Join server" button a href to the default selected channel in the server
//link is recived from widget json
document.getElementById('guildInvite').setAttribute('href', data.instant_invite)
}
})
});
}
</script>
@endpush
@@ -0,0 +1,21 @@
@props(['user'])
<div class="relative flex items-center justify-between overflow-hidden rounded px-10 me-backdrop"
style="background: rgba(0, 0, 0, 0.3) url({{ setting('cms_me_backdrop') }});">
<div>
<a href="{{ route('profile.show', $user) }}"
class="absolute -bottom-12 left-0 drop-shadow transition duration-300 ease-in-out hover:scale-105">
<img style="image-rendering: pixelated;"
src="{{ setting('avatar_imager') }}{{ $user->look }}&direction=2&head_direction=3&gesture=sml&action=wav&size=l"
alt="">
</a>
</div>
<a data-turbolinks="false" href="{{ route('nitro-client') }}">
<button
class="cms-button bg-yellow-500 hover:bg-yellow-400 text-yellow-900 font-bold py-2 px-4 rounded-lg shadow-lg transition-all duration-300 hover:scale-105">
{{ __('Go to :hotel', ['hotel' => setting('hotel_name')]) }}
</button>
</a>
</div>
@@ -0,0 +1,15 @@
@props(['colSpan'])
<div class="col-span-2 lg:col-span-{{ $colSpan }}">
{{ $image }}
<div class="shadow">
<div class="flex gap-x-2 rounded-t border-b bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-900">
<p class="font-semibold text-black dark:text-white">{{ $title }}</p>
</div>
<section class="rounded-b bg-white p-3 dark:bg-gray-800 dark:text-white">
{{ $slot }}
</section>
</div>
</div>
@@ -0,0 +1,47 @@
<a href="{{ route('settings.account.show') }}"
class="{{ request()->routeIs('settings.account.show') ? 'bg-[#eeb425] text-white' : 'bg-[#21242e]' }} text-gray-100 flex gap-x-2 justify-center items-center rounded p-2 md:p-6 text-center md:text-xl font-semibold transition duration-200 ease-in-out hover:bg-[#eeb425] hover:text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
{{ __('Account settings') }}
</a>
<a href="{{ route('settings.password.show') }}"
class="{{ request()->routeIs('settings.password.show') ? 'bg-[#eeb425] text-white' : 'bg-[#21242e]' }} text-gray-100 flex gap-x-2 justify-center rounded p-2 md:p-6 text-center md:text-xl font-semibold transition duration-200 ease-in-out hover:bg-[#eeb425] hover:text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
{{ __('Password settings') }}
</a>
<a href="{{ route('settings.two-factor') }}"
class="{{ request()->routeIs('settings.two-factor') ? 'bg-[#eeb425] text-white' : 'bg-[#21242e]' }} text-gray-100 flex gap-x-2 justify-center rounded p-2 md:p-6 text-center md:text-xl font-semibold transition duration-200 ease-in-out hover:bg-[#eeb425] hover:text-white">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="h-6 w-6">
<path stroke-linecap="round" stroke-linejoin="round"
d="M7.864 4.243A7.5 7.5 0 0119.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 004.5 10.5a7.464 7.464 0 01-1.15 3.993m1.989 3.559A11.209 11.209 0 008.25 10.5a3.75 3.75 0 117.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 01-3.6 9.75m6.633-4.596a18.666 18.666 0 01-2.485 5.33" />
</svg>
{{ __('Two factor') }}
</a>
<a href="{{ route('settings.session-logs') }}"
class="{{ request()->routeIs('settings.session-logs') ? 'bg-[#eeb425] text-white' : 'bg-[#21242e]' }} text-gray-100 flex gap-x-2 justify-center rounded p-2 md:p-6 text-center md:text-xl font-semibold transition duration-200 ease-in-out hover:bg-[#eeb425] hover:text-white">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="5" y="3" width="14" height="18" rx="2" />
<line x1="9" y1="7" x2="15" y2="7" />
<line x1="9" y1="11" x2="15" y2="11" />
<line x1="9" y1="15" x2="13" y2="15" />
</svg>
{{ __('Session logs') }}
</a>
+640
View File
@@ -0,0 +1,640 @@
<x-app-layout>
@push('title', __('Badge Generator'))
<script>
const translations = {
buy_confirmation: @json(__('badge_purchase_confirmation', ['cost' => $cost, 'currency' => $currencyType])),
purchase_success: @json(__('badge_purchase_success', ['currency' => ucfirst($currencyType)])),
purchase_error_insufficient: @json(__('badge_purchase_error_insufficient', ['currency' => $currencyType])),
purchase_error_general: @json(__('badge_purchase_error_general')),
missing_fields: @json(__('Please fill in the badge name, description, and draw something on the canvas.')),
invalid_content: @json(__('Badge name and description cannot contain URLs.')),
invalid_file_type: @json(__('Only PNG and GIF files are allowed.'))
};
</script>
<div class="col-span-12 flex flex-col lg:grid grid-cols-4 gap-4" x-data="badgeDrawer({ cost: {{ $cost }}, currencyType: '{{ $currencyType }}' })">
<x-content.content-card icon="hotel-icon" classes="border dark:border-gray-900 col-span-3">
<x-slot:title>
{{ __('Badge Drawer') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Draw your very own badge') }}
</x-slot:under-title>
<div class="px-2 text-sm dark:text-gray-200">
@if ($folderError)
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4" role="alert">
<strong class="font-bold">Error:</strong>
<span class="block sm:inline">{{ $errorMessage }}</span>
</div>
@endif
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-2">
<div class="flex flex-col md:flex-row flex-wrap gap-4 justify-between">
<button @click="toggleCopyMode" :aria-pressed="copyMode" aria-label="{{ __('Toggle copy color mode') }}" class="flex items-center justify-center font-medium gap-2 flex-1 border-2 border-black rounded badge-drawer-button text-black dark:text-white">
{{ __('Copy color:') }}
<i class="fa-solid fa-eye-dropper"></i>
</button>
<button @click="toggleEraseMode" :aria-pressed="eraseMode" aria-label="{{ __('Toggle erase mode') }}" class="flex items-center justify-center font-medium gap-2 flex-1 border-2 border-black rounded badge-drawer-button text-black dark:text-white">
{{ __('Erase mode:') }}
<i class="fa-solid fa-eraser"></i>
</button>
<button @click="$refs.fileInput.click()" aria-label="{{ __('Import a picture for your badge') }}" class="flex items-center justify-center font-medium gap-2 flex-1 border-2 border-black rounded badge-drawer-button text-black dark:text-white">
{{ __('Import Picture:') }}
<i class="fa-solid fa-file-import"></i>
</button>
<button @click="showGrid = !showGrid" :aria-pressed="showGrid" aria-label="{{ __('Toggle grid visibility') }}" class="flex items-center justify-center font-medium gap-2 flex-1 border-2 border-black rounded badge-drawer-button text-black dark:text-white">
{{ __('Show Grid:') }}
<i class="fa-solid fa-table-cells"></i>
</button>
</div>
</div>
<input type="file" accept="image/png,image/gif" x-ref="fileInput" style="display:none;" @change="importImage($event)">
<div class="flex flex-col md:flex-row gap-4 md:gap-8 items-start">
<div class="checkerboard w-full max-w-[640px] mx-auto aspect-square relative">
<div id="guide" x-ref="guide"></div>
<canvas width="40" height="40" x-ref="canvas" class="w-full h-full border border-gray-300 dark:border-gray-700" style="image-rendering: pixelated; background: transparent;" role="img" aria-label="{{ __('Badge drawing area') }}"></canvas>
</div>
</div>
<div class="flex flex-col md:flex-row flex-wrap gap-4 justify-between">
<div class="flex flex-col gap-2 flex-1">
<p class="font-semibold mb-1 dark:text-gray-100">{{ __('Choose Color') }}</p>
<div class="flex items-center gap-2 w-full">
<div @click="$refs.colorInput.click()" class="w-12 h-12 !p-0 border-2 border-black rounded badge-drawer-button cursor-pointer flex items-center justify-center relative">
<i class="fa-solid fa-fill-drip fa-lg text-black"></i>
<input type="color" id="colorInput" x-ref="colorInput" x-model="color" class="absolute opacity-0 w-full h-full cursor-pointer top-0 left-0" @input="color = $event.target.value" >
</div>
<div :style="'background-color: ' + color" class="!w-full !max-w-none !h-12 badge-drawer-palette"></div>
</div>
<div class="flex flex-col gap-2 flex-1">
<p class="font-semibold mb-1 dark:text-gray-100">{{ __('Recent color') }}</p>
<div class="flex items-center flex-wrap gap-x-2 mx-auto w-full gap-y-2">
<template x-for="col in recentColors" :key="col">
<div @click="color = col; eraseMode = false" :style="'background-color: ' + col" class="badge-drawer-palette"></div>
</template>
<template x-for="i in (12 - recentColors.length)" :key="'empty-' + i">
<div class="badge-drawer-palette"></div>
</template>
</div>
</div>
</div>
<div class="flex flex-col gap-2 flex-1">
<p class="font-semibold mb-1 dark:text-gray-100">{{ __('Palette') }}</p>
<div class="flex items-center flex-wrap gap-x-2 mx-auto w-full gap-y-2">
<template x-for="col in colors">
<div @click="color = col; eraseMode = false" :style="'background-color: ' + col" class="badge-drawer-palette"></div>
</template>
</div>
</div>
</div>
<div class="flex flex-col md:flex-row gap-4 justify-between">
<button type="button" @click="clearBoard" class="w-full rounded bg-red-600 hover:bg-red-700 text-white p-2 border-2 border-red-500 transition ease-in-out duration-150 font-semibold">{{ __('Clear All') }}</button>
<button type="button" @click="generateCanvas('download')" class="w-full rounded bg-[#eeb425] text-white p-2 border-2 border-yellow-400 transition ease-in-out duration-200 hover:bg-[#d49f1c] font-semibold"> {{ __('Download badge') }} </button>
</div>
</div>
</div>
</x-content.content-card>
<x-content.content-card icon="hotel-icon" classes="border dark:border-gray-900 col-span-1 h-max">
<x-slot:title>
{{ __('Badge Drawer Details') }}
</x-slot:title>
<x-slot:under-title>
{{ __('My Badge Details') }}
</x-slot:under-title>
<div class="px-2 text-sm">
<div class="flex flex-col gap-3">
<h3 class="font-semibold dark:text-gray-100">{{ __('Preview') }}</h3>
<div id="avatarbox" class="mx-auto">
<div class="username"
style="font-size: 12px;margin-top: 13px;margin-left: 30px;color: #FFF;">
{{ auth()->user()->username}}
</div>
<div class="avatara"
style="float: left;background: url('{{ setting('avatar_imager') }}{{ auth()->user()->look}}&direction=4&head_direction=3') no-repeat;width: 60px;height: 120px;margin-left: 15px;margin-top: 10px;">
</div>
<div class="preview" style='float: left;margin-left: 15px;margin-top: 7px;'>
<canvas width="40" height="40" x-ref="previewCanvas" style="image-rendering: pixelated; background: transparent; role="img" aria-label="Badge preview""></canvas>
</div>
</div>
<div class="">
<label for="badgeName" class="font-semibold dark:text-gray-100">{{ __('Badge Name:') }}</label>
<input type="text" id="badgeName" x-model="badgeName" maxlength="24" class="mt-1 focus:ring-0 border-4 border-gray-200 rounded dark:bg-gray-800 dark:border-gray-700 dark:text-gray-200 focus:border-[#eeb425] w-full">
</div>
<div class="mt-2">
<label for="badgeDescription" class="font-semibold mb-4 dark:text-gray-100">{{ __('Badge Description:') }}</label>
<input type="text" id="badgeDescription" x-model="badgeDescription" maxlength="255" class="mt-1 focus:ring-0 border-4 border-gray-200 rounded dark:bg-gray-800 dark:border-gray-700 dark:text-gray-200 focus:border-[#eeb425] w-full">
</div>
<button type="button" x-text="buttonText" @click="buyBadge" class="w-full rounded text-white p-2 border-2 border-green-500 transition ease-in-out duration-150 font-semibold bg-green-600 hover:bg-green-700" :class="isValid ? 'cursor-pointer' : 'cursor-not-allowed'" :disabled="!isValid || {{ $folderError ? 'true' : 'false' }}"></button>
</div>
</div>
</x-content.content-card>
</div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.0/css/all.min.css" integrity="sha512-DxV+EoADOkOygM4IR9yXP8Sb2qwgidEmeqAEmDKIOfPRQZOWbXCzLC6vjbZyy0vPisbH2SyW27+ddLVCN+OMzQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="{{ asset('js/gif/gif.js') }}"></script>
<script>
function badgeDrawer({ cost, currencyType }) {
return {
cost: cost,
currencyType: currencyType,
color: '#000000',
recentColors: [],
eraseMode: false,
copyMode: false,
showGrid: true,
isDrawing: false,
cellSideCount: 40,
colorHistory: {},
badgeName: '',
badgeDescription: '',
lastBuyTime: null,
canBuy: true,
buttonText: `{{ __('Buy Badge') }} (${cost} ${currencyType.charAt(0).toUpperCase() + currencyType.slice(1)})`,
drawingContext: null,
previewContext: null,
guide: null,
canvas: null,
previewCanvas: null,
colors: [
'#000000', '#FFFFFF', '#808080', '#C0C0C0', '#FF0000', '#800000',
'#FFFF00', '#808000', '#00FF00', '#008000', '#00FFFF', '#008080',
'#0000FF', '#000080', '#FF00FF', '#800080', '#FF4500', '#FFA500',
'#FFD700', '#F0E68C', '#90EE90', '#98FB98', '#AFEEEE', '#ADD8E6',
'#87CEFA', '#6495ED', '#DDA0DD', '#EE82EE', '#A52A2A', '#D2691E',
'#CD853F', '#F4A460', '#FFC0CB', '#9370DB', '#228B22', '#20B2AA'
],
get isValid() {
return this.badgeName.trim().length > 0 && this.badgeName.trim().length <= 24 && this.badgeDescription.trim().length > 0 && this.badgeDescription.trim().length <= 255 && Object.keys(this.colorHistory).length > 0 && !this.badgeName.match(/https?:\/\/|www\./i) && !this.badgeDescription.match(/https?:\/\/|www\./i) && this.canBuy;
},
init() {
this.canvas = this.$refs.canvas;
this.previewCanvas = this.$refs.previewCanvas;
this.guide = this.$refs.guide;
this.drawingContext = this.canvas.getContext('2d');
this.previewContext = this.previewCanvas.getContext('2d');
// Get actual rendered size of the canvas
const canvasWidth = this.canvas.clientWidth;
const cellSize = canvasWidth / this.cellSideCount;
// Setup the guide with dynamic grid lines using gradients (no borders, no child divs)
this.guide.style.width = `${canvasWidth}px`;
this.guide.style.height = `${canvasWidth}px`;
// Clear any existing children (no longer needed)
this.guide.innerHTML = '';
// Determine border color based on dark mode
const isDark = document.documentElement.classList.contains('dark');
const borderColor = isDark ? '#fff' : '#4b5563'; // gray-700
// Set grid lines as background gradients
this.guide.style.backgroundImage = `
repeating-linear-gradient(to bottom, ${borderColor} 0px 1px, transparent 1px ${cellSize}px),
repeating-linear-gradient(to right, ${borderColor} 0px 1px, transparent 1px ${cellSize}px)
`;
// Watch for grid toggle (use block instead of grid)
this.$watch('showGrid', (value) => {
this.guide.style.display = value ? 'block' : 'none';
});
// Make checkerboard dynamic and aligned to cellSize
const checkerboardEl = this.$el.querySelector('.checkerboard');
let bgColor, checkColor;
if (isDark) {
bgColor = '#1a1a1a';
checkColor = '#2a2a2a';
} else {
bgColor = '#fff';
checkColor = '#eee';
}
const checkUnit = cellSize / 2; // Half cell for finer checks (2x2 per cell)
checkerboardEl.style.backgroundColor = bgColor;
checkerboardEl.style.backgroundImage = `
linear-gradient(45deg, ${checkColor} 25%, transparent 25%),
linear-gradient(-45deg, ${checkColor} 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, ${checkColor} 75%),
linear-gradient(-45deg, transparent 75%, ${checkColor} 75%)
`;
checkerboardEl.style.backgroundSize = `${checkUnit * 2}px ${checkUnit * 2}px`;
checkerboardEl.style.backgroundPosition = `0 0, 0 ${checkUnit}px, ${checkUnit}px -${checkUnit}px, -${checkUnit}px 0px`;
// Watch for erase/copy toggle
this.$watch('eraseMode', (value) => {
if (value) this.copyMode = false;
});
this.$watch('copyMode', (value) => {
if (value) this.eraseMode = false;
});
// Mouse events
this.canvas.addEventListener('mousedown', this.handleMousedown.bind(this));
this.canvas.addEventListener('mousemove', this.handleMousemove.bind(this));
this.canvas.addEventListener('mouseup', () => { this.isDrawing = false; });
this.canvas.addEventListener('mouseleave', () => { this.isDrawing = false; });
// Touch events
this.canvas.addEventListener('touchstart', this.handleTouchstart.bind(this));
this.canvas.addEventListener('touchmove', this.handleTouchmove.bind(this));
this.canvas.addEventListener('touchend', () => { this.isDrawing = false; });
this.canvas.addEventListener('touchcancel', () => { this.isDrawing = false; });
// Initial preview
this.updatePreview();
},
toggleCopyMode() {
this.copyMode = !this.copyMode;
if (this.copyMode) {
this.eraseMode = false;
}
},
toggleEraseMode() {
this.eraseMode = !this.eraseMode;
if (this.eraseMode) {
this.copyMode = false;
}
},
handleMousedown(e) {
if (e.button !== 0) return;
const canvasBoundingRect = this.canvas.getBoundingClientRect();
const scaleX = this.canvas.width / canvasBoundingRect.width;
const scaleY = this.canvas.height / canvasBoundingRect.height;
const x = (e.clientX - canvasBoundingRect.left) * scaleX;
const y = (e.clientY - canvasBoundingRect.top) * scaleY;
const cellX = Math.floor(x);
const cellY = Math.floor(y);
const currentColor = this.colorHistory[`${cellX}_${cellY}`];
if (this.copyMode) {
if (currentColor) {
this.color = currentColor;
this.copyMode = false;
}
} else if (e.ctrlKey) {
if (currentColor) {
this.color = currentColor;
}
} else {
this.paintCell(cellX, cellY);
this.isDrawing = true;
}
},
handleMousemove(e) {
if (!this.isDrawing) return;
const canvasBoundingRect = this.canvas.getBoundingClientRect();
const scaleX = this.canvas.width / canvasBoundingRect.width;
const scaleY = this.canvas.height / canvasBoundingRect.height;
const x = (e.clientX - canvasBoundingRect.left) * scaleX;
const y = (e.clientY - canvasBoundingRect.top) * scaleY;
const cellX = Math.floor(x);
const cellY = Math.floor(y);
this.paintCell(cellX, cellY);
},
handleTouchstart(e) {
e.preventDefault();
const touch = e.touches[0];
const canvasBoundingRect = this.canvas.getBoundingClientRect();
const scaleX = this.canvas.width / canvasBoundingRect.width;
const scaleY = this.canvas.height / canvasBoundingRect.height;
const x = (touch.clientX - canvasBoundingRect.left) * scaleX;
const y = (touch.clientY - canvasBoundingRect.top) * scaleY;
const cellX = Math.floor(x);
const cellY = Math.floor(y);
const currentColor = this.colorHistory[`${cellX}_${cellY}`];
if (this.copyMode) {
if (currentColor) {
this.color = currentColor;
this.copyMode = false;
}
} else {
this.paintCell(cellX, cellY);
this.isDrawing = true;
}
},
handleTouchmove(e) {
e.preventDefault();
if (!this.isDrawing) return;
const touch = e.touches[0];
const canvasBoundingRect = this.canvas.getBoundingClientRect();
const scaleX = this.canvas.width / canvasBoundingRect.width;
const scaleY = this.canvas.height / canvasBoundingRect.height;
const x = (touch.clientX - canvasBoundingRect.left) * scaleX;
const y = (touch.clientY - canvasBoundingRect.top) * scaleY;
const cellX = Math.floor(x);
const cellY = Math.floor(y);
this.paintCell(cellX, cellY);
},
importImage(e) {
const file = e.target.files[0];
if (!file) return;
if (!['image/png', 'image/gif'].includes(file.type)) {
alert(translations.invalid_file_type);
return;
}
if (!confirm('Import image and overwrite the canvas?')) {
e.target.value = '';
return;
}
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onerror = () => {
alert('Invalid image format. Please upload a valid PNG or GIF file.');
e.target.value = '';
};
img.onload = () => {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = this.cellSideCount;
tempCanvas.height = this.cellSideCount;
const tempContext = tempCanvas.getContext('2d');
tempContext.drawImage(img, 0, 0, this.cellSideCount, this.cellSideCount);
// Binarize alpha to avoid semi-transparent pixels
const imageData = tempContext.getImageData(0, 0, this.cellSideCount, this.cellSideCount);
for (let i = 0; i < imageData.data.length; i += 4) {
const a = imageData.data[i + 3];
if (a < 128) {
imageData.data[i + 3] = 0;
} else {
imageData.data[i + 3] = 255;
}
}
tempContext.putImageData(imageData, 0, 0);
// Clear the board
this.drawingContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.colorHistory = {};
// Draw the resized image
this.drawingContext.drawImage(tempCanvas, 0, 0);
// Update colorHistory
const importedImageData = this.drawingContext.getImageData(0, 0, this.cellSideCount, this.cellSideCount);
for (let y = 0; y < this.cellSideCount; y++) {
for (let x = 0; x < this.cellSideCount; x++) {
const index = (y * this.cellSideCount + x) * 4;
const r = importedImageData.data[index];
const g = importedImageData.data[index + 1];
const b = importedImageData.data[index + 2];
const a = importedImageData.data[index + 3];
if (a > 0) {
const hexColor = this.rgbToHex(r, g, b);
this.colorHistory[`${x}_${y}`] = hexColor;
// Add to recentColors
if (!this.recentColors.includes(hexColor)) {
this.recentColors.unshift(hexColor);
if (this.recentColors.length > 12) {
this.recentColors = this.recentColors.slice(0, 12);
}
}
}
}
}
this.updatePreview();
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
},
rgbToHex(r, g, b) {
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
return '#' + ((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1).toUpperCase();
},
paintCell(cellX, cellY) {
if (this.eraseMode) {
this.drawingContext.clearRect(cellX, cellY, 1, 1);
delete this.colorHistory[`${cellX}_${cellY}`];
} else {
this.drawingContext.fillStyle = this.color;
this.drawingContext.fillRect(cellX, cellY, 1, 1);
this.colorHistory[`${cellX}_${cellY}`] = this.color;
// Add to recentColors only when painting
if (!this.recentColors.includes(this.color)) {
this.recentColors.unshift(this.color);
if (this.recentColors.length > 12) {
this.recentColors = this.recentColors.slice(0, 12);
}
}
}
this.updatePreview();
},
clearBoard() {
if (!confirm('Clear the entire board?')) return;
this.drawingContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.colorHistory = {};
this.recentColors = []; // Clear recent colors as well
this.updatePreview();
},
updatePreview() {
this.previewContext.clearRect(0, 0, this.previewCanvas.width, this.previewCanvas.height);
this.previewContext.drawImage(this.canvas, 0, 0);
},
generateGifBlob() {
return new Promise((resolve) => {
// Get image data to find used colors
const imageData = this.drawingContext.getImageData(0, 0, this.canvas.width, this.canvas.height);
let usedColors = new Set();
for (let y = 0; y < this.canvas.height; y++) {
for (let x = 0; x < this.canvas.width; x++) {
const idx = (y * this.canvas.width + x) * 4;
if (imageData.data[idx + 3] > 0) { // Only consider opaque pixels
const r = imageData.data[idx];
const g = imageData.data[idx + 1];
const b = imageData.data[idx + 2];
usedColors.add((r << 16) | (g << 8) | b); // Store as integer for efficiency
}
}
}
// Find an unused color for transparency placeholder, starting from 0xFF00FF
let transColor = 0xFF00FF;
while (usedColors.has(transColor)) {
transColor = (transColor + 1) % 0x1000000; // Increment and wrap around (unlikely to loop much)
}
const transHex = '#' + transColor.toString(16).padStart(6, '0').toUpperCase();
const tempCanvas = document.createElement('canvas');
tempCanvas.width = this.canvas.width;
tempCanvas.height = this.canvas.height;
const tempContext = tempCanvas.getContext('2d');
tempContext.fillStyle = transHex; // Use the dynamic placeholder
tempContext.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
tempContext.drawImage(this.canvas, 0, 0);
const gif = new GIF({
workers: 2,
quality: 10,
workerScript: '{{ asset('js/gif/gif.worker.js') }}', // Local worker script
width: this.canvas.width,
height: this.canvas.height,
transparent: transColor // Use the dynamic integer value
});
gif.addFrame(tempCanvas);
gif.on('finished', (blob) => {
resolve(blob);
});
gif.render();
});
},
async generateCanvas(action) {
const blob = await this.generateGifBlob();
if (action === 'download') {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'badge.gif';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
},
async buyBadge() {
if (Object.keys(this.colorHistory).length === 0 || !this.badgeName.trim() || !this.badgeDescription.trim()) {
alert(translations.missing_fields);
return;
}
if (this.badgeName.match(/https?:\/\/|www\./i) || this.badgeDescription.match(/https?:\/\/|www\./i)) {
alert(translations.invalid_content);
return;
}
if (!this.canBuy || !confirm(translations.buy_confirmation)) return;
this.lastBuyTime = Date.now();
this.canBuy = false;
this.buttonText = `Cooldown (${Math.ceil((30000 - (Date.now() - this.lastBuyTime)) / 1000)} sec)`;
const interval = setInterval(() => {
const remainingTime = Math.ceil((30000 - (Date.now() - this.lastBuyTime)) / 1000);
if (remainingTime <= 0) {
clearInterval(interval);
this.canBuy = true;
this.buttonText = `{{ __('Buy Badge') }} (${this.cost} ${this.currencyType.charAt(0).toUpperCase() + this.currencyType.slice(1)})`;
} else {
this.buttonText = `Cooldown (${remainingTime} sec)`;
}
}, 1000);
const blob = await this.generateGifBlob();
const reader = new FileReader();
reader.onloadend = () => {
const base64data = reader.result;
fetch('{{ route('badge.buy') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
body: JSON.stringify({ badge_data: base64data, badge_name: this.badgeName, badge_description: this.badgeDescription })
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(translations.purchase_success);
} else {
alert(data.message || translations.purchase_error_insufficient);
clearInterval(interval); // Clear interval if buy fails
this.canBuy = true;
this.buttonText = `{{ __('Buy Badge') }} (${this.cost} ${this.currencyType.charAt(0).toUpperCase() + this.currencyType.slice(1)})`;
}
})
.catch(error => {
console.error('Error:', error);
alert(translations.purchase_error_general);
clearInterval(interval); // Clear interval if buy fails
this.canBuy = true;
this.buttonText = `{{ __('Buy Badge') }} (${this.cost} ${this.currencyType.charAt(0).toUpperCase() + this.currencyType.slice(1)})`;
});
};
reader.readAsDataURL(blob);
// Ensure cooldown ends even if fetch fails or takes long
setTimeout(() => {
if (!this.canBuy) {
clearInterval(interval);
this.canBuy = true;
this.buttonText = `{{ __('Buy Badge') }} (${this.cost} ${this.currencyType.charAt(0).toUpperCase() + this.currencyType.slice(1)})`;
}
}, 30000);
}
}
}
</script>
<style>
#canvas {
cursor: pointer;
}
#guide {
display: block;
pointer-events: none;
position: absolute;
top: 0;
left: 0;
background-repeat: repeat;
}
.checkerboard {
}
.dark .checkerboard {
}
input[type="color"] {
position: absolute;
top: auto;
left: auto;
bottom: 0;
right: 0;
transform: translateY(100%);
}
#avatarbox {
background: url('/assets/images/badgecreator/avatarbox.png');
width: 199px;
height: 180px;
float: right;
}
.tint-red {
filter: invert(21%) sepia(87%) saturate(4855%) hue-rotate(346deg) brightness(91%) contrast(92%);
}
</style>
</x-app-layout>
+51
View File
@@ -0,0 +1,51 @@
<x-app-layout>
@push('title', auth()->user()->username)
<div class="col-span-12 flex flex-col lg:flex-row gap-4">
<div class="flex flex-col gap-4 w-full lg:w-3/5">
@foreach($categories->where('small_box', false) as $category)
<x-content.content-card icon="duo-chat-icon" classes="border border-gray-900">
<x-slot:title>
{{ $category->name }}
</x-slot:title>
<div class="px-2 text-sm text-gray-200">
<img class="px-2" style="float: right !important;"
src="{{ asset('/assets/images/help-center/' . $category->image_url) }}" alt="">
{!! $category->content !!}
</div>
<a data-turbolinks="false" href="{{ $category->button_url ?? '#' }}" class="mt-4 ml-2">
<button
style="background-color: {{ $category->button_color }}; border: {{ $category->button_border_color }} solid 2px;"
class="px-2 py-1 text-white font-semibold rounded transition hover:scale-105">
{{ $category->button_text }}
</button>
</a>
</x-content.content-card>
@endforeach
</div>
<div class="flex flex-col gap-4 w-full lg:w-2/5">
@foreach($categories->where('small_box', true) as $category)
<x-content.content-card icon="duo-chat-icon" classes="border border-gray-900">
<x-slot:title>
{{ $category->name }}
</x-slot:title>
<div class="px-2 text-sm text-gray-200">
{!! $category->content !!}
</div>
<a data-turbolinks="false" href="{{ $category->button_url ?? '#' }}" class="mt-4 ml-2">
<button
style="background-color: {{ $category->button_color }}; border: {{ $category->button_border_color }} solid 2px;"
class="px-2 py-1 text-white font-semibold rounded transition hover:scale-105">
{{ $category->button_text }}
</button>
</a>
</x-content.content-card>
@endforeach
</div>
</div>
</x-app-layout>
@@ -0,0 +1,78 @@
<x-app-layout>
@push('title', 'Create a ticket')
<x-content.content-card icon="chat-icon" classes="border border-gray-900 col-span-12 lg:col-span-9">
<x-slot:title>
{{ __('Create a ticket') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Please describe your request below') }}
</x-slot:under-title>
<form action="{{ route('help-center.ticket.store') }}" method="POST">
@csrf
<select name="category_id" id="category_id"
class="focus:ring-0 border-4 rounded bg-gray-800 border-gray-700 text-gray-200 focus:border-[#eeb425] w-full @error('category_id') border-red-600 ring-red-500 @enderror">
@foreach($categories as $category)
<option value="{{ $category->id }}">
{{ $category->name }}
</option>
@endforeach
</select>
<div class="mt-4 no-tailwind">
<x-form.label for="password_confirmation">
{{ __('Title') }}
</x-form.label>
<x-form.input name="title" type="text"
placeholder="{{ __('Enter a title for your ticket') }}"/>
</div>
<x-form.wysiwyg-editor/>
<x-form.secondary-button type="submit" classes="mt-4">
{{ __('Submit ticket') }}
</x-form.secondary-button>
</form>
</x-content.content-card>
<div class="col-span-12 lg:col-span-3">
<x-content.content-card icon="duo-chat-icon"
classes="border border-gray-900 text-gray-100">
<x-slot:title>
{{ __('Open tickets') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Your current open tickets') }}
</x-slot:under-title>
<div class="flex flex-col gap-2">
@forelse($openTickets as $ticket)
<div class="w-full rounded p-2 bg-gray-700">
<div class="flex items-center gap-x-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M11.25 4.5l7.5 7.5-7.5 7.5m-6-15l7.5 7.5-7.5 7.5"/>
</svg>
<a data-turbolinks="false" href="{{ route('help-center.ticket.show', $ticket) }}" class="hover:text-[#eeb425]">
{{ Str::limit($ticket->title, 20) }}
</a>
</div>
</div>
@empty
<p>
You currently have no open tickets.
</p>
@endforelse
</div>
</x-content.content-card>
</div>
</x-app-layout>
@@ -0,0 +1,83 @@
<x-app-layout>
@push('title', 'Create a ticket')
<x-content.content-card icon="chat-icon" classes="border dark:border-gray-900 col-span-12 lg:col-span-9">
<x-slot:title>
{{ __('Edit your ticket') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Please describe your request below') }}
</x-slot:under-title>
<form action="{{ route('help-center.ticket.update', $ticket) }}" method="POST">
@method('PUT')
@csrf
<select name="category_id" id="category_id"
class="focus:ring-0 border-4 border-gray-200 rounded dark:bg-gray-800 dark:border-gray-700 dark:text-gray-200 focus:border-[#eeb425] w-full @error('category_id') border-red-600 ring-red-500 @enderror">
<option value="{{ $ticket->category_id }}" selected>
{{ $ticket->category->name }}
</option>
@foreach($categories as $category)
<option value="{{ $category->id }}">
{{ $category->name }}
</option>
@endforeach
</select>
<div class="mt-4 no-tailwind">
<x-form.label for="password_confirmation">
{{ __('Title') }}
</x-form.label>
<x-form.input name="title" type="text" value="{{ $ticket->title }}"
placeholder="{{ __('Enter a title for your ticket') }}"/>
</div>
<x-form.wysiwyg-editor :content="$ticket->content"/>
<x-form.secondary-button type="submit" classes="mt-4">
{{ __('Update ticket') }}
</x-form.secondary-button>
</form>
</x-content.content-card>
<div class="col-span-12 lg:col-span-3">
<x-content.content-card icon="duo-chat-icon"
classes="border dark:border-gray-900 dark:text-gray-100">
<x-slot:title>
{{ __('Open tickets') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Your current open tickets') }}
</x-slot:under-title>
<div class="flex flex-col gap-2">
@forelse($openTickets as $ticket)
<div class="w-full rounded bg-gray-200 p-2 dark:bg-gray-700">
<div class="flex items-center gap-x-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M11.25 4.5l7.5 7.5-7.5 7.5m-6-15l7.5 7.5-7.5 7.5"/>
</svg>
<a data-turbolinks="false" href="{{ route('help-center.ticket.show', $ticket) }}" class="hover:text-[#eeb425]">
{{ Str::limit($ticket->title, 20) }}
</a>
</div>
</div>
@empty
<p>
You currently have no open tickets.
</p>
@endforelse
</div>
</x-content.content-card>
</div>
</x-app-layout>
@@ -0,0 +1,83 @@
<x-app-layout>
@push('title', 'Create a ticket')
<x-content.content-card icon="chat-icon" classes="border dark:border-gray-900 dark:text-gray-100 col-span-12">
<x-slot:title>
{{ __('All tickets') }}
</x-slot:title>
<div class="overflow-hidden overflow-x-auto rounded border border-gray-200 dark:border-gray-700">
<table class="min-w-full text-sm divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-100 dark:bg-gray-800">
<tr>
<th class="whitespace-nowrap px-4 py-2 text-left font-medium text-gray-900 dark:text-white">
{{ __('Title') }}
</th>
<th class="whitespace-nowrap px-4 py-2 text-left font-medium text-gray-900 dark:text-white">
{{ __('Author') }}
</th>
<th class="whitespace-nowrap px-4 py-2 text-left font-medium text-gray-900 dark:text-white">
{{ __('Status') }}
</th>
<th class="whitespace-nowrap px-4 py-2 text-left font-medium text-gray-900 dark:text-white">
{{ __('Actions') }}
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
@forelse ($tickets as $ticket)
<tr>
<td class="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-gray-300">
{{ Str::limit($ticket->title, 80) }}
</td>
<td class="px-4 py-2 text-gray-700 dark:text-gray-300">
{{ $ticket->user->username }}</td>
<td class="px-4 py-2 text-gray-700 dark:text-gray-300">
{{ $ticket->open ? 'Open' : 'Closed' }}
</td>
<td class="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-gray-300 flex gap-x-3">
<a data-turbolinks="false" href="{{ route('help-center.ticket.show', $ticket) }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</a>
<a data-turbolinks="false" href="{{ route('help-center.ticket.edit', $ticket) }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
</svg>
</a>
@if(hasPermission('delete_website_tickets'))
<form action="{{ route('help-center.ticket.destroy', $ticket) }}" method="POST">
@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="w-5 h-5">
<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
</td>
</tr>
@empty
<tr>
<td class="whitespace-nowrap px-4 py-2 text-center text-gray-700 dark:text-gray-300"
colspan="3">
{{ __('No tickets available') }}
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-4">
{{ $tickets->links() }}
</div>
</x-content.content-card>
</x-app-layout>
@@ -0,0 +1,185 @@
<x-app-layout>
@push('title', 'Create a ticket')
<x-content.content-card icon="chat-icon" classes="border dark:border-gray-900 dark:text-gray-100 col-span-12 lg:col-span-9">
<x-slot:title>
<div class="flex gap-x-2">
{{ $ticket->title }} [{{ $ticket->category->name }}]
@if($ticket->canManageTicket())
<a data-turbolinks="false" href="{{ route('help-center.ticket.edit', $ticket) }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
</svg>
</a>
@endif
</div>
</x-slot:title>
<div class="w-full flex gap-x-3">
@if($ticket->isOpen())
<form action="{{ route('help-center.ticket.toggle-status', $ticket) }}" method="POST" class="w-full">
@method('PUT')
@csrf
<x-form.secondary-button>
Close
</x-form.secondary-button>
</form>
@else
<form action="{{ route('help-center.ticket.toggle-status', $ticket) }}" method="POST" class="w-full">
@method('PUT')
@csrf
<x-form.primary-button>
Re-open
</x-form.primary-button>
</form>
@endif
<form action="{{ route('help-center.ticket.destroy', $ticket) }}" method="POST" class="w-full">
@method('DELETE')
@csrf
<x-form.danger-button>
Delete
</x-form.danger-button>
</form>
</div>
<article class="prose-xl mt-8" style="width: 100%;">
{!! $ticket->content !!}
</article>
</x-content.content-card>
<div class="col-span-12 lg:col-span-3">
<x-content.content-card icon="duo-chat-icon"
classes="border dark:border-gray-900 dark:text-gray-100">
<x-slot:title>
{{ __('Open tickets') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Your current open tickets') }}
</x-slot:under-title>
<div class="flex flex-col gap-2">
@forelse($openTickets as $ticket)
<div class="w-full rounded bg-gray-200 p-2 dark:bg-gray-700">
<div class="flex items-center gap-x-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M11.25 4.5l7.5 7.5-7.5 7.5m-6-15l7.5 7.5-7.5 7.5"/>
</svg>
<a data-turbolinks="false" href="{{ route('help-center.ticket.show', $ticket) }}" class="hover:text-[#eeb425]">
{{ Str::limit($ticket->title, 20) }}
</a>
</div>
</div>
@empty
<p>
You currently have no open tickets.
</p>
@endforelse
</div>
</x-content.content-card>
</div>
<x-content.content-card icon="duo-chat-icon"
classes="border dark:border-gray-900 dark:text-gray-100 border dark:border-gray-900 dark:text-gray-100 col-span-12 lg:col-span-9 -mt-4">
<x-slot:title>
{{ __('Comments') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Please submit your reply below') }}
</x-slot:under-title>
@if($ticket->isOpen())
<form action="{{ route('help-center.ticket.reply.store', $ticket) }}" method="POST">
@csrf
<x-form.wysiwyg-editor />
<x-form.secondary-button classes="mt-2">
{{ __('Submit reply') }}
</x-form.secondary-button>
</form>
@endif
<div class="flex flex-col gap-y-4 mt-4">
@forelse($ticket->replies as $reply)
@if($reply->user_id === auth()->user()->id)
<div class="w-full rounded bg-gray-200 dark:bg-gray-700">
<div class="h-[50px] px-4 flex items-center justify-between border-b border-gray-300 dark:border-gray-800 relative overflow-hidden">
<div class="flex">
<small class="ml-14 text-gray-400">{{ $reply->user->username }}</small>
<div class="absolute left-2 -bottom-10 flex gap-x-2">
<img src="{{ setting('avatar_imager') }}/{{ $reply->user->look }}" alt="">
</div>
</div>
<div class="flex gap-x-2">
<small class="text-gray-400">{{ $reply->created_at->diffForHumans() }}</small>
@if($reply->user_id === Auth::id() || hasPermission('delete_website_ticket_replies'))
<form action="{{ route('help-center.ticket.reply.destroy', $reply) }}" method="POST">
@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="w-4 h-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 class="p-4">
{!! $reply->content !!}
</div>
</div>
@else
<div class="w-full rounded bg-gray-200 dark:bg-gray-700">
<div class="h-[50px] px-4 flex items-center justify-between border-b border-gray-300 dark:border-gray-800 relative overflow-hidden">
<div class="flex gap-x-2">
<form action="{{ route('help-center.ticket.reply.destroy', $reply) }}" method="POST">
@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="w-4 h-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>
<small class="text-gray-400">{{ $reply->created_at->diffForHumans() }}</small>
</div>
<div class="flex">
<small class="mr-14 text-gray-400">{{ $reply->user->username }}</small>
<div class="absolute right-2 -bottom-10 flex gap-x-2">
<img class="scale-x-[-1]" src="{{ setting('avatar_imager') }}/{{ $reply->user->look }}" alt="">
</div>
</div>
</div>
<div class="p-4">
{!! $reply->content !!}
</div>
</div>
@endif
@empty
<p>
{{ __('There is currently no replies') }}
</p>
@endforelse
</div>
</x-content.content-card>
</x-app-layout>
+17
View File
@@ -0,0 +1,17 @@
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Dashboard') }}
</h2>
</x-slot>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="overflow-hidden bg-white shadow-xs sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
You're logged in!
</div>
</div>
</div>
</div>
</x-app-layout>
+111
View File
@@ -0,0 +1,111 @@
<x-app-layout>
@push('title', __('Welcome to the best hotel on the web!'))
<div class="col-span-12 md:col-span-6 min-h-[250px] bg-gray-900/50 rounded-xl flex flex-col py-6 px-8 text-white">
<h2 class="text-2xl">{{ __('Login') }}</h2>
<form action="{{ route('login') }}" method="POST">
@csrf
<div class="relative w-full overflow-hidden text-black">
<input id="username-input" type="text" placeholder="{{ __('Enter your username') }}" name="username" class="relative py-2 rounded-md mt-3 w-full">
<img id="user-avatar" class="absolute right-0 -top-4" src="{{ asset('/assets/images/dusk/ghost.png') }}" alt="">
</div>
<input type="password" placeholder="{{ __('Enter your password') }}" name="password" class="relative py-2 rounded-md mt-3 text-black w-full">
<x-site-captchas />
<div class="mt-4 flex gap-4">
<button type="submit" class="py-2 px-4 text-white bg-yellow-500 border-2 border-yellow-300 w-full rounded-md transition duration-300 ease-in-out hover:scale-[102%]">{{ __('Login') }}</button>
<a href="{{ route('register') }}" class="w-full">
<button type="button" class="py-2 px-4 text-white bg-gray-700 border-2 border-gray-600 w-full rounded-md transition duration-300 ease-in-out hover:scale-[102%]">{{ __('Register') }}</button>
</a>
</div>
</form>
</div>
{{-- Articles --}}
<div class="col-span-12 md:col-span-6 h-[250px]">
<!-- Slider main container -->
<div class="swiper h-[250px] rounded-md">
<!-- If we need pagination -->
<div class="swiper-pagination"></div>
<!-- If we need navigation buttons -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
<!-- Additional required wrapper -->
<div class="swiper-wrapper" style="z-index: 14;">
@foreach($articles as $article)
<x-article-card :article="$article" />
@endforeach
</div>
</div>
</div>
<div class="col-span-12 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
@foreach($photos as $photo)
<a href="{{ $photo->url }}" data-fancybox="gallery" class="cursor-pointer relative transition duration-300 ease-in-out hover:scale-[102%]">
<div class="photo-overlay"></div>
<img class="h-[250px] w-full object-cover object-center rounded-md custom-shadow" src="{{ $photo->url }}" alt="">
<div class="absolute right-2 bottom-2 bg-black/70 p-2 rounded-md text-white flex gap-x-2 z-[5]">
<img class="self-center" src="{{ asset('/assets/images/dusk/author_camera_icon.png') }}" alt="">
<small>
{{ $photo->user->username }}
</small>
</div>
</a>
@endforeach
</div>
<script>
function debounce(func, wait) {
let timeout;
return function() {
const context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
const avatar = document.getElementById('user-avatar');
const usernameInput = document.getElementById('username-input');
const updateAvatar = debounce(async () => {
const username = usernameInput.value;
if (!username) return;
try {
const response = await fetch(`/api/user/${username}`);
if (!response.ok) {
console.error('Failed to fetch avatar');
return;
}
const data = await response.json();
if (!data.data.look) {
avatar.src = "/assets/images/dusk/ghost.png";
return;
}
avatar.src = '{{ setting('avatar_imager') }}' + '/' + data.data.look + '&direction=4&action=wav&head_direction=3';
} catch (error) {
console.error('An error occurred:', error);
}
}, 200);
usernameInput.addEventListener('keyup', updateAvatar);
</script>
</x-app-layout>
+139
View File
@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">
@vite(['resources/themes/' . setting('theme', 'dusk') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.js'], 'build')
<style>
:root {
--color-primary: {{ setting('color_primary', '#eeb425') }};
--color-background: {{ setting('color_background', '#1a1a2e') }};
--color-surface: {{ setting('color_surface', '#2d2d44') }};
--color-dropdown: {{ setting('color_dropdown', '#2d2d44') }};
--color-navbar: {{ setting('color_navbar', '#ffffff') }};
--color-navbar-text: {{ setting('color_navbar_text', '#1a1a2e') }};
--color-text: {{ setting('color_text', '#ffffff') }};
--color-text-muted: {{ setting('color_text_muted', '#9ca3af') }};
--color-accent: {{ setting('color_accent', '#10b981') }};
--button-color: {{ setting('button_primary_color', '#eeb425') }};
--button-text-color: {{ setting('button_text_color', '#1a1a2e') }};
--button-secondary-color: {{ setting('button_secondary_color', '#22c55e') }};
--button-secondary-text-color: {{ setting('button_secondary_text_color', '#ffffff') }};
--button-secondary-hover-color: {{ setting('button_secondary_hover_color', '#16a34a') }};
--button-secondary-border-color: {{ setting('button_secondary_border_color', '#16a34a') }};
--button-danger-color: {{ setting('button_danger_color', '#ef4444') }};
--button-danger-text-color: {{ setting('button_danger_text_color', '#ffffff') }};
--button-danger-hover-color: {{ setting('button_danger_hover_color', '#dc2626') }};
--button-danger-border-color: {{ setting('button_danger_border_color', '#dc2626') }};
--button-outline-color: {{ setting('button_outline_color', '#eeb425') }};
--button-outline-text-color: {{ setting('button_outline_text_color', '#1a1a2e') }};
--button-outline-hover-color: {{ setting('button_outline_hover_color', '#cf9d15') }};
--border-radius: {{ setting('border_radius', '12') }}px;
--font-family: '{{ setting('font_family', 'Nunito') }}', sans-serif;
--dropdown-radius: {{ setting('dropdown_style', 'rounded') === 'square' ? '0px' : (setting('dropdown_style', 'rounded') === 'pill' ? '20px' : '8px') }};
--dropdown-border: {{ setting('dropdown_border', '1') === '1' ? '1px solid var(--color-primary)' : 'none' }};
--dropdown-shadow: {{ setting('dropdown_shadow', 'medium') === 'none' ? 'none' : (setting('dropdown_shadow', 'medium') === 'small' ? '0 2px 8px rgba(0,0,0,0.15)' : (setting('dropdown_shadow', 'medium') === 'large' ? '0 8px 32px rgba(0,0,0,0.4)' : '0 4px 16px rgba(0,0,0,0.25)')) }};
}
</style>
<x-turnstile.scripts />
<script src="{{ asset('/assets/js/dusk.js') }}"></script>
</head>
<body class="font-sans antialiased select-none">
<div id="app" class="min-h-screen bg-[#262a35] relative">
<x-messages.flash-messages />
{{-- Desktop navigation --}}
<div class="hidden lg:block">
<x-navigation.navigation-menu />
</div>
{{-- Mobile navigation --}}
<div class="block lg:hidden">
<x-navigation.mobile-navigation-menu />
</div>
{{-- Sub header --}}
<div class="sub-header px-5 xl:px-0">
<div class="max-w-7xl w-full h-[40px] flex items-center justify-between">
<div class="flex gap-4 items-center z-20 relative">
<div>
<x-navigation.language-selector>
<img src="/assets/images/icons/flags/{{ session()->has('locale') ? session()->get('locale') : config('habbo.site.default_language') }}.png"
alt="">
</x-navigation.language-selector>
</div>
<a href="{{ is_array(setting('discord_invitation_link')) ? '' : setting('discord_invitation_link') }}" target="_blank" class="transition duration-300 ease-in-out hover:text-gray-300">
Discord
</a>
</div>
<div class="flex gap-4 items-center">
<a href="{{ route('help-center.rules.index') }}" class="transition duration-300 ease-in-out hover:scale-110">
<img src="{{ asset('/assets/images/dusk/rules_icon.png') }}" alt="">
</a>
@if(hasPermission('generate_logo'))
<a href="{{ route('logo-generator.index') }}" target="_blank" class="transition duration-300 ease-in-out hover:scale-110">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
</svg>
</a>
@endif
@if(hasPermission('view_server_logs'))
<a href="/log-viewer" target="_blank" class="transition duration-300 ease-in-out hover:scale-110">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
</svg>
</a>
@endif
@if(Auth::check())
<a href="{{ setting('housekeeping_url') }}" target="_blank" class="transition duration-300 ease-in-out hover:scale-110">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 0 1 1.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.559.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.894.149c-.424.07-.764.383-.929.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 0 1-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.398.165-.71.505-.781.929l.15-.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 0 1-.12-1.45l.527-.737c.25-.35.272-.806.108-1.204-.165-.397-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 0 1 .12-1.45l.773-.773a1.125 1.125 0 0 1 1.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</a>
@endif
</div>
</div>
</div>
{{-- Site background --}}
<div class="site-bg w-full"></div>
<!-- Page Content -->
<main class="main-content">
<div class="max-w-7xl w-full grid grid-cols-12 gap-4 relative py-4 lg:py-20 px-4 xl:px-0">
{{ $slot }}
</div>
</main>
</div>
<x-footer />
<script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.umd.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.css" />
@stack('javascript')
@stack('scripts')
</body>
</html>
+33
View File
@@ -0,0 +1,33 @@
<nav class="bg-white border-b border-gray-100">
<!-- Primary Navigation Menu -->
<div class="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="flex justify-between h-16 text-gray-500 hover:text-gray-700 text-sm font-medium leading-5">
<div class="flex">
<!-- Logo -->
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<a href="{{ route('home') }}"
class="{{ request()->routeIs('home') ? 'border-indigo-400 text-gray-900 focus:border-indigo-700' : 'border-transparent hover:border-gray-300 focus:text-gray-700 focus:border-gray-300' }} inline-flex items-center px-1 pt-1 border-b-2 focus:outline-hidden">
{{ __('Dashboard') }}
</a>
</div>
</div>
<!-- Settings -->
<div class="hidden sm:flex sm:items-center sm:ml-6">
@auth
<div>{{ Auth::user()->name }}</div>
<form action="{{ route('logout') }}" method="POST" class="inline">
@csrf
<button type="submit"
class="h-16 ml-4 border-transparent hover:border-gray-300 focus:text-gray-700 focus:border-gray-300 inline-flex items-center px-1 pt-1 border-b-2 focus:outline-hidden">
{{ __('Logout') }}
</button>
</form>
@endauth
</div>
</div>
</div>
</nav>
+22
View File
@@ -0,0 +1,22 @@
<x-app-layout>
@push('title', __('Leaderboard'))
<div class="col-span-12">
<x-page-header>
<x-slot:icon>
<img src="{{ asset('/assets/images/dusk/leaderboard_icon.png') }}" alt="">
</x-slot:icon>
{{ __('Leaderboards') }}
</x-page-header>
<div class="grid grid-cols-1 gap-5 md:grid-cols-3 mt-4">
<x-leaderboard-card title="{{ __('Top credits') }}" icon="credits.png" :data="$credits" valueKey="credits" valueType="Credits" />
<x-leaderboard-card title="{{ __('Top duckets') }}" icon="duckets.png" :data="$duckets" relationship="user" valueKey="amount" valueType="Duckets" />
<x-leaderboard-card title="{{ __('Top diamonds') }}" icon="diamond.png" :data="$diamonds" relationship="user" valueKey="amount" valueType="Diamonds" />
<x-leaderboard-card title="{{ __('Hours online') }}" icon="clock.gif" :data="$mostOnline" relationship="user" valueKey="online_time" valueType="Hours online" :formatValue="fn($value) => round($value / 3600)" />
<x-leaderboard-card title="{{ __('Respects received') }}" icon="heart.gif" :data="$respectsReceived" relationship="user" valueKey="respects_received" valueType="Respect received" />
<x-leaderboard-card title="{{ __('Achievement score') }}" icon="star.gif" :data="$achievementScores" relationship="user" valueKey="achievement_score" valueType="Achievement points" />
</div>
</div>
</x-app-layout>
+179
View File
@@ -0,0 +1,179 @@
<x-app-layout>
@push('title', __('Generator'))
<div class="col-span-12">
<x-content.content-card icon="hotel-icon" classes="border border-gray-900">
<x-slot:title>
{{ __('Logo generator') }}
</x-slot:title>
<x-slot:under-title>
{{ __('Generate your very own logo') }}
</x-slot:under-title>
<div class="px-2 text-sm text-gray-200">
<div x-data="logoGenerator()" class="mt-4">
<div class="grid grid-cols-6 gap-3">
<div x-bind:class="{'bg-gray-200 bg-gray-900 ring-2 ring-emerald-700': fontType === 'atom'}" class="h-24 rounded border border-gray-700 p-2 flex gap-2 justify-center items-center transition duration-300 ease-in-out hover:bg-gray-200 hover:bg-gray-900 cursor-pointer" x-on:click="selectFont('atom')">
<img src="{{ asset('/assets/images/logo-generator/atom/a.png') }}" alt="Letter a">
<img src="{{ asset('/assets/images/logo-generator/atom/b.png') }}" alt="Letter b">
<img src="{{ asset('/assets/images/logo-generator/atom/c.png') }}" alt="Letter c">
</div>
<div x-bind:class="{'bg-gray-200 bg-gray-900 ring-2 ring-emerald-700': fontType === 'sunrise'}" class="h-24 rounded border border-gray-700 p-2 flex gap-2 justify-center items-center transition duration-300 ease-in-out hover:bg-gray-200 hover:bg-gray-900 cursor-pointer" x-on:click="selectFont('sunrise')">
<img src="{{ asset('/assets/images/logo-generator/sunrise/a.png') }}" alt="Letter a">
<img src="{{ asset('/assets/images/logo-generator/sunrise/b.png') }}" alt="Letter b">
<img src="{{ asset('/assets/images/logo-generator/sunrise/c.png') }}" alt="Letter c">
</div>
<div x-bind:class="{'bg-gray-200 bg-gray-900 ring-2 ring-emerald-700': fontType === 'marine'}" class="h-24 rounded border border-gray-700 p-2 flex gap-2 justify-center items-center transition duration-300 ease-in-out hover:bg-gray-200 hover:bg-gray-900 cursor-pointer" x-on:click="selectFont('marine')">
<img src="{{ asset('/assets/images/logo-generator/marine/a.png') }}" alt="Letter a">
<img src="{{ asset('/assets/images/logo-generator/marine/b.png') }}" alt="Letter b">
<img src="{{ asset('/assets/images/logo-generator/marine/c.png') }}" alt="Letter c">
</div>
<div x-bind:class="{'bg-gray-200 bg-gray-900 ring-2 ring-emerald-700': fontType === 'danlie'}" class="h-24 rounded border border-gray-700 p-2 flex gap-2 justify-center items-center transition duration-300 ease-in-out hover:bg-gray-200 hover:bg-gray-900 cursor-pointer" x-on:click="selectFont('danlie')">
<img src="{{ asset('/assets/images/logo-generator/danlie/a.png') }}" alt="Letter a">
<img src="{{ asset('/assets/images/logo-generator/danlie/b.png') }}" alt="Letter b">
<img src="{{ asset('/assets/images/logo-generator/danlie/c.png') }}" alt="Letter c">
</div>
<div x-bind:class="{'bg-gray-200 bg-gray-900 ring-2 ring-emerald-700': fontType === 'habton'}" class="h-24 rounded border border-gray-700 p-2 flex gap-2 justify-center items-center transition duration-300 ease-in-out hover:bg-gray-200 hover:bg-gray-900 cursor-pointer" x-on:click="selectFont('habton')">
<img src="{{ asset('/assets/images/logo-generator/habton/a.png') }}" alt="Letter a">
<img src="{{ asset('/assets/images/logo-generator/habton/b.png') }}" alt="Letter b">
<img src="{{ asset('/assets/images/logo-generator/habton/c.png') }}" alt="Letter c">
</div>
<div x-bind:class="{'bg-gray-200 bg-gray-900 ring-2 ring-emerald-700': fontType === 'habton_capitalized'}" class="h-24 rounded border border-gray-700 p-2 flex gap-2 justify-center items-center transition duration-300 ease-in-out hover:bg-gray-200 hover:bg-gray-900 cursor-pointer" x-on:click="selectFont('habton_capitalized')">
<img src="{{ asset('/assets/images/logo-generator/habton_capitalized/a.png') }}" alt="Letter a">
<img src="{{ asset('/assets/images/logo-generator/habton_capitalized/b.png') }}" alt="Letter b">
<img src="{{ asset('/assets/images/logo-generator/habton_capitalized/c.png') }}" alt="Letter c">
</div>
<div x-bind:class="{'bg-gray-200 bg-gray-900 ring-2 ring-emerald-700': fontType === 'habbo_modern'}" class="h-24 rounded border border-gray-700 p-2 flex gap-2 justify-center items-center transition duration-300 ease-in-out hover:bg-gray-200 hover:bg-gray-900 cursor-pointer" x-on:click="selectFont('habbo_modern')">
<img src="{{ asset('/assets/images/logo-generator/habbo_modern/a.png') }}" alt="Letter a">
<img src="{{ asset('/assets/images/logo-generator/habbo_modern/b.png') }}" alt="Letter b">
<img src="{{ asset('/assets/images/logo-generator/habbo_modern/c.png') }}" alt="Letter c">
</div>
</div>
<div class="mt-4">
<label for="text" class="font-bold"> {{ __('Logo text') }} </label>
<input x-model="text" class="mt-2 focus:ring-0 border-4 rounded bg-gray-800 border-gray-700 text-gray-200 focus:border-[#eeb425] w-full" id="text" type="text" name="text" placeholder="Type here...">
<div id="logoContainer" class="flex mt-4" :class="text !== '' ? 'mb-4' : ''" style="gap: 2px;" x-html="generateLogoHtml"></div>
<div class="flex gap-4 justify-between">
<button @click="generateCanvas('download')" class="w-full rounded bg-[#eeb425] text-white p-2 border-2 border-yellow-400 transition ease-in-out duration-200 hover:bg-[#d49f1c] font-semibold"> {{ __('Download logo') }} </button>
<button @click="generateCanvas('use')" class="w-full rounded bg-green-600 hover:bg-green-700 text-white p-2 border-2 border-green-500 transition ease-in-out duration-150 font-semibold"> {{ __('Use logo') }} </button>
</div>
</div>
</div>
</div>
</x-content.content-card>
</div>
{{-- TODO: Selfhost --}}
<script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.3.3/html2canvas.min.js"></script>
<script>
function logoGenerator() {
return {
fontType: 'atom',
text: '',
get generateLogoHtml() {
let sanitizedText = this.text.toLowerCase().replace(/[^a-z ]/g, '');
let html = '';
for (let i = 0; i < sanitizedText.length; i++) {
let letter = sanitizedText[i];
html += (letter === ' ') ? '<div style="width: 15px;"></div>' : `<img src="/assets/images/logo-generator/${this.fontType}/${letter}.png" alt="${letter}">`;
}
return html;
},
async generateCanvas(action) {
const logoContainer = document.querySelector('#logoContainer');
const originalStyles = logoContainer.getAttribute('style');
let combinedWidth = 0;
let maxHeight = 0;
logoContainer.childNodes.forEach(child => {
const rect = child.getBoundingClientRect();
combinedWidth += rect.width;
if (rect.height > maxHeight) {
maxHeight = rect.height;
}
});
combinedWidth += (logoContainer.childNodes.length - 1) * 2;
logoContainer.style.display = 'flex';
setTimeout(async () => {
const canvas = await html2canvas(logoContainer, {
backgroundColor: null,
width: combinedWidth,
height: maxHeight
});
logoContainer.setAttribute('style', originalStyles);
if (action === 'use') {
this.uploadLogo(canvas);
} else {
this.downloadCanvasAsImage(canvas);
}
}, 100);
},
async uploadLogo(canvas) {
canvas.toBlob(async (blob) => {
const formData = new FormData();
const sanitizedFileName = `${this.text.replace(/[^a-zA-Z0-9]/g, '_')}_${Date.now()}.png`;
formData.append('logo', blob, sanitizedFileName);
await fetch('{{ route('store.generated-logo') }}', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': "{{ csrf_token() }}"
},
body: formData
}).then(response => response.json())
.then(data => {
if (data.success) {
Toast.fire({
icon: 'success',
title: data.message
})
setTimeout(() => {
window.location.href = "{{ url()->current() }}";
}, 1000);
} else {
Toast.fire({
icon: 'error',
title: 'Something went wrong'
})
}
});
}, 'image/png');
},
async downloadCanvasAsImage(canvas) {
const a = document.createElement('a');
const sanitizedFileName = this.text.replace(/[^a-zA-Z0-9]/g, '_');
a.href = canvas.toDataURL("image/png", 1.0);
a.download = `${sanitizedFileName}.png`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
},
selectFont(font) {
this.fontType = font;
}
}
}
</script>
</x-app-layout>
+162
View File
@@ -0,0 +1,162 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{ setting('hotel_name') }} - {{ __('Maintenance') }}</title>
<link rel="stylesheet" href="https://unpkg.com/flowbite@1.5.1/dist/flowbite.min.css"/>
<script src="https://unpkg.com/flowbite@1.5.1/dist/flowbite.js"></script>
@vite(['resources/themes/' . setting('theme') . '/css/app.css', 'resources/themes/' . setting('theme') . '/js/app.js'], 'build')
</head>
<body class="h-screen overflow-hidden relative bg-[#233143]">
<x-messages.flash-messages/>
<div class="w-full h-full flex">
<div class="bg-[#111827] w-96 h-full py-10 hidden lg:flex lg:flex-col items-center gap-10 relative px-6">
<div>
<img src="{{ setting('cms_logo') }}" alt="">
</div>
<div class="flex flex-col gap-2 w-full relative z-10">
@foreach($tasks as $task)
<div
class="relative h-[80px] w-full overflow-hidden bg-[#233143] py-2 pr-2 transition duration-150 ease-in-out hover:scale-[101%] text-gray-100">
<div class="flex h-full w-full items-center gap-x-6">
<div>
@if($task->user)
<img
class="drop-shadow-thin -mb-8 transition duration-300 ease-in-out"
style="image-rendering: auto"
src="{{ setting('avatar_imager') }}{{ $task->user->look }}&direction=3&head_direction=3&gesture=sml&action=wav&frame=0"
alt=""/>
@else
<div class="w-16 h-16 bg-gray-600 rounded-full flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
@endif
</div>
<div class="flex h-full w-2/3 items-center break-words">
{{ $task->task }}
</div>
</div>
<div class="absolute bottom-2 right-2 flex justify-between w-full">
<small class="pl-24">{{ __('By: :user', ['user' => $task->user?->username]) }}</small>
@if(auth()->check() && auth()->user()->rank >= setting('min_staff_rank'))
<div onclick="event.stopPropagation()">
@livewire(\App\Livewire\MaintenanceTaskStatus::class, ['taskId' => $task->id, 'completed' => $task->completed])
</div>
@else
@if($task->completed)
<small class="flex gap-1 items-center">
{{ __('Status:') }}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 text-green-400">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
</small>
@else
<small class="flex gap-1 items-center">
{{ __('Status:') }}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-yellow-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</small>
@endif
@endif
</div>
</div>
@endforeach
{{ $tasks->links() }}
</div>
<div class="absolute bottom-0 right-0 hidden lg:block z-0">
<img src="/assets/images/maintenance/fireman.png" alt="">
</div>
</div>
<div class="px-4 lg:px-14 text-gray-100 h-full flex flex-col justify-center relative z-10">
<h2 class="text-4xl lg:text-5xl font-bold uppercase">{{ __('Maintenance break!') }}</h2>
@php
$scheduledAt = setting('maintenance_scheduled_at');
$duration = (int) setting('maintenance_duration_minutes', 30);
$endTime = $scheduledAt ? \Carbon\Carbon::parse($scheduledAt)->addMinutes($duration) : null;
$isScheduled = $endTime && $endTime->isFuture();
@endphp
@if($isScheduled)
<div class="mt-6 p-4 bg-amber-600/20 border border-amber-500/30 rounded-lg">
<p class="text-amber-300 text-sm uppercase tracking-wider mb-2">{{ __('Estimated completion') }}</p>
<div id="countdown" class="text-3xl font-mono font-bold text-amber-400" data-end="{{ $endTime->timestamp }}">
--:--:--
</div>
<p class="text-amber-300/70 text-xs mt-1">{{ $endTime->format('M d, Y H:i') }}</p>
</div>
<script>
function updateCountdown() {
const countdownEl = document.getElementById('countdown');
if (!countdownEl) return;
const endTime = parseInt(countdownEl.dataset.end) * 1000;
const now = Date.now();
const diff = endTime - now;
if (diff <= 0) {
countdownEl.textContent = '00:00:00';
return;
}
const hours = Math.floor(diff / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
countdownEl.textContent =
String(hours).padStart(2, '0') + ':' +
String(minutes).padStart(2, '0') + ':' +
String(seconds).padStart(2, '0');
}
updateCountdown();
setInterval(updateCountdown, 1000);
</script>
@endif
<article class="mt-4 text-lg lg:text-xl max-w-[600px] text-wrap">
<p>{!! setting('maintenance_message') !!}</p>
</article>
</div>
</div>
<img class="absolute bottom-0 right-0 opacity-60 hidden lg:block z-0" src="/assets/images/maintenance/hotelview.png" alt="">
@guest
<div class="absolute top-6 right-6 z-50">
<x-modals.modal-wrapper>
<button @click="open = !open"
class="rounded-full bg-white/70 px-4 py-2 font-semibold text-black transition duration-200 ease-in-out hover:bg-white">
{{ __('Staff login') }}
</button>
<x-modals.regular-modal x-model="show {{ session()->get('wrong-auth') }}">
<x-auth.login-form/>
</x-modals.regular-modal>
</x-modals.modal-wrapper>
</div>
@endguest
</body>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" defer></script>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+223
View File
@@ -0,0 +1,223 @@
#flash-container,
body {
width: 100%;
height: 100%;
margin: 0;
}
body {
background-color: #000;
}
#client-ui {
height: 100%;
}
#flash-container {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
overflow: hidden;
}
.interstitial,
Rabbo-client-reload {
height: 100%;
position: absolute;
top: 0;
width: 100%;
}
Rabbo-client-reload {
left: 0;
-ms-flex-align: center;
align-items: center;
background: rgba(0, 0, 0, 0.9);
display: flex;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-pack: center;
justify-content: center;
z-index: 620;
}
.client-reload__button {
box-shadow: 0 3px 0 1px rgba(0, 0, 0, 0.3);
display: inline-block;
line-height: 1.2;
text-align: center;
background-color: #00813e;
color: #fff;
font-size: 16px;
border-radius: 5px;
margin-bottom: 4px;
text-transform: uppercase;
border-color: #8eda55;
border-style: solid;
border-width: 2px;
padding: 12px 24px;
}
.client-reload__button:hover {
background-color: #00ab54;
border-color: #b9f373;
}
.client-reload__button:active,
.client-reload__button:disabled {
background-color: #006743;
border-color: #5abb37;
}
.client-reload__button:active,
.client-reload__button:hover {
border-bottom-style: solid;
border-bottom-width: 2px;
}
.client-reload__button:active {
box-shadow: 0 1px 0 1px rgba(0, 0, 0, 0.3);
transform: translate(0, 2px);
}
.client-reload__button:disabled,
.client__buttons .client__close:disabled,
.client__buttons button:disabled,
.discussions__reply:disabled,
.register-banner__button:disabled {
opacity: 0.4;
}
.text {
text-transform: uppercase;
font-size: 36px;
color: #fff;
font-family: "Ubuntu Condensed", "Trebuchet MS", "Lucida Grande",
"Lucida Sans Unicode", "Lucida Sans", Tahoma, sans-serif;
font-weight: 400;
line-height: 1.2;
margin: 0.6em 0;
}
#adsbanner120,
#flash-container {
position: absolute;
overflow: hidden;
}
#adsbanner120 {
color: #000;
width: 173px;
font-family: "Exo 2", sans-serif;
font-size: 15px;
z-index: 70000;
top: 47px;
right: 5px;
display: block;
min-height: 20px;
background-position: 0 214px;
padding: 8px;
}
.hb-container {
max-width: 600px;
background-color: #111524;
border-radius: 10px;
overflow: hidden;
color: #96b9ee;
display: block;
font-family: Ubuntu, "Trebuchet MS", "Lucida Grande", "Lucida Sans Unicode",
"Lucida Sans", Tahoma, sans-serif;
margin: 80px auto 0;
padding: 12px 24px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 400;
line-height: 1.2;
text-transform: uppercase;
color: #fff;
margin: 0.6em 0;
}
.hb-container p {
font-size: 16px;
margin: 0.75em 0;
}
.hb-container ul {
list-style-type: disc;
padding-left: 26px;
margin: 12px 0;
}
a {
color: #fff;
cursor: pointer;
outline: 0;
text-decoration: none;
}
.frank {
float: right;
}
.hb-button_nav {
position: fixed;
top: 15px;
left: 15px;
}
.hb-button_nav .button-left {
background-color: #ffb900;
border: 2px solid #ffea00;
font-size: 12px;
border-radius: 5px 0 0 5px;
display: inline-block;
padding: 5px;
}
.hb-button_nav .button-center {
background-color: #ffb900;
border: 2px solid #ffea00;
font-size: 12px;
border-radius: 0;
display: inline-block;
padding: 5px;
}
.hb-button_nav .button-right {
background-color: #ffb900;
border: 2px solid #ffea00;
font-size: 12px;
border-radius: 0 5px 5px 0;
display: inline-block;
padding: 5px;
}
.hb-button_nav .button-left:hover,
.hb-button_nav .button-center:hover,
.hb-button_nav .button-right:hover {
background-color: #ffd400;
border: 2px solid #fffd70;
}
.hb-button_nav .button-left img,
.hb-button_nav .button-center img,
.hb-button_nav .button-right img {
vertical-align: top;
}
.hb-button_nav .button-left a,
.hb-button_nav .button-center a,
.hb-button_nav .button-right a {
color: #000;
text-transform: uppercase;
}
+1
View File
@@ -0,0 +1 @@
.tippy-box[data-animation=scale][data-placement^=top]{transform-origin:bottom}.tippy-box[data-animation=scale][data-placement^=bottom]{transform-origin:top}.tippy-box[data-animation=scale][data-placement^=left]{transform-origin:right}.tippy-box[data-animation=scale][data-placement^=right]{transform-origin:left}.tippy-box[data-animation=scale][data-state=hidden]{transform:scale(.5);opacity:0}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Some files were not shown because too many files have changed in this diff Show More