quote_text, $card->quote_author, $card->title, $card->description, ])); // Rule-based: extract meaningful words as tag suggestions. // In production, replace/extend with an actual NLP/AI call. $words = preg_split('/[\s\-_,\.]+/u', strtolower($text)); $stopWords = ['the', 'a', 'an', 'in', 'on', 'at', 'to', 'for', 'of', 'and', 'or', 'is', 'it', 'be', 'by', 'that', 'this', 'with', 'you', 'your', 'we', 'our', 'not']; $filtered = array_values(array_filter( array_unique($words ?? []), fn ($w) => is_string($w) && mb_strlen($w) >= 4 && ! in_array($w, $stopWords, true), )); return array_slice($filtered, 0, 6); } public function suggestMood(NovaCard $card): array { $text = strtolower((string) $card->quote_text . ' ' . (string) $card->title); $moods = []; $moodMap = [ 'love|heart|romance|kiss|tender' => 'romantic', 'dark|shadow|night|alone|silence|void|lost' => 'dark-poetry', 'inspire|hope|dream|believe|courage|strength|rise' => 'inspirational', 'morning|sunrise|calm|peace|gentle|soft|breeze' => 'soft-morning', 'minimal|simple|quiet|still|breath|moment' => 'minimal', 'power|bold|fierce|fire|warrior|fight|hustle' => 'motivational', 'cyber|neon|digital|code|matrix|tech|signal' => 'cyber-mood', ]; foreach ($moodMap as $pattern => $mood) { if (preg_match('/(' . $pattern . ')/i', $text)) { $moods[] = $mood; } } return array_slice(array_unique($moods), 0, 3); } public function suggestLayout(NovaCard $card): array { $project = is_array($card->project_json) ? $card->project_json : []; $quoteLength = mb_strlen((string) $card->quote_text); $hasAuthor = ! empty($card->quote_author); // Heuristic layout suggestions based on text content. $suggestions = []; if ($quoteLength < 80) { $suggestions[] = [ 'layout' => 'quote_centered', 'reason' => 'Short quotes work well centered with generous padding.', ]; } elseif ($quoteLength < 200) { $suggestions[] = [ 'layout' => 'quote_heavy', 'reason' => 'Medium quotes benefit from a focused heavy layout.', ]; } else { $suggestions[] = [ 'layout' => 'editorial', 'reason' => 'Longer quotes fit editorial multi-column layouts.', ]; } if ($hasAuthor) { $suggestions[] = [ 'layout' => 'byline_bottom', 'reason' => 'With an author, a bottom byline anchors attribution cleanly.', ]; } return $suggestions; } public function suggestBackground(NovaCard $card): array { $moods = $this->suggestMood($card); $suggestions = []; $moodGradientMap = [ 'romantic' => ['gradient_preset' => 'rose-poetry', 'reason' => 'Warm rose tones suit romantic content.'], 'dark-poetry' => ['gradient_preset' => 'midnight-nova', 'reason' => 'Deep dark gradients amplify dark poetry vibes.'], 'inspirational' => ['gradient_preset' => 'golden-hour', 'reason' => 'Warm golden tones elevate inspirational messages.'], 'soft-morning' => ['gradient_preset' => 'soft-dawn', 'reason' => 'Gentle pastels suit morning or calm content.'], 'minimal' => ['gradient_preset' => 'carbon-minimal', 'reason' => 'Clean dark or light neutrals suit minimal style.'], 'motivational' => ['gradient_preset' => 'bold-fire', 'reason' => 'Bold warm gradients energise motivational content.'], 'cyber-mood' => ['gradient_preset' => 'cyber-pulse', 'reason' => 'Electric neon gradients suit cyber aesthetic.'], ]; foreach ($moods as $mood) { if (isset($moodGradientMap[$mood])) { $suggestions[] = $moodGradientMap[$mood]; } } // Default if no match. if (empty($suggestions)) { $suggestions[] = ['gradient_preset' => 'midnight-nova', 'reason' => 'A versatile dark gradient that works for most content.']; } return $suggestions; } public function suggestFontPairing(NovaCard $card): array { $moods = $this->suggestMood($card); $moodFontMap = [ 'romantic' => ['font_preset' => 'romantic-serif', 'reason' => 'Elegant serif pairs beautifully with romantic content.'], 'dark-poetry' => ['font_preset' => 'dark-poetic', 'reason' => 'Strong contrast serif pairs with dark poetry style.'], 'inspirational' => ['font_preset' => 'modern-sans', 'reason' => 'Clean modern sans feels energising and clear.'], 'soft-morning' => ['font_preset' => 'soft-rounded', 'reason' => 'Rounded type has warmth and approachability.'], 'minimal' => ['font_preset' => 'minimal-mono', 'reason' => 'Monospaced type enforces a minimalist aesthetic.'], 'cyber-mood' => ['font_preset' => 'cyber-display', 'reason' => 'Tech display fonts suit cyber and digital themes.'], ]; foreach ($moods as $mood) { if (isset($moodFontMap[$mood])) { return [$moodFontMap[$mood]]; } } return [['font_preset' => 'modern-sans', 'reason' => 'A clean, versatile sans-serif for most content.']]; } public function suggestReadabilityFixes(NovaCard $card): array { $issues = []; $project = is_array($card->project_json) ? $card->project_json : []; $textColor = Arr::get($project, 'typography.text_color', '#ffffff'); $bgType = Arr::get($project, 'background.type', 'gradient'); $overlayStyle = Arr::get($project, 'background.overlay_style', 'dark-soft'); $quoteSize = (int) Arr::get($project, 'typography.quote_size', 72); $lineHeight = (float) Arr::get($project, 'typography.line_height', 1.2); // Detect light text without overlay on upload background. if ($bgType === 'upload' && in_array($overlayStyle, ['none', 'minimal'], true)) { $issues[] = [ 'field' => 'background.overlay_style', 'message' => 'Adding a dark overlay improves text legibility on photo backgrounds.', 'suggestion' => 'dark-soft', ]; } // Detect very small quote text. if ($quoteSize < 40) { $issues[] = [ 'field' => 'typography.quote_size', 'message' => 'Quote text may be too small to read on mobile.', 'suggestion' => 52, ]; } // Detect very tight line height on long text. if ($lineHeight < 1.1 && mb_strlen((string) $card->quote_text) > 100) { $issues[] = [ 'field' => 'typography.line_height', 'message' => 'Increasing line height improves readability for longer quotes.', 'suggestion' => 1.3, ]; } return $issues; } /** * Return all suggestions in one call, for the AI assist panel. */ public function allSuggestions(NovaCard $card): array { return [ 'tags' => $this->suggestTags($card), 'moods' => $this->suggestMood($card), 'layouts' => $this->suggestLayout($card), 'backgrounds' => $this->suggestBackground($card), 'font_pairings' => $this->suggestFontPairing($card), 'readability_fixes' => $this->suggestReadabilityFixes($card), ]; } }