Commit 379cefca authored by Mahmoud Aglan's avatar Mahmoud Aglan

Website Builder V2 Phase A: theme engine, colors, typography, spacing, shapes

- Add 8 template presets with one-click color scheme application
- Expand color palette from 4 to 8 (add background, surface, border, muted)
- Add auto-palette generation from accent color (dark/light detection)
- Add typography controls: font size scale, heading weight, line height, letter spacing
- Add density control (compact/comfortable/spacious) affecting section padding and card gaps
- Add border radius presets (sharp/subtle/rounded/pill)
- Add card styles (flat/raised/bordered/glass)
- Add section dividers (none/line/wave/angle/curve) rendered as SVG between sections
- Add image shape control (square/rounded/circle/blob)
- Add animation toggle and speed control
- Add per-section: background type (solid/gradient/image/video), custom animation, padding override, mobile visibility toggle
- All new columns have defaults — existing sites render unchanged until admin modifies settings
- CSS variables drive all theming; public layout emits full token set
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 4343122d
...@@ -22,6 +22,17 @@ class WebsiteSection extends Model ...@@ -22,6 +22,17 @@ class WebsiteSection extends Model
'settings', 'settings',
'sort_order', 'sort_order',
'is_enabled', 'is_enabled',
'bg_type',
'bg_color',
'bg_gradient_from',
'bg_gradient_to',
'bg_gradient_angle',
'bg_image_path',
'bg_overlay_opacity',
'bg_video_url',
'animation',
'padding_y',
'hidden_on_mobile',
]; ];
protected $casts = [ protected $casts = [
...@@ -29,6 +40,9 @@ class WebsiteSection extends Model ...@@ -29,6 +40,9 @@ class WebsiteSection extends Model
'settings' => 'array', 'settings' => 'array',
'sort_order' => 'integer', 'sort_order' => 'integer',
'is_enabled' => 'boolean', 'is_enabled' => 'boolean',
'bg_gradient_angle' => 'integer',
'bg_overlay_opacity' => 'integer',
'hidden_on_mobile' => 'boolean',
]; ];
public function getSetting(string $key, $default = null) public function getSetting(string $key, $default = null)
......
...@@ -16,8 +16,23 @@ class WebsiteSetting extends Model ...@@ -16,8 +16,23 @@ class WebsiteSetting extends Model
'secondary_color', 'secondary_color',
'accent_color', 'accent_color',
'text_color', 'text_color',
'background_color',
'surface_color',
'border_color',
'muted_text_color',
'heading_font', 'heading_font',
'body_font', 'body_font',
'font_size_scale',
'heading_weight',
'line_height',
'letter_spacing',
'density',
'border_radius_preset',
'card_style',
'section_divider',
'image_shape',
'animations_enabled',
'animation_speed',
'navbar_style', 'navbar_style',
'site_title', 'site_title',
'site_title_en', 'site_title_en',
...@@ -35,6 +50,7 @@ class WebsiteSetting extends Model ...@@ -35,6 +50,7 @@ class WebsiteSetting extends Model
protected $casts = [ protected $casts = [
'social_links' => 'array', 'social_links' => 'array',
'is_published' => 'boolean', 'is_published' => 'boolean',
'animations_enabled' => 'boolean',
'published_at' => 'datetime', 'published_at' => 'datetime',
]; ];
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace App\Livewire\Website; namespace App\Livewire\Website;
use App\Domain\Website\Services\WebsiteCacheService;
use App\Domain\Website\Services\WebsiteSettingService; use App\Domain\Website\Services\WebsiteSettingService;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
...@@ -11,13 +12,42 @@ ...@@ -11,13 +12,42 @@
#[Title('تصميم الموقع')] #[Title('تصميم الموقع')]
class ThemeEditor extends Component class ThemeEditor extends Component
{ {
// Template
public string $template = 'bold_athletic';
// Colors
public string $primary_color = '#1a1a2e'; public string $primary_color = '#1a1a2e';
public string $secondary_color = '#16213e'; public string $secondary_color = '#16213e';
public string $accent_color = '#e94560'; public string $accent_color = '#e94560';
public string $text_color = '#ffffff'; public string $text_color = '#ffffff';
public string $background_color = '#ffffff';
public string $surface_color = '#f8fafc';
public string $border_color = '#e2e8f0';
public string $muted_text_color = '#64748b';
// Typography
public string $heading_font = 'Cairo'; public string $heading_font = 'Cairo';
public string $body_font = 'Cairo'; public string $body_font = 'Cairo';
public string $font_size_scale = 'medium';
public string $heading_weight = '700';
public string $line_height = 'normal';
public string $letter_spacing = 'normal';
// Spacing & Shapes
public string $density = 'comfortable';
public string $border_radius_preset = 'rounded';
public string $card_style = 'raised';
public string $section_divider = 'none';
public string $image_shape = 'rounded';
// Animation
public bool $animations_enabled = true;
public string $animation_speed = 'normal';
// Navbar
public string $navbar_style = 'transparent'; public string $navbar_style = 'transparent';
// General
public string $site_title = ''; public string $site_title = '';
public string $site_title_en = ''; public string $site_title_en = '';
public string $site_description = ''; public string $site_description = '';
...@@ -30,7 +60,19 @@ class ThemeEditor extends Component ...@@ -30,7 +60,19 @@ class ThemeEditor extends Component
public array $availableFonts = [ public array $availableFonts = [
'Cairo', 'Tajawal', 'IBM Plex Sans Arabic', 'Noto Sans Arabic', 'Cairo', 'Tajawal', 'IBM Plex Sans Arabic', 'Noto Sans Arabic',
'Almarai', 'Readex Pro', 'Rubik', 'El Messiri', 'Almarai', 'Readex Pro', 'Rubik', 'El Messiri', 'Changa',
'Noto Kufi Arabic', 'Alexandria', 'Baloo Bhaijaan 2',
];
public array $templatePresets = [
'bold_athletic' => ['label' => 'رياضي جريء', 'primary' => '#1a1a2e', 'secondary' => '#16213e', 'accent' => '#e94560', 'text' => '#ffffff', 'bg' => '#0f0f1a', 'surface' => '#1a1a2e', 'border' => '#2d2d4a', 'muted' => '#94a3b8'],
'clean_professional' => ['label' => 'احترافي نظيف', 'primary' => '#ffffff', 'secondary' => '#f8fafc', 'accent' => '#2563eb', 'text' => '#1f2937', 'bg' => '#ffffff', 'surface' => '#f9fafb', 'border' => '#e5e7eb', 'muted' => '#6b7280'],
'vibrant_playful' => ['label' => 'حيوي ومرح', 'primary' => '#fef3c7', 'secondary' => '#ecfccb', 'accent' => '#f59e0b', 'text' => '#1c1917', 'bg' => '#fffbeb', 'surface' => '#fef9c3', 'border' => '#fde68a', 'muted' => '#78716c'],
'dark_sport' => ['label' => 'رياضي داكن', 'primary' => '#0a0a0a', 'secondary' => '#1c1c1c', 'accent' => '#22c55e', 'text' => '#ffffff', 'bg' => '#000000', 'surface' => '#111111', 'border' => '#2a2a2a', 'muted' => '#a1a1aa'],
'minimal_corporate' => ['label' => 'مؤسسي بسيط', 'primary' => '#f9fafb', 'secondary' => '#ffffff', 'accent' => '#6366f1', 'text' => '#111827', 'bg' => '#ffffff', 'surface' => '#f3f4f6', 'border' => '#d1d5db', 'muted' => '#9ca3af'],
'gradient_modern' => ['label' => 'عصري متدرج', 'primary' => '#1e1b4b', 'secondary' => '#312e81', 'accent' => '#a78bfa', 'text' => '#ffffff', 'bg' => '#0f0b2e', 'surface' => '#1e1b4b', 'border' => '#3730a3', 'muted' => '#a5b4fc'],
'neon_energy' => ['label' => 'نيون حيوي', 'primary' => '#18181b', 'secondary' => '#27272a', 'accent' => '#facc15', 'text' => '#ffffff', 'bg' => '#09090b', 'surface' => '#18181b', 'border' => '#3f3f46', 'muted' => '#a1a1aa'],
'earth_natural' => ['label' => 'طبيعي دافئ', 'primary' => '#fefce8', 'secondary' => '#f0fdf4', 'accent' => '#65a30d', 'text' => '#1c1917', 'bg' => '#fafaf9', 'surface' => '#f5f5f4', 'border' => '#d6d3d1', 'muted' => '#78716c'],
]; ];
public function mount(): void public function mount(): void
...@@ -39,12 +81,28 @@ public function mount(): void ...@@ -39,12 +81,28 @@ public function mount(): void
$settings = app(WebsiteSettingService::class)->getOrCreate(app('current_academy')); $settings = app(WebsiteSettingService::class)->getOrCreate(app('current_academy'));
$this->template = $settings->template ?? 'bold_athletic';
$this->primary_color = $settings->primary_color; $this->primary_color = $settings->primary_color;
$this->secondary_color = $settings->secondary_color; $this->secondary_color = $settings->secondary_color;
$this->accent_color = $settings->accent_color; $this->accent_color = $settings->accent_color;
$this->text_color = $settings->text_color; $this->text_color = $settings->text_color;
$this->background_color = $settings->background_color ?? '#ffffff';
$this->surface_color = $settings->surface_color ?? '#f8fafc';
$this->border_color = $settings->border_color ?? '#e2e8f0';
$this->muted_text_color = $settings->muted_text_color ?? '#64748b';
$this->heading_font = $settings->heading_font; $this->heading_font = $settings->heading_font;
$this->body_font = $settings->body_font; $this->body_font = $settings->body_font;
$this->font_size_scale = $settings->font_size_scale ?? 'medium';
$this->heading_weight = $settings->heading_weight ?? '700';
$this->line_height = $settings->line_height ?? 'normal';
$this->letter_spacing = $settings->letter_spacing ?? 'normal';
$this->density = $settings->density ?? 'comfortable';
$this->border_radius_preset = $settings->border_radius_preset ?? 'rounded';
$this->card_style = $settings->card_style ?? 'raised';
$this->section_divider = $settings->section_divider ?? 'none';
$this->image_shape = $settings->image_shape ?? 'rounded';
$this->animations_enabled = $settings->animations_enabled ?? true;
$this->animation_speed = $settings->animation_speed ?? 'normal';
$this->navbar_style = $settings->navbar_style; $this->navbar_style = $settings->navbar_style;
$this->site_title = $settings->site_title ?? ''; $this->site_title = $settings->site_title ?? '';
$this->site_title_en = $settings->site_title_en ?? ''; $this->site_title_en = $settings->site_title_en ?? '';
...@@ -57,15 +115,78 @@ public function mount(): void ...@@ -57,15 +115,78 @@ public function mount(): void
$this->facebook_pixel_id = $settings->facebook_pixel_id ?? ''; $this->facebook_pixel_id = $settings->facebook_pixel_id ?? '';
} }
public function applyTemplate(string $template): void
{
if (!isset($this->templatePresets[$template])) {
return;
}
$preset = $this->templatePresets[$template];
$this->template = $template;
$this->primary_color = $preset['primary'];
$this->secondary_color = $preset['secondary'];
$this->accent_color = $preset['accent'];
$this->text_color = $preset['text'];
$this->background_color = $preset['bg'];
$this->surface_color = $preset['surface'];
$this->border_color = $preset['border'];
$this->muted_text_color = $preset['muted'];
}
public function generatePaletteFromAccent(): void
{
$hex = ltrim($this->accent_color, '#');
$r = hexdec(substr($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
[$h, $s, $l] = $this->rgbToHsl($r, $g, $b);
$isDark = $l < 0.5;
if ($isDark) {
$this->background_color = '#ffffff';
$this->surface_color = '#f9fafb';
$this->text_color = '#111827';
$this->muted_text_color = '#6b7280';
$this->border_color = '#e5e7eb';
$this->primary_color = $this->hslToHex($h, max(0.1, $s * 0.3), 0.95);
$this->secondary_color = $this->hslToHex($h, max(0.05, $s * 0.2), 0.97);
} else {
$this->background_color = $this->hslToHex($h, max(0.05, $s * 0.4), 0.06);
$this->surface_color = $this->hslToHex($h, max(0.08, $s * 0.3), 0.10);
$this->text_color = '#ffffff';
$this->muted_text_color = $this->hslToHex($h, 0.1, 0.65);
$this->border_color = $this->hslToHex($h, 0.1, 0.20);
$this->primary_color = $this->hslToHex($h, max(0.1, $s * 0.4), 0.12);
$this->secondary_color = $this->hslToHex($h, max(0.1, $s * 0.35), 0.15);
}
}
public function save(): void public function save(): void
{ {
$this->validate([ $this->validate([
'template' => 'required|in:bold_athletic,clean_professional,vibrant_playful,dark_sport,minimal_corporate,gradient_modern,neon_energy,earth_natural',
'primary_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/', 'primary_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/',
'secondary_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/', 'secondary_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/',
'accent_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/', 'accent_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/',
'text_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/', 'text_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/',
'background_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/',
'surface_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/',
'border_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/',
'muted_text_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/',
'heading_font' => 'required|string|max:50', 'heading_font' => 'required|string|max:50',
'body_font' => 'required|string|max:50', 'body_font' => 'required|string|max:50',
'font_size_scale' => 'required|in:small,medium,large,xlarge',
'heading_weight' => 'required|in:400,500,600,700,800,900',
'line_height' => 'required|in:tight,normal,relaxed,loose',
'letter_spacing' => 'required|in:tight,normal,wide',
'density' => 'required|in:compact,comfortable,spacious',
'border_radius_preset' => 'required|in:sharp,subtle,rounded,pill',
'card_style' => 'required|in:flat,raised,bordered,glass',
'section_divider' => 'required|in:none,line,wave,angle,curve',
'image_shape' => 'required|in:square,rounded,circle,blob',
'animations_enabled' => 'boolean',
'animation_speed' => 'required|in:slow,normal,fast',
'navbar_style' => 'required|in:solid,transparent,floating', 'navbar_style' => 'required|in:solid,transparent,floating',
'site_title' => 'nullable|string|max:255', 'site_title' => 'nullable|string|max:255',
'site_description' => 'nullable|string|max:1000', 'site_description' => 'nullable|string|max:1000',
...@@ -73,12 +194,28 @@ public function save(): void ...@@ -73,12 +194,28 @@ public function save(): void
]); ]);
app(WebsiteSettingService::class)->update(app('current_academy'), [ app(WebsiteSettingService::class)->update(app('current_academy'), [
'template' => $this->template,
'primary_color' => $this->primary_color, 'primary_color' => $this->primary_color,
'secondary_color' => $this->secondary_color, 'secondary_color' => $this->secondary_color,
'accent_color' => $this->accent_color, 'accent_color' => $this->accent_color,
'text_color' => $this->text_color, 'text_color' => $this->text_color,
'background_color' => $this->background_color,
'surface_color' => $this->surface_color,
'border_color' => $this->border_color,
'muted_text_color' => $this->muted_text_color,
'heading_font' => $this->heading_font, 'heading_font' => $this->heading_font,
'body_font' => $this->body_font, 'body_font' => $this->body_font,
'font_size_scale' => $this->font_size_scale,
'heading_weight' => $this->heading_weight,
'line_height' => $this->line_height,
'letter_spacing' => $this->letter_spacing,
'density' => $this->density,
'border_radius_preset' => $this->border_radius_preset,
'card_style' => $this->card_style,
'section_divider' => $this->section_divider,
'image_shape' => $this->image_shape,
'animations_enabled' => $this->animations_enabled,
'animation_speed' => $this->animation_speed,
'navbar_style' => $this->navbar_style, 'navbar_style' => $this->navbar_style,
'site_title' => $this->site_title, 'site_title' => $this->site_title,
'site_title_en' => $this->site_title_en, 'site_title_en' => $this->site_title_en,
...@@ -91,9 +228,66 @@ public function save(): void ...@@ -91,9 +228,66 @@ public function save(): void
'facebook_pixel_id' => $this->facebook_pixel_id, 'facebook_pixel_id' => $this->facebook_pixel_id,
]); ]);
app(WebsiteCacheService::class)->invalidateAll(app('current_academy'));
session()->flash('success', __('تم حفظ إعدادات التصميم بنجاح')); session()->flash('success', __('تم حفظ إعدادات التصميم بنجاح'));
} }
private function rgbToHsl(int $r, int $g, int $b): array
{
$r /= 255;
$g /= 255;
$b /= 255;
$max = max($r, $g, $b);
$min = min($r, $g, $b);
$l = ($max + $min) / 2;
if ($max === $min) {
return [0, 0, $l];
}
$d = $max - $min;
$s = $l > 0.5 ? $d / (2 - $max - $min) : $d / ($max + $min);
$h = match ($max) {
$r => (($g - $b) / $d + ($g < $b ? 6 : 0)) / 6,
$g => (($b - $r) / $d + 2) / 6,
default => (($r - $g) / $d + 4) / 6,
};
return [$h, $s, $l];
}
private function hslToHex(float $h, float $s, float $l): string
{
$s = max(0, min(1, $s));
$l = max(0, min(1, $l));
if ($s === 0.0) {
$v = (int) round($l * 255);
return sprintf('#%02x%02x%02x', $v, $v, $v);
}
$q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
$p = 2 * $l - $q;
$r = $this->hueToRgb($p, $q, $h + 1 / 3);
$g = $this->hueToRgb($p, $q, $h);
$b = $this->hueToRgb($p, $q, $h - 1 / 3);
return sprintf('#%02x%02x%02x', (int) round($r * 255), (int) round($g * 255), (int) round($b * 255));
}
private function hueToRgb(float $p, float $q, float $t): float
{
if ($t < 0) $t += 1;
if ($t > 1) $t -= 1;
if ($t < 1 / 6) return $p + ($q - $p) * 6 * $t;
if ($t < 1 / 2) return $q;
if ($t < 2 / 3) return $p + ($q - $p) * (2 / 3 - $t) * 6;
return $p;
}
public function render() public function render()
{ {
return view('livewire.website.theme-editor'); return view('livewire.website.theme-editor');
......
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// --- website_settings: new columns for Phase A (template, colors, typography, density, shapes) ---
Schema::table('website_settings', function (Blueprint $table) {
$table->string('background_color', 7)->default('#ffffff')->after('text_color');
$table->string('surface_color', 7)->default('#f8fafc')->after('background_color');
$table->string('border_color', 7)->default('#e2e8f0')->after('surface_color');
$table->string('muted_text_color', 7)->default('#64748b')->after('border_color');
$table->string('font_size_scale', 10)->default('medium')->after('body_font');
$table->string('heading_weight', 10)->default('700')->after('font_size_scale');
$table->string('line_height', 10)->default('normal')->after('heading_weight');
$table->string('letter_spacing', 10)->default('normal')->after('line_height');
$table->string('density', 10)->default('comfortable')->after('letter_spacing');
$table->string('border_radius_preset', 10)->default('rounded')->after('density');
$table->string('card_style', 10)->default('raised')->after('border_radius_preset');
$table->string('section_divider', 10)->default('none')->after('card_style');
$table->string('image_shape', 10)->default('rounded')->after('section_divider');
$table->boolean('animations_enabled')->default(true)->after('image_shape');
$table->string('animation_speed', 10)->default('normal')->after('animations_enabled');
});
// Update template CHECK constraint to add new templates
DB::statement("ALTER TABLE website_settings DROP CONSTRAINT IF EXISTS website_settings_template_check");
DB::statement("ALTER TABLE website_settings ADD CONSTRAINT website_settings_template_check CHECK (template IN ('bold_athletic', 'clean_professional', 'vibrant_playful', 'dark_sport', 'minimal_corporate', 'gradient_modern', 'neon_energy', 'earth_natural'))");
// Add CHECK constraints for new columns
DB::statement("ALTER TABLE website_settings ADD CONSTRAINT website_settings_font_size_scale_check CHECK (font_size_scale IN ('small', 'medium', 'large', 'xlarge'))");
DB::statement("ALTER TABLE website_settings ADD CONSTRAINT website_settings_heading_weight_check CHECK (heading_weight IN ('400', '500', '600', '700', '800', '900'))");
DB::statement("ALTER TABLE website_settings ADD CONSTRAINT website_settings_line_height_check CHECK (line_height IN ('tight', 'normal', 'relaxed', 'loose'))");
DB::statement("ALTER TABLE website_settings ADD CONSTRAINT website_settings_letter_spacing_check CHECK (letter_spacing IN ('tight', 'normal', 'wide'))");
DB::statement("ALTER TABLE website_settings ADD CONSTRAINT website_settings_density_check CHECK (density IN ('compact', 'comfortable', 'spacious'))");
DB::statement("ALTER TABLE website_settings ADD CONSTRAINT website_settings_border_radius_preset_check CHECK (border_radius_preset IN ('sharp', 'subtle', 'rounded', 'pill'))");
DB::statement("ALTER TABLE website_settings ADD CONSTRAINT website_settings_card_style_check CHECK (card_style IN ('flat', 'raised', 'bordered', 'glass'))");
DB::statement("ALTER TABLE website_settings ADD CONSTRAINT website_settings_section_divider_check CHECK (section_divider IN ('none', 'line', 'wave', 'angle', 'curve'))");
DB::statement("ALTER TABLE website_settings ADD CONSTRAINT website_settings_image_shape_check CHECK (image_shape IN ('square', 'rounded', 'circle', 'blob'))");
DB::statement("ALTER TABLE website_settings ADD CONSTRAINT website_settings_animation_speed_check CHECK (animation_speed IN ('slow', 'normal', 'fast'))");
// --- website_sections: per-section background + animation + padding ---
Schema::table('website_sections', function (Blueprint $table) {
$table->string('bg_type', 20)->default('inherit')->after('is_enabled');
$table->string('bg_color', 7)->nullable()->after('bg_type');
$table->string('bg_gradient_from', 7)->nullable()->after('bg_color');
$table->string('bg_gradient_to', 7)->nullable()->after('bg_gradient_from');
$table->smallInteger('bg_gradient_angle')->default(180)->after('bg_gradient_to');
$table->string('bg_image_path', 500)->nullable()->after('bg_gradient_angle');
$table->smallInteger('bg_overlay_opacity')->default(60)->after('bg_image_path');
$table->string('bg_video_url', 500)->nullable()->after('bg_overlay_opacity');
$table->string('animation', 20)->default('fade-up')->after('bg_video_url');
$table->string('padding_y', 10)->default('inherit')->after('animation');
$table->boolean('hidden_on_mobile')->default(false)->after('padding_y');
});
DB::statement("ALTER TABLE website_sections ADD CONSTRAINT website_sections_bg_type_check CHECK (bg_type IN ('inherit', 'solid', 'gradient', 'image', 'video'))");
DB::statement("ALTER TABLE website_sections ADD CONSTRAINT website_sections_animation_check CHECK (animation IN ('none', 'fade-up', 'fade-down', 'fade-left', 'fade-right', 'zoom-in', 'flip', 'bounce'))");
DB::statement("ALTER TABLE website_sections ADD CONSTRAINT website_sections_padding_y_check CHECK (padding_y IN ('inherit', 'none', 'small', 'medium', 'large', 'xlarge'))");
}
public function down(): void
{
DB::statement("ALTER TABLE website_settings DROP CONSTRAINT IF EXISTS website_settings_font_size_scale_check");
DB::statement("ALTER TABLE website_settings DROP CONSTRAINT IF EXISTS website_settings_heading_weight_check");
DB::statement("ALTER TABLE website_settings DROP CONSTRAINT IF EXISTS website_settings_line_height_check");
DB::statement("ALTER TABLE website_settings DROP CONSTRAINT IF EXISTS website_settings_letter_spacing_check");
DB::statement("ALTER TABLE website_settings DROP CONSTRAINT IF EXISTS website_settings_density_check");
DB::statement("ALTER TABLE website_settings DROP CONSTRAINT IF EXISTS website_settings_border_radius_preset_check");
DB::statement("ALTER TABLE website_settings DROP CONSTRAINT IF EXISTS website_settings_card_style_check");
DB::statement("ALTER TABLE website_settings DROP CONSTRAINT IF EXISTS website_settings_section_divider_check");
DB::statement("ALTER TABLE website_settings DROP CONSTRAINT IF EXISTS website_settings_image_shape_check");
DB::statement("ALTER TABLE website_settings DROP CONSTRAINT IF EXISTS website_settings_animation_speed_check");
Schema::table('website_settings', function (Blueprint $table) {
$table->dropColumn([
'background_color', 'surface_color', 'border_color', 'muted_text_color',
'font_size_scale', 'heading_weight', 'line_height', 'letter_spacing',
'density', 'border_radius_preset', 'card_style', 'section_divider', 'image_shape',
'animations_enabled', 'animation_speed',
]);
});
DB::statement("ALTER TABLE website_settings DROP CONSTRAINT IF EXISTS website_settings_template_check");
DB::statement("ALTER TABLE website_settings ADD CONSTRAINT website_settings_template_check CHECK (template IN ('bold_athletic', 'clean_professional', 'vibrant_playful'))");
DB::statement("ALTER TABLE website_sections DROP CONSTRAINT IF EXISTS website_sections_bg_type_check");
DB::statement("ALTER TABLE website_sections DROP CONSTRAINT IF EXISTS website_sections_animation_check");
DB::statement("ALTER TABLE website_sections DROP CONSTRAINT IF EXISTS website_sections_padding_y_check");
Schema::table('website_sections', function (Blueprint $table) {
$table->dropColumn([
'bg_type', 'bg_color', 'bg_gradient_from', 'bg_gradient_to',
'bg_gradient_angle', 'bg_image_path', 'bg_overlay_opacity', 'bg_video_url',
'animation', 'padding_y', 'hidden_on_mobile',
]);
});
}
};
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div class="flex items-center justify-between mb-6"> <div class="flex items-center justify-between mb-6">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">تصميم الموقع</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">تصميم الموقع</h1>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">تخصيص الألوان والخطوط والروابط الاجتماعية</p> <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">تخصيص القالب والألوان والخطوط والتأثيرات</p>
</div> </div>
<a href="{{ route('website.manage.sections') }}" class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"> <a href="{{ route('website.manage.sections') }}" class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
← إدارة الأقسام ← إدارة الأقسام
...@@ -16,137 +16,283 @@ ...@@ -16,137 +16,283 @@
@endif @endif
<form wire:submit="save" class="space-y-6"> <form wire:submit="save" class="space-y-6">
{{-- Template Presets --}}
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">القالب</h3>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">اختر قالبًا جاهزًا كنقطة بداية — يمكنك تعديل الألوان بعدها</p>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
@foreach($templatePresets as $key => $preset)
<button type="button" wire:click="applyTemplate('{{ $key }}')"
class="relative p-3 rounded-xl border-2 transition-all text-start {{ $template === $key ? 'border-blue-500 ring-2 ring-blue-200' : 'border-gray-200 dark:border-gray-600 hover:border-gray-300' }}">
<div class="flex gap-1 mb-2">
<span class="w-4 h-4 rounded-full" style="background-color: {{ $preset['primary'] }}"></span>
<span class="w-4 h-4 rounded-full" style="background-color: {{ $preset['accent'] }}"></span>
<span class="w-4 h-4 rounded-full" style="background-color: {{ $preset['bg'] }}"></span>
</div>
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">{{ $preset['label'] }}</span>
@if($template === $key)
<span class="absolute top-1.5 end-1.5 w-4 h-4 bg-blue-500 rounded-full flex items-center justify-center">
<svg class="w-2.5 h-2.5 text-white" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
</span>
@endif
</button>
@endforeach
</div>
</div>
{{-- Colors --}} {{-- Colors --}}
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6"> <div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">الألوان</h3> <div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">الألوان</h3>
<button type="button" wire:click="generatePaletteFromAccent" class="text-xs px-3 py-1.5 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors text-gray-700 dark:text-gray-300">
توليد تلقائي من اللون المميز
</button>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4"> <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div> @foreach([
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">اللون الرئيسي</label> ['primary_color', 'اللون الرئيسي'],
<div class="flex items-center gap-2"> ['secondary_color', 'اللون الثانوي'],
<input type="color" wire:model="primary_color" class="w-10 h-10 rounded cursor-pointer border-0 p-0"> ['accent_color', 'اللون المميز'],
<input type="text" wire:model="primary_color" dir="ltr" class="flex-1 text-sm rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white" placeholder="#1e40af"> ['text_color', 'لون النص'],
</div> ['background_color', 'لون الخلفية'],
</div> ['surface_color', 'لون السطح'],
<div> ['border_color', 'لون الحدود'],
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">اللون الثانوي</label> ['muted_text_color', 'نص خافت'],
<div class="flex items-center gap-2"> ] as [$field, $label])
<input type="color" wire:model="secondary_color" class="w-10 h-10 rounded cursor-pointer border-0 p-0"> <div>
<input type="text" wire:model="secondary_color" dir="ltr" class="flex-1 text-sm rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white" placeholder="#f59e0b"> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{ $label }}</label>
</div> <div class="flex items-center gap-2">
</div> <input type="color" wire:model.live="{{ $field }}" class="w-10 h-10 rounded cursor-pointer border-0 p-0">
<div> <input type="text" wire:model.live="{{ $field }}" dir="ltr" class="flex-1 text-sm rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">اللون المميز</label> </div>
<div class="flex items-center gap-2">
<input type="color" wire:model="accent_color" class="w-10 h-10 rounded cursor-pointer border-0 p-0">
<input type="text" wire:model="accent_color" dir="ltr" class="flex-1 text-sm rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white" placeholder="#10b981">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">لون النص</label>
<div class="flex items-center gap-2">
<input type="color" wire:model="text_color" class="w-10 h-10 rounded cursor-pointer border-0 p-0">
<input type="text" wire:model="text_color" dir="ltr" class="flex-1 text-sm rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white" placeholder="#ffffff">
</div> </div>
</div> @endforeach
</div> </div>
{{-- Color Preview --}} {{-- Color Preview --}}
<div class="mt-4 p-4 rounded-lg border border-gray-200 dark:border-gray-600" style="background-color: {{ $secondary_color }}"> <div class="mt-4 p-4 rounded-lg border" style="background-color: {{ $background_color }}; border-color: {{ $border_color }}">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3 flex-wrap">
<span class="px-3 py-1 rounded text-sm font-medium" style="background-color: {{ $primary_color }}; color: {{ $text_color }}">رئيسي</span> <span class="px-3 py-1.5 rounded text-sm font-medium" style="background-color: {{ $primary_color }}; color: {{ $text_color }}">رئيسي</span>
<span class="px-3 py-1 rounded text-sm font-medium" style="background-color: {{ $accent_color }}; color: {{ $text_color }}">مميز</span> <span class="px-3 py-1.5 rounded text-sm font-medium" style="background-color: {{ $secondary_color }}; color: {{ $text_color }}">ثانوي</span>
<span class="px-3 py-1 rounded text-sm font-medium" style="color: {{ $text_color }}; border: 1px solid {{ $text_color }}">نص</span> <span class="px-3 py-1.5 rounded text-sm font-medium" style="background-color: {{ $accent_color }}; color: {{ $text_color }}">مميز</span>
<span class="px-3 py-1.5 rounded text-sm font-medium" style="background-color: {{ $surface_color }}; color: {{ $muted_text_color }}; border: 1px solid {{ $border_color }}">سطح</span>
</div> </div>
</div> </div>
</div> </div>
{{-- Fonts --}} {{-- Typography --}}
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6"> <div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">الخطوط</h3> <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">الخطوط والنصوص</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div> <div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">خط العناوين</label> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">خط العناوين</label>
<select wire:model="heading_font" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"> <select wire:model="heading_font" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white">
<option value="Cairo">Cairo</option> @foreach($availableFonts as $font)
<option value="Tajawal">Tajawal</option> <option value="{{ $font }}">{{ $font }}</option>
<option value="IBM Plex Sans Arabic">IBM Plex Sans Arabic</option> @endforeach
<option value="Noto Sans Arabic">Noto Sans Arabic</option>
<option value="Almarai">Almarai</option>
<option value="Changa">Changa</option>
<option value="El Messiri">El Messiri</option>
</select> </select>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">خط النصوص</label> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">خط النصوص</label>
<select wire:model="body_font" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"> <select wire:model="body_font" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white">
<option value="Cairo">Cairo</option> @foreach($availableFonts as $font)
<option value="Tajawal">Tajawal</option> <option value="{{ $font }}">{{ $font }}</option>
<option value="IBM Plex Sans Arabic">IBM Plex Sans Arabic</option> @endforeach
<option value="Noto Sans Arabic">Noto Sans Arabic</option> </select>
<option value="Almarai">Almarai</option> </div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">حجم الخط</label>
<select wire:model="font_size_scale" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white">
<option value="small">صغير</option>
<option value="medium">متوسط</option>
<option value="large">كبير</option>
<option value="xlarge">كبير جدًا</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">وزن العنوان</label>
<select wire:model="heading_weight" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white">
<option value="400">عادي (400)</option>
<option value="500">متوسط (500)</option>
<option value="600">شبه عريض (600)</option>
<option value="700">عريض (700)</option>
<option value="800">أعرض (800)</option>
<option value="900">أسود (900)</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">ارتفاع السطر</label>
<select wire:model="line_height" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white">
<option value="tight">ضيق</option>
<option value="normal">عادي</option>
<option value="relaxed">مريح</option>
<option value="loose">واسع</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">تباعد الحروف</label>
<select wire:model="letter_spacing" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white">
<option value="tight">ضيق</option>
<option value="normal">عادي</option>
<option value="wide">واسع</option>
</select> </select>
</div> </div>
</div> </div>
</div> </div>
{{-- Navbar Style --}} {{-- Spacing & Density --}}
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6"> <div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">شريط التنقل</h3> <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">المسافات والكثافة</h3>
<div> <div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">نمط الشريط</label> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">كثافة المحتوى</label>
<div class="grid grid-cols-3 gap-3"> <div class="grid grid-cols-3 gap-3">
<label class="relative cursor-pointer"> @foreach([
<input type="radio" wire:model="navbar_style" value="transparent" class="sr-only peer"> ['compact', 'مضغوط', 'عناصر متقاربة'],
<div class="p-3 rounded-lg border-2 peer-checked:border-blue-500 border-gray-200 dark:border-gray-600 text-center transition-colors"> ['comfortable', 'مريح', 'التوازن الافتراضي'],
<span class="text-sm">شفاف</span> ['spacious', 'واسع', 'مساحات كبيرة'],
</div> ] as [$val, $label, $desc])
</label> <label class="relative cursor-pointer">
<label class="relative cursor-pointer"> <input type="radio" wire:model="density" value="{{ $val }}" class="sr-only peer">
<input type="radio" wire:model="navbar_style" value="solid" class="sr-only peer"> <div class="p-3 rounded-lg border-2 peer-checked:border-blue-500 border-gray-200 dark:border-gray-600 text-center transition-colors">
<div class="p-3 rounded-lg border-2 peer-checked:border-blue-500 border-gray-200 dark:border-gray-600 text-center transition-colors"> <span class="text-sm font-medium block">{{ $label }}</span>
<span class="text-sm">معتم</span> <span class="text-xs text-gray-400">{{ $desc }}</span>
</div> </div>
</label> </label>
<label class="relative cursor-pointer"> @endforeach
<input type="radio" wire:model="navbar_style" value="floating" class="sr-only peer">
<div class="p-3 rounded-lg border-2 peer-checked:border-blue-500 border-gray-200 dark:border-gray-600 text-center transition-colors">
<span class="text-sm">عائم</span>
</div>
</label>
</div> </div>
</div> </div>
</div> </div>
{{-- Social Links --}} {{-- Shapes & Borders --}}
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6"> <div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">روابط التواصل الاجتماعي</h3> <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">الأشكال والحدود</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div> <div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Facebook</label> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">زوايا العناصر</label>
<input type="url" wire:model="social_links.facebook" dir="ltr" placeholder="https://facebook.com/..." class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"> <div class="grid grid-cols-4 gap-2">
@foreach([
['sharp', 'حاد', 'rounded-none'],
['subtle', 'خفيف', 'rounded'],
['rounded', 'دائري', 'rounded-xl'],
['pill', 'كبسولة', 'rounded-full'],
] as [$val, $label, $class])
<label class="cursor-pointer">
<input type="radio" wire:model="border_radius_preset" value="{{ $val }}" class="sr-only peer">
<div class="p-2 border-2 peer-checked:border-blue-500 border-gray-200 dark:border-gray-600 text-center transition-colors {{ $class }}">
<div class="w-8 h-6 mx-auto bg-gray-300 dark:bg-gray-500 {{ $class }}"></div>
<span class="text-xs mt-1 block">{{ $label }}</span>
</div>
</label>
@endforeach
</div>
</div> </div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">نمط البطاقات</label>
<div class="grid grid-cols-4 gap-2">
@foreach([
['flat', 'مسطح'],
['raised', 'مرتفع'],
['bordered', 'محدد'],
['glass', 'زجاجي'],
] as [$val, $label])
<label class="cursor-pointer">
<input type="radio" wire:model="card_style" value="{{ $val }}" class="sr-only peer">
<div class="p-2 rounded-lg border-2 peer-checked:border-blue-500 border-gray-200 dark:border-gray-600 text-center transition-colors">
<span class="text-xs">{{ $label }}</span>
</div>
</label>
@endforeach
</div>
</div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Instagram</label> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">الفاصل بين الأقسام</label>
<input type="url" wire:model="social_links.instagram" dir="ltr" placeholder="https://instagram.com/..." class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"> <select wire:model="section_divider" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white">
<option value="none">بدون</option>
<option value="line">خط</option>
<option value="wave">موجة</option>
<option value="angle">زاوية</option>
<option value="curve">منحنى</option>
</select>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Twitter / X</label> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">شكل الصور</label>
<input type="url" wire:model="social_links.twitter" dir="ltr" placeholder="https://x.com/..." class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"> <select wire:model="image_shape" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white">
<option value="square">مربع</option>
<option value="rounded">مستدير</option>
<option value="circle">دائري</option>
<option value="blob">عضوي</option>
</select>
</div> </div>
</div>
</div>
{{-- Animations --}}
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">الحركة والتأثيرات</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">YouTube</label> <label class="flex items-center gap-3 cursor-pointer">
<input type="url" wire:model="social_links.youtube" dir="ltr" placeholder="https://youtube.com/@..." class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"> <input type="checkbox" wire:model="animations_enabled" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">تفعيل الرسوم المتحركة</span>
</label>
<p class="text-xs text-gray-400 mt-1 ms-8">تأثيرات الظهور عند التمرير</p>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">TikTok</label> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">سرعة التأثيرات</label>
<input type="url" wire:model="social_links.tiktok" dir="ltr" placeholder="https://tiktok.com/@..." class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"> <select wire:model="animation_speed" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white" @if(!$animations_enabled) disabled @endif>
<option value="slow">بطيء</option>
<option value="normal">عادي</option>
<option value="fast">سريع</option>
</select>
</div> </div>
</div> </div>
</div> </div>
{{-- WhatsApp --}} {{-- Navbar Style --}}
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6"> <div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">واتساب</h3> <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">شريط التنقل</h3>
<div class="grid grid-cols-3 gap-3">
@foreach([
['transparent', 'شفاف'],
['solid', 'معتم'],
['floating', 'عائم'],
] as [$val, $label])
<label class="relative cursor-pointer">
<input type="radio" wire:model="navbar_style" value="{{ $val }}" class="sr-only peer">
<div class="p-3 rounded-lg border-2 peer-checked:border-blue-500 border-gray-200 dark:border-gray-600 text-center transition-colors">
<span class="text-sm">{{ $label }}</span>
</div>
</label>
@endforeach
</div>
</div>
{{-- Social Links --}}
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">روابط التواصل الاجتماعي</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
@foreach([
['facebook', 'Facebook', 'https://facebook.com/...'],
['instagram', 'Instagram', 'https://instagram.com/...'],
['twitter', 'Twitter / X', 'https://x.com/...'],
['youtube', 'YouTube', 'https://youtube.com/@...'],
['tiktok', 'TikTok', 'https://tiktok.com/@...'],
] as [$key, $label, $placeholder])
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{ $label }}</label>
<input type="url" wire:model="social_links.{{ $key }}" dir="ltr" placeholder="{{ $placeholder }}" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white">
</div>
@endforeach
</div>
</div>
{{-- WhatsApp & Site Info --}}
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">معلومات الموقع</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">رقم واتساب</label> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">رقم واتساب</label>
...@@ -157,6 +303,10 @@ ...@@ -157,6 +303,10 @@
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">عنوان الموقع</label> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">عنوان الموقع</label>
<input type="text" wire:model="site_title" placeholder="اسم الأكاديمية" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"> <input type="text" wire:model="site_title" placeholder="اسم الأكاديمية" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white">
</div> </div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">وصف الموقع</label>
<textarea wire:model="site_description" rows="2" placeholder="وصف قصير للموقع..." class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"></textarea>
</div>
</div> </div>
</div> </div>
...@@ -175,13 +325,23 @@ ...@@ -175,13 +325,23 @@
</div> </div>
</div> </div>
{{-- Custom CSS --}}
<div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">CSS مخصص</h3>
<textarea wire:model="custom_css" dir="ltr" rows="6" placeholder="/* أضف تنسيقاتك هنا */" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white font-mono text-sm"></textarea>
<p class="text-xs text-gray-500 mt-1">سيتم حقن هذا الكود مباشرة في صفحة الموقع</p>
</div>
{{-- Save Button --}} {{-- Save Button --}}
<div class="flex items-center gap-3"> <div class="flex items-center gap-3 sticky bottom-4 bg-white dark:bg-gray-800 p-4 rounded-xl border border-gray-200 dark:border-gray-700 shadow-lg">
<button type="submit" class="px-8 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium transition-colors" <button type="submit" class="px-8 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium transition-colors"
wire:loading.attr="disabled" wire:target="save"> wire:loading.attr="disabled" wire:target="save">
<span wire:loading.remove wire:target="save">حفظ الإعدادات</span> <span wire:loading.remove wire:target="save">حفظ الإعدادات</span>
<span wire:loading wire:target="save">جارٍ الحفظ...</span> <span wire:loading wire:target="save">جارٍ الحفظ...</span>
</button> </button>
<a href="{{ route('website.preview') }}" target="_blank" class="px-4 py-3 text-sm font-medium text-gray-700 dark:text-gray-300 rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
معاينة الموقع ↗
</a>
</div> </div>
</form> </form>
</div> </div>
...@@ -2,13 +2,35 @@ ...@@ -2,13 +2,35 @@
@section('content') @section('content')
@foreach($sections as $section) @foreach($sections as $section)
<section id="section-{{ $section->section_key->value }}"> @php
@include('website.sections.' . $section->section_key->value, [ $paddingClass = match($section->padding_y) {
'section' => $section, 'none' => 'py-0',
'academy' => $academy, 'small' => 'py-8',
'settings' => $settings, 'medium' => 'py-16',
'data' => $data, 'large' => 'py-24',
]) 'xlarge' => 'py-40',
default => '',
};
$mobileHidden = $section->hidden_on_mobile ? 'hidden md:block' : '';
$animAttr = ($settings->animations_enabled ?? true) ? $section->animation ?? 'fade-up' : 'none';
@endphp
<section id="section-{{ $section->section_key->value }}"
class="{{ $mobileHidden }} {{ $paddingClass }}"
@if($animAttr !== 'none') data-animation="{{ $animAttr }}" @endif>
@if($section->bg_type !== 'inherit')
@include('website.partials.section-bg', ['section' => $section])
@else
@include('website.sections.' . $section->section_key->value, [
'section' => $section,
'academy' => $academy,
'settings' => $settings,
'data' => $data,
])
@endif
</section> </section>
@if(($settings->section_divider ?? 'none') !== 'none' && !$loop->last)
@include('website.partials.divider', ['settings' => $settings])
@endif
@endforeach @endforeach
@endsection @endsection
...@@ -27,6 +27,26 @@ ...@@ -27,6 +27,26 @@
@livewireStyles @livewireStyles
{{-- Dynamic CSS Variables from Academy Branding --}} {{-- Dynamic CSS Variables from Academy Branding --}}
@php
$fontSizeMap = ['small' => '14px', 'medium' => '16px', 'large' => '18px', 'xlarge' => '20px'];
$h1Map = ['small' => '2rem', 'medium' => '2.5rem', 'large' => '3rem', 'xlarge' => '3.5rem'];
$h2Map = ['small' => '1.6rem', 'medium' => '2rem', 'large' => '2.4rem', 'xlarge' => '2.8rem'];
$h3Map = ['small' => '1.3rem', 'medium' => '1.5rem', 'large' => '1.75rem', 'xlarge' => '2rem'];
$lineHeightMap = ['tight' => '1.3', 'normal' => '1.6', 'relaxed' => '1.8', 'loose' => '2'];
$letterSpacingMap = ['tight' => '-0.02em', 'normal' => '0', 'wide' => '0.05em'];
$sectionPyMap = ['compact' => '3rem', 'comfortable' => '5rem', 'spacious' => '8rem'];
$cardGapMap = ['compact' => '1rem', 'comfortable' => '1.5rem', 'spacious' => '2.5rem'];
$radiusMap = ['sharp' => '0px', 'subtle' => '4px', 'rounded' => '12px', 'pill' => '9999px'];
$radiusLgMap = ['sharp' => '0px', 'subtle' => '8px', 'rounded' => '20px', 'pill' => '9999px'];
$animDurationMap = ['slow' => '1.2s', 'normal' => '0.8s', 'fast' => '0.4s'];
$fss = $settings->font_size_scale ?? 'medium';
$lh = $settings->line_height ?? 'normal';
$ls = $settings->letter_spacing ?? 'normal';
$dn = $settings->density ?? 'comfortable';
$br = $settings->border_radius_preset ?? 'rounded';
$as = $settings->animation_speed ?? 'normal';
@endphp
<style> <style>
:root { :root {
--site-primary: {{ $settings->primary_color ?? '#1e3a5f' }}; --site-primary: {{ $settings->primary_color ?? '#1e3a5f' }};
...@@ -34,11 +54,57 @@ ...@@ -34,11 +54,57 @@
--site-accent: {{ $settings->accent_color ?? '#2563eb' }}; --site-accent: {{ $settings->accent_color ?? '#2563eb' }};
--site-text: {{ $settings->text_color ?? '#1f2937' }}; --site-text: {{ $settings->text_color ?? '#1f2937' }};
--site-bg: {{ $settings->background_color ?? '#ffffff' }}; --site-bg: {{ $settings->background_color ?? '#ffffff' }};
--site-surface: {{ $settings->surface_color ?? '#f8fafc' }};
--site-border: {{ $settings->border_color ?? '#e2e8f0' }};
--site-muted: {{ $settings->muted_text_color ?? '#64748b' }};
--site-heading-font: '{{ $settings->heading_font ?? 'Cairo' }}', 'Noto Sans Arabic', sans-serif; --site-heading-font: '{{ $settings->heading_font ?? 'Cairo' }}', 'Noto Sans Arabic', sans-serif;
--site-body-font: '{{ $settings->body_font ?? 'Cairo' }}', 'Noto Sans Arabic', sans-serif; --site-body-font: '{{ $settings->body_font ?? 'Cairo' }}', 'Noto Sans Arabic', sans-serif;
--site-text-base: {{ $fontSizeMap[$fss] }};
--site-h1: {{ $h1Map[$fss] }};
--site-h2: {{ $h2Map[$fss] }};
--site-h3: {{ $h3Map[$fss] }};
--site-heading-weight: {{ $settings->heading_weight ?? '700' }};
--site-line-height: {{ $lineHeightMap[$lh] }};
--site-letter-spacing: {{ $letterSpacingMap[$ls] }};
--section-py: {{ $sectionPyMap[$dn] }};
--card-gap: {{ $cardGapMap[$dn] }};
--site-radius: {{ $radiusMap[$br] }};
--site-radius-lg: {{ $radiusLgMap[$br] }};
--anim-duration: {{ $animDurationMap[$as] }};
}
.website-body {
font-family: var(--site-body-font);
font-size: var(--site-text-base);
line-height: var(--site-line-height);
letter-spacing: var(--site-letter-spacing);
background-color: var(--site-bg);
color: var(--site-text);
}
.hero-title, .section-title, .stat-number {
font-family: var(--site-heading-font);
font-weight: var(--site-heading-weight);
}
/* Card Styles */
.site-card {
border-radius: var(--site-radius-lg);
{{ match($settings->card_style ?? 'raised') {
'flat' => 'box-shadow: none; border: none; background: transparent;',
'raised' => 'box-shadow: 0 4px 20px rgba(0,0,0,0.15);',
'bordered' => 'box-shadow: none; border: 1px solid var(--site-border);',
'glass' => 'box-shadow: 0 8px 32px rgba(0,0,0,0.1); border: 1px solid rgba(255,255,255,0.1); backdrop-filter: blur(10px); background: rgba(255,255,255,0.05);',
default => '',
} }}
}
/* Image Shapes */
.site-img {
{{ match($settings->image_shape ?? 'rounded') {
'square' => 'border-radius: 0;',
'rounded' => 'border-radius: var(--site-radius-lg);',
'circle' => 'border-radius: 50%;',
'blob' => 'border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%;',
default => '',
} }}
} }
.website-body { font-family: var(--site-body-font); }
.hero-title, .section-title, .stat-number { font-family: var(--site-heading-font); }
</style> </style>
@if($settings->custom_css) @if($settings->custom_css)
...@@ -71,7 +137,7 @@ function gtag(){dataLayer.push(arguments);} ...@@ -71,7 +137,7 @@ function gtag(){dataLayer.push(arguments);}
</script> </script>
@endif @endif
</head> </head>
<body class="website-body"> <body class="website-body" data-template="{{ $settings->template ?? 'bold_athletic' }}" data-density="{{ $settings->density ?? 'comfortable' }}" data-radius="{{ $settings->border_radius_preset ?? 'rounded' }}" data-card="{{ $settings->card_style ?? 'raised' }}" data-animations="{{ ($settings->animations_enabled ?? true) ? 'true' : 'false' }}" data-anim-speed="{{ $settings->animation_speed ?? 'normal' }}">
{{-- Navbar --}} {{-- Navbar --}}
@include('website.navbar') @include('website.navbar')
......
@switch($settings->section_divider)
@case('line')
<div class="w-full px-8">
<div class="border-t" style="border-color: var(--site-border)"></div>
</div>
@break
@case('wave')
<div class="w-full overflow-hidden" style="color: var(--site-bg)">
<svg viewBox="0 0 1200 60" preserveAspectRatio="none" class="w-full h-10 block">
<path d="M0,30 C200,60 400,0 600,30 C800,60 1000,0 1200,30 L1200,60 L0,60 Z" fill="currentColor"/>
</svg>
</div>
@break
@case('angle')
<div class="w-full overflow-hidden" style="color: var(--site-bg)">
<svg viewBox="0 0 1200 60" preserveAspectRatio="none" class="w-full h-10 block">
<polygon points="0,60 1200,0 1200,60" fill="currentColor"/>
</svg>
</div>
@break
@case('curve')
<div class="w-full overflow-hidden" style="color: var(--site-bg)">
<svg viewBox="0 0 1200 60" preserveAspectRatio="none" class="w-full h-10 block">
<path d="M0,60 Q600,0 1200,60 Z" fill="currentColor"/>
</svg>
</div>
@break
@endswitch
@php
$bgStyle = match($section->bg_type) {
'solid' => "background-color: {$section->bg_color};",
'gradient' => "background: linear-gradient({$section->bg_gradient_angle}deg, {$section->bg_gradient_from}, {$section->bg_gradient_to});",
'image' => $section->bg_image_path ? "background-image: url('" . Storage::disk('public')->url($section->bg_image_path) . "'); background-size: cover; background-position: center;" : '',
default => '',
};
@endphp
<div class="relative" style="{{ $bgStyle }}">
@if($section->bg_type === 'image' && $section->bg_overlay_opacity > 0)
<div class="absolute inset-0 bg-black" style="opacity: {{ $section->bg_overlay_opacity / 100 }}"></div>
@endif
@if($section->bg_type === 'video' && $section->bg_video_url)
<video autoplay muted loop playsinline class="absolute inset-0 w-full h-full object-cover">
<source src="{{ $section->bg_video_url }}" type="video/mp4">
</video>
<div class="absolute inset-0 bg-black" style="opacity: {{ $section->bg_overlay_opacity / 100 }}"></div>
@endif
<div class="relative z-10">
@include('website.sections.' . $section->section_key->value, [
'section' => $section,
'academy' => $academy,
'settings' => $settings,
'data' => $data,
])
</div>
</div>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment