feat: add light and dark mode variants for logo and icon with dynamic theme switching support

This commit is contained in:
Yoga Pangestu 2026-04-30 23:19:55 +07:00
parent dc68c73c2a
commit 098c2a9cf1
11 changed files with 387 additions and 156 deletions

View File

@ -29,12 +29,20 @@ public function update(GeneralSettingRequest $request): RedirectResponse
$setting = GeneralSetting::create($validated);
}
if ($request->hasFile('logo')) {
$setting->addMediaFromRequest('logo')->toMediaCollection('logo');
if ($request->hasFile('logo_light')) {
$setting->addMediaFromRequest('logo_light')->toMediaCollection('logo_light');
}
if ($request->hasFile('icon')) {
$setting->addMediaFromRequest('icon')->toMediaCollection('icon');
if ($request->hasFile('logo_dark')) {
$setting->addMediaFromRequest('logo_dark')->toMediaCollection('logo_dark');
}
if ($request->hasFile('icon_light')) {
$setting->addMediaFromRequest('icon_light')->toMediaCollection('icon_light');
}
if ($request->hasFile('icon_dark')) {
$setting->addMediaFromRequest('icon_dark')->toMediaCollection('icon_dark');
}
return redirect()->back()->with('success', 'Pengaturan berhasil diperbarui');

View File

@ -2,6 +2,7 @@
namespace App\Http\Middleware;
use App\Models\GeneralSetting;
use Illuminate\Http\Request;
use Inertia\Middleware;
@ -38,6 +39,7 @@ public function share(Request $request): array
return [
...parent::share($request),
'name' => config('app.name'),
'setting' => GeneralSetting::first(),
'auth' => [
'user' => $request->user() ? $request->user()->load('profile') : null,
'roles' => $request->user() ? $request->user()->getRoleNames() : [],

View File

@ -30,8 +30,10 @@ public function rules(): array
'description' => ['required', 'string'],
'address' => ['required', 'string'],
'phone' => ['required', 'string', 'max:20'],
'logo' => [$settingExists ? 'nullable' : 'required', 'image', 'max:2048'],
'icon' => [$settingExists ? 'nullable' : 'required', 'image', 'max:1024'],
'logo_light' => [$settingExists ? 'nullable' : 'required', 'image', 'max:2048'],
'logo_dark' => ['nullable', 'image', 'max:2048'],
'icon_light' => [$settingExists ? 'nullable' : 'required', 'image', 'max:1024'],
'icon_dark' => ['nullable', 'image', 'max:1024'],
];
}
}

View File

@ -14,31 +14,51 @@
use Spatie\MediaLibrary\InteractsWithMedia;
#[Guarded(['id'])]
#[Appends(['logo_url', 'icon_url'])]
#[Appends(['logo_light_url', 'logo_dark_url', 'icon_light_url', 'icon_dark_url'])]
class GeneralSetting extends Model implements HasMedia
{
use HasFactory, InteractsWithMedia, LogsActivity;
public function registerMediaCollections(): void
{
$this->addMediaCollection('logo')
$this->addMediaCollection('logo_light')
->singleFile();
$this->addMediaCollection('icon')
$this->addMediaCollection('logo_dark')
->singleFile();
$this->addMediaCollection('icon_light')
->singleFile();
$this->addMediaCollection('icon_dark')
->singleFile();
}
protected function logoUrl(): Attribute
protected function logoLightUrl(): Attribute
{
return Attribute::make(
get: fn () => $this->getFirstMediaUrl('logo') ?: null,
get: fn () => $this->getFirstMediaUrl('logo_light') ?: null,
);
}
protected function iconUrl(): Attribute
protected function logoDarkUrl(): Attribute
{
return Attribute::make(
get: fn () => $this->getFirstMediaUrl('icon') ?: null,
get: fn () => $this->getFirstMediaUrl('logo_dark') ?: null,
);
}
protected function iconLightUrl(): Attribute
{
return Attribute::make(
get: fn () => $this->getFirstMediaUrl('icon_light') ?: null,
);
}
protected function iconDarkUrl(): Attribute
{
return Attribute::make(
get: fn () => $this->getFirstMediaUrl('icon_dark') ?: null,
);
}

View File

@ -1,3 +1,32 @@
export default function AppLogoIcon() {
return <img src="/assets/logo.webp" alt="Logo" />;
import { cn } from '@/lib/utils';
import { usePage } from '@inertiajs/react';
import type { GeneralSetting } from '@/types';
export default function AppLogoIcon({ className }: { className?: string }) {
const { setting } = usePage<{ setting: GeneralSetting }>().props;
if (!setting?.icon_light_url && !setting?.icon_dark_url) {
return (
<img src="/assets/logo.webp" alt="Logo" className={className} />
);
}
return (
<>
{setting?.icon_light_url && (
<img
src={setting.icon_light_url}
alt="Logo Light"
className={cn('dark:hidden', className)}
/>
)}
{setting?.icon_dark_url && (
<img
src={setting.icon_dark_url}
alt="Logo Dark"
className={cn('hidden dark:block', className)}
/>
)}
</>
);
}

View File

@ -1,11 +1,30 @@
import { usePage } from '@inertiajs/react';
import type { GeneralSetting } from '@/types';
export default function AppLogo() {
const { setting } = usePage<{ setting: GeneralSetting }>().props;
return (
<>
<div className="ml-1 grid flex-1 text-left text-sm">
<span className="mb-0.5 truncate leading-tight font-semibold">
VN Grup
<div className="flex items-center gap-2 overflow-hidden">
{setting?.logo_light_url ? (
<img
src={setting.logo_light_url}
alt={setting.name}
className="max-h-8 w-auto dark:hidden"
/>
) : null}
{setting?.logo_dark_url ? (
<img
src={setting.logo_dark_url}
alt={setting.name}
className="hidden max-h-8 w-auto dark:block"
/>
) : null}
{!setting?.logo_light_url && !setting?.logo_dark_url && (
<span className="truncate font-semibold tracking-tight">
{setting?.name || 'VN Grup'}
</span>
</div>
</>
)}
</div>
);
}

View File

@ -21,61 +21,57 @@ export default function SettingIndex({
description: setting?.description || '',
address: setting?.address || '',
phone: setting?.phone || '',
logo: null as File | null,
icon: null as File | null,
logo_light: null as File | null,
logo_dark: null as File | null,
icon_light: null as File | null,
icon_dark: null as File | null,
});
const logoInputRef = useRef<HTMLInputElement>(null);
const iconInputRef = useRef<HTMLInputElement>(null);
const logoLightInputRef = useRef<HTMLInputElement>(null);
const logoDarkInputRef = useRef<HTMLInputElement>(null);
const iconLightInputRef = useRef<HTMLInputElement>(null);
const iconDarkInputRef = useRef<HTMLInputElement>(null);
const [logoPreview, setLogoPreview] = useState<string | null>(
setting?.logo_url || null,
const [logoLightPreview, setLogoLightPreview] = useState<string | null>(
setting?.logo_light_url || null,
);
const [iconPreview, setIconPreview] = useState<string | null>(
setting?.icon_url || null,
const [logoDarkPreview, setLogoDarkPreview] = useState<string | null>(
setting?.logo_dark_url || null,
);
const [iconLightPreview, setIconLightPreview] = useState<string | null>(
setting?.icon_light_url || null,
);
const [iconDarkPreview, setIconDarkPreview] = useState<string | null>(
setting?.icon_dark_url || null,
);
const handleLogoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleFileChange = (
e: React.ChangeEvent<HTMLInputElement>,
field: 'logo_light' | 'logo_dark' | 'icon_light' | 'icon_dark',
setPreview: React.Dispatch<React.SetStateAction<string | null>>,
) => {
const file = e.target.files?.[0];
if (!file) {
return;
}
setData('logo', file);
setData(field, file);
const reader = new FileReader();
reader.onloadend = () => setLogoPreview(reader.result as string);
reader.onloadend = () => setPreview(reader.result as string);
reader.readAsDataURL(file);
};
const removeLogo = () => {
setData('logo', null);
setLogoPreview(null);
const removeFile = (
field: 'logo_light' | 'logo_dark' | 'icon_light' | 'icon_dark',
setPreview: React.Dispatch<React.SetStateAction<string | null>>,
inputRef: React.RefObject<HTMLInputElement | null>,
) => {
setData(field, null);
setPreview(null);
if (logoInputRef.current) {
logoInputRef.current.value = '';
}
};
const handleIconChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) {
return;
}
setData('icon', file);
const reader = new FileReader();
reader.onloadend = () => setIconPreview(reader.result as string);
reader.readAsDataURL(file);
};
const removeIcon = () => {
setData('icon', null);
setIconPreview(null);
if (iconInputRef.current) {
iconInputRef.current.value = '';
if (inputRef.current) {
inputRef.current.value = '';
}
};
@ -203,51 +199,118 @@ export default function SettingIndex({
<CardHeader className="border-b">
<CardTitle>Logo Aplikasi</CardTitle>
</CardHeader>
<CardContent>
{logoPreview ? (
<div className="relative inline-block w-full">
<img
src={logoPreview}
alt="Logo preview"
className="w-full rounded-xl border object-cover shadow"
/>
<CardContent className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="text-xs">
Logo Terang
</Label>
{logoLightPreview ? (
<div className="relative h-20 w-full overflow-hidden rounded-xl border bg-white shadow-sm">
<img
src={logoLightPreview}
alt="Logo Light preview"
className="h-full w-full object-contain p-2"
/>
<button
type="button"
onClick={() =>
removeFile(
'logo_light',
setLogoLightPreview,
logoLightInputRef,
)
}
className="absolute top-1 right-1 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white shadow-md transition-colors hover:bg-red-600"
>
<X className="h-3.5 w-3.5" />
</button>
</div>
) : (
<button
type="button"
onClick={removeLogo}
className="absolute -top-2 -right-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white shadow-md transition-colors hover:bg-red-600"
onClick={() =>
logoLightInputRef.current?.click()
}
className="group flex h-20 w-full cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed border-border transition-all hover:border-primary hover:bg-primary/5"
>
<X className="h-3.5 w-3.5" />
<ImagePlus className="h-5 w-5 text-muted-foreground transition-colors group-hover:text-primary" />
</button>
</div>
) : (
<button
type="button"
onClick={() =>
logoInputRef.current?.click()
)}
<input
ref={logoLightInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={(e) =>
handleFileChange(
e,
'logo_light',
setLogoLightPreview,
)
}
className="group flex h-40 w-full cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed border-border transition-all hover:border-primary hover:bg-primary/5"
>
<ImagePlus className="mb-2 h-10 w-10 text-muted-foreground transition-colors group-hover:text-primary" />
<span className="text-sm font-medium text-muted-foreground transition-colors group-hover:text-primary">
Klik untuk upload logo
</span>
<span className="mt-1 text-xs text-muted-foreground">
PNG, JPG, WEBP maks. 2MB
</span>
</button>
)}
<input
ref={logoInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleLogoChange}
/>
<FieldError
error={errors.logo}
label="Logo Aplikasi"
className="mt-2 text-xs"
/>
/>
<FieldError
error={errors.logo_light}
label="Logo Terang"
className="mt-1 text-[10px]"
/>
</div>
<div className="space-y-2">
<Label className="text-xs">
Logo Gelap
</Label>
{logoDarkPreview ? (
<div className="relative h-20 w-full overflow-hidden rounded-xl border bg-slate-900 shadow-sm">
<img
src={logoDarkPreview}
alt="Logo Dark preview"
className="h-full w-full object-contain p-2"
/>
<button
type="button"
onClick={() =>
removeFile(
'logo_dark',
setLogoDarkPreview,
logoDarkInputRef,
)
}
className="absolute top-1 right-1 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white shadow-md transition-colors hover:bg-red-600"
>
<X className="h-3.5 w-3.5" />
</button>
</div>
) : (
<button
type="button"
onClick={() =>
logoDarkInputRef.current?.click()
}
className="group flex h-20 w-full cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed border-border transition-all hover:border-primary hover:bg-primary/5"
>
<ImagePlus className="h-5 w-5 text-muted-foreground transition-colors group-hover:text-primary" />
</button>
)}
<input
ref={logoDarkInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={(e) =>
handleFileChange(
e,
'logo_dark',
setLogoDarkPreview,
)
}
/>
<FieldError
error={errors.logo_dark}
label="Logo Gelap"
className="mt-1 text-[10px]"
/>
</div>
</CardContent>
</Card>
@ -255,53 +318,118 @@ export default function SettingIndex({
<CardHeader className="border-b">
<CardTitle>Favicon / Icon</CardTitle>
</CardHeader>
<CardContent>
{iconPreview ? (
<div className="relative flex inline-block w-full justify-center">
<div className="relative">
<CardContent className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="text-xs">
Icon Terang
</Label>
{iconLightPreview ? (
<div className="relative h-20 w-full overflow-hidden rounded-xl border bg-white shadow-sm">
<img
src={iconPreview}
alt="Icon preview"
className="size-24 rounded-xl border object-contain p-2 shadow"
src={iconLightPreview}
alt="Icon Light preview"
className="h-full w-full object-contain p-3"
/>
<button
type="button"
onClick={removeIcon}
className="absolute -top-2 -right-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white shadow-md transition-colors hover:bg-red-600"
onClick={() =>
removeFile(
'icon_light',
setIconLightPreview,
iconLightInputRef,
)
}
className="absolute top-1 right-1 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white shadow-md transition-colors hover:bg-red-600"
>
<X className="h-3.5 w-3.5" />
</button>
</div>
</div>
) : (
<button
type="button"
onClick={() =>
iconInputRef.current?.click()
) : (
<button
type="button"
onClick={() =>
iconLightInputRef.current?.click()
}
className="group flex h-20 w-full cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed border-border transition-all hover:border-primary hover:bg-primary/5"
>
<ImagePlus className="h-5 w-5 text-muted-foreground transition-colors group-hover:text-primary" />
</button>
)}
<input
ref={iconLightInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={(e) =>
handleFileChange(
e,
'icon_light',
setIconLightPreview,
)
}
className="group flex h-32 w-full cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed border-border transition-all hover:border-primary hover:bg-primary/5"
>
<ImagePlus className="mb-2 h-8 w-8 text-muted-foreground transition-colors group-hover:text-primary" />
<span className="text-sm font-medium text-muted-foreground transition-colors group-hover:text-primary">
Klik untuk upload icon
</span>
<span className="mt-1 text-xs text-muted-foreground">
ICO, PNG maks. 1MB
</span>
</button>
)}
<input
ref={iconInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleIconChange}
/>
<FieldError
error={errors.icon}
label="Favicon / Icon"
className="mt-2 text-xs"
/>
/>
<FieldError
error={errors.icon_light}
label="Icon Terang"
className="mt-1 text-[10px]"
/>
</div>
<div className="space-y-2">
<Label className="text-xs">
Icon Gelap
</Label>
{iconDarkPreview ? (
<div className="relative h-20 w-full overflow-hidden rounded-xl border bg-slate-900 shadow-sm">
<img
src={iconDarkPreview}
alt="Icon Dark preview"
className="h-full w-full object-contain p-3"
/>
<button
type="button"
onClick={() =>
removeFile(
'icon_dark',
setIconDarkPreview,
iconDarkInputRef,
)
}
className="absolute top-1 right-1 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white shadow-md transition-colors hover:bg-red-600"
>
<X className="h-3.5 w-3.5" />
</button>
</div>
) : (
<button
type="button"
onClick={() =>
iconDarkInputRef.current?.click()
}
className="group flex h-20 w-full cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed border-border transition-all hover:border-primary hover:bg-primary/5"
>
<ImagePlus className="h-5 w-5 text-muted-foreground transition-colors group-hover:text-primary" />
</button>
)}
<input
ref={iconDarkInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={(e) =>
handleFileChange(
e,
'icon_dark',
setIconDarkPreview,
)
}
/>
<FieldError
error={errors.icon_dark}
label="Icon Gelap"
className="mt-1 text-[10px]"
/>
</div>
</CardContent>
</Card>
</div>

View File

@ -138,7 +138,15 @@ export default function Homepage({ categories = [], bestSellers, topSellingProdu
}`}>
<div className="mx-auto flex max-w-7xl items-center justify-between px-6 lg:px-12">
<div className="flex items-center gap-2 text-xl font-bold uppercase tracking-tighter">
VN Grup
{setting?.logo_light_url ? (
<img
src={setting.logo_light_url}
alt={setting.name}
className="max-h-8 w-auto"
/>
) : (
setting?.name || 'VN Grup'
)}
</div>
<nav className="hidden items-center gap-10 text-[10px] font-bold uppercase tracking-[0.2em] md:flex">
@ -326,7 +334,7 @@ export default function Homepage({ categories = [], bestSellers, topSellingProdu
<p className="mb-4 text-sm font-medium opacity-60">{formatCurrency(item.retail_price)}</p>
<a
href={`https://wa.me/628123456789?text=${encodeURIComponent(`Halo VN Grup, saya ingin memesan ${item.name} seharga ${formatCurrency(item.retail_price)}. Mohon info detail pembayarannya.`)}`}
href={`https://wa.me/628123456789?text=${encodeURIComponent(`Halo ${setting?.name || 'VN Grup'}, saya ingin memesan ${item.name} seharga ${formatCurrency(item.retail_price)}. Mohon info detail pembayarannya.`)}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 border border-[#1e4a6d] px-6 py-2 text-[8px] font-bold uppercase tracking-widest transition-all hover:bg-[#1e4a6d] hover:text-white"
@ -404,7 +412,7 @@ export default function Homepage({ categories = [], bestSellers, topSellingProdu
{/* Brand Info */}
<div className="lg:col-span-6">
<div className="mb-6 flex items-center gap-2 text-xl font-bold uppercase tracking-tighter">
{setting?.logo_url ? <img src={setting.logo_url} alt={setting.name} className="max-h-8" /> : (setting?.name || 'VN Grup')}
{setting?.logo_light_url ? <img src={setting.logo_light_url} alt={setting.name} className="max-h-8" /> : (setting?.name || 'VN Grup')}
</div>
<p className="mb-8 text-sm leading-relaxed opacity-60">
{setting?.description || 'Menghadirkan produk esensial yang tak lekang oleh waktu dengan fokus pada kualitas, keberlanjutan, dan gaya yang abadi.'}
@ -556,7 +564,7 @@ export default function Homepage({ categories = [], bestSellers, topSellingProdu
<div className="mt-auto pt-8 border-t border-[#1e4a6d]/10">
<a
href={`https://wa.me/628123456789?text=${encodeURIComponent(`Halo VN Grup, saya tertarik dengan produk ${selectedProduct?.name} seharga ${formatCurrency(selectedProduct?.retail_price || 0)}. Bisa tanya-tanya dulu?`)}`}
href={`https://wa.me/628123456789?text=${encodeURIComponent(`Halo ${setting?.name || 'VN Grup'}, saya tertarik dengan produk ${selectedProduct?.name} seharga ${formatCurrency(selectedProduct?.retail_price || 0)}. Bisa tanya-tanya dulu?`)}`}
target="_blank"
rel="noopener noreferrer"
className="w-full bg-[#1e4a6d] py-4 text-[10px] font-bold uppercase tracking-[0.2em] text-white transition-all hover:bg-[#15344e] flex items-center justify-center gap-2"

View File

@ -4,8 +4,10 @@ export interface GeneralSetting {
description: string | null;
address: string | null;
phone: string | null;
logo_url: string | null;
icon_url: string | null;
logo_light_url: string | null;
logo_dark_url: string | null;
icon_light_url: string | null;
icon_dark_url: string | null;
created_at: string;
updated_at: string;
}

View File

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" @class(['dark' => ($appearance ?? 'system') == 'dark'])>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@ -7,7 +8,7 @@
{{-- Inline script to detect system dark mode preference and apply it immediately --}}
<script>
(function() {
const appearance = '{{ $appearance ?? "system" }}';
const appearance = '{{ $appearance ?? 'system' }}';
if (appearance === 'system') {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
@ -30,9 +31,15 @@
}
</style>
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
@php
$setting = \App\Models\GeneralSetting::first();
@endphp
@if ($setting && $setting->icon_light_url)
<link rel="icon" href="{{ $setting->icon_light_url }}" sizes="any">
@else
<link rel="icon" href="/favicon.ico" sizes="any">
@endif
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet" />
@ -40,10 +47,12 @@
@viteReactRefresh
@vite(['resources/css/app.css', 'resources/js/app.tsx', "resources/js/pages/{$page['component']}.tsx"])
<x-inertia::head>
<title>{{ config('app.name', 'Laravel') }}</title>
<title>{{ config('app.name', 'VN Grup') }}</title>
</x-inertia::head>
</head>
<body class="font-sans antialiased">
<x-inertia::app />
</body>
</html>

View File

@ -46,7 +46,7 @@
);
});
it('can update general settings with logo and icon', function () {
it('can update general settings with logo and icon variants', function () {
Storage::fake('public');
// Ensure a setting exists first for testing update logic in Controller
@ -62,8 +62,10 @@
'description' => 'Toko baju premium',
'address' => 'Jakarta, Indonesia',
'phone' => '081234567890',
'logo' => UploadedFile::fake()->image('logo.png'),
'icon' => UploadedFile::fake()->image('icon.png'),
'logo_light' => UploadedFile::fake()->image('logo_light.png'),
'logo_dark' => UploadedFile::fake()->image('logo_dark.png'),
'icon_light' => UploadedFile::fake()->image('icon_light.png'),
'icon_dark' => UploadedFile::fake()->image('icon_dark.png'),
];
postJson(route('system.settings.update'), $data)
@ -77,17 +79,19 @@
]);
$setting->refresh();
expect($setting->getFirstMediaUrl('logo'))->not->toBeEmpty();
expect($setting->getFirstMediaUrl('icon'))->not->toBeEmpty();
expect($setting->getFirstMediaUrl('logo_light'))->not->toBeEmpty();
expect($setting->getFirstMediaUrl('logo_dark'))->not->toBeEmpty();
expect($setting->getFirstMediaUrl('icon_light'))->not->toBeEmpty();
expect($setting->getFirstMediaUrl('icon_dark'))->not->toBeEmpty();
});
it('validates settings update', function () {
// If setting doesn't exist, logo and icon are required
it('validates settings update with new variants', function () {
// If setting doesn't exist, logo_light and icon_light are required
GeneralSetting::truncate();
postJson(route('system.settings.update'), [])
->assertStatus(422)
->assertJsonValidationErrors(['name', 'description', 'address', 'phone', 'logo', 'icon']);
->assertJsonValidationErrors(['name', 'description', 'address', 'phone', 'logo_light', 'icon_light']);
});
});