$activeTheme], $themeConfig); } return data_get($themeConfig, $key, $default); } } if (!function_exists('theme_name')) { /** * Get the current theme name * * @return string */ function theme_name() { return theme('name', 'Timebank.cc'); } } if (!function_exists('theme_id')) { /** * Get the current theme ID/key * * @return string */ function theme_id() { return config('themes.active', 'timebank_cc'); } } if (!function_exists('theme_color')) { /** * Get a theme color value * * @param string $colorKey Color key (e.g., 'primary.500', 'accent', 'text.primary') * @param string|null $default Default color value * @return string */ function theme_color($colorKey, $default = null) { return theme("colors.{$colorKey}", $default); } } if (!function_exists('theme_font')) { /** * Get a theme typography value * * @param string $fontKey Font key (e.g., 'font_family_body', 'font_size_base') * @param string|null $default Default font value * @return string */ function theme_font($fontKey, $default = null) { return theme("typography.{$fontKey}", $default); } } if (!function_exists('is_theme')) { /** * Check if the current theme matches the given theme ID * * @param string $themeId Theme ID to check against * @return bool */ function is_theme($themeId) { return theme_id() === $themeId; } } if (!function_exists('theme_logo')) { /** * Get a theme logo path or view name * * @param string $type Logo type: 'svg_inline' or 'email_logo' * @param string|null $default Default value if logo not configured * @return string */ function theme_logo($type = 'svg_inline', $default = null) { return theme("logos.{$type}", $default); } } if (!function_exists('theme_email_logo')) { /** * The theme's email logo, relative to storage/app/public. When the theme has none or its file was * not uploaded: a text logo with the platform name (App\Services\GeneratedLogo). The tracked * app_mail_logo(_inverted).png is the Timebank.cc logo, so only the timebank_cc theme falls back * to it, and any theme when the text logo cannot be drawn. * * @param bool $inverted The light version, for dark backgrounds * @return string */ function theme_email_logo(bool $inverted = false): string { $logo = theme_logo($inverted ? 'email_logo_inverted' : 'email_logo'); if ($logo && is_file(storage_path('app/public/' . $logo))) { return $logo; } if (theme_id() !== 'timebank_cc' && ($generated = \App\Services\GeneratedLogo::path($inverted))) { return $generated; } return $inverted ? 'app-images/app_mail_logo_inverted.png' : 'app-images/app_mail_logo.png'; } } if (!function_exists('platform_public_file')) { /** * A file in the platform folder's public/, as [file path, URL], when it exists and is served (the link * public/platform made by php artisan platform:link). Null otherwise. * * @return array{0: string, 1: string}|null */ function platform_public_file(string $file): ?array { $file = ltrim($file, '/'); return is_file(public_path("platform/{$file}")) ? [public_path("platform/{$file}"), asset("platform/{$file}")] : null; } } if (!function_exists('theme_email_logo_url')) { /** * URL of the email logo: platform/public/mail-logo(-inverted).png when the platform has one, else * theme_email_logo() in storage. */ function theme_email_logo_url(bool $inverted = false): string { $platform = platform_public_file($inverted ? 'mail-logo-inverted.png' : 'mail-logo.png'); return $platform[1] ?? asset('storage/' . theme_email_logo($inverted)); } } if (!function_exists('theme_email_logo_file')) { /** * File path of the email logo (for PDFs and inline images), with the same order as theme_email_logo_url(). */ function theme_email_logo_file(bool $inverted = false): string { $platform = platform_public_file($inverted ? 'mail-logo-inverted.png' : 'mail-logo.png'); return $platform[0] ?? storage_path('app/public/' . theme_email_logo($inverted)); } } if (!function_exists('theme_favicon')) { /** * URL of a favicon file (favicon.ico, favicon-32x32.png, ...). Taken from the platform folder's public/ when * it holds the file, else from the theme's logos.favicon_dir (relative to public/), else from public/ itself. * * @param string $file * @return string */ function theme_favicon(string $file): string { if ($platform = platform_public_file($file)) { return $platform[1]; } $dir = trim((string) theme_logo('favicon_dir'), '/'); if ($dir !== '' && is_file(public_path("{$dir}/{$file}"))) { return asset("{$dir}/{$file}"); } return asset($file); } } if (!function_exists('theme_favicon_optional')) { /** * URL of an optional icon file (favicon.svg, favicon-96x96.png, site.webmanifest), or null when neither the * platform folder, the theme's favicon_dir nor public/ has it: the layouts link it only when it exists. */ function theme_favicon_optional(string $file): ?string { if ($platform = platform_public_file($file)) { return $platform[1]; } $dir = trim((string) theme_logo('favicon_dir'), '/'); if ($dir !== '' && is_file(public_path("{$dir}/{$file}"))) { return asset("{$dir}/{$file}"); } return is_file(public_path($file)) ? asset($file) : null; } } if (!function_exists('theme_og_image')) { /** * URL of the preview image for links shared on social media (og:image): platform/public/og-image.png, else the * theme's logos.og_image (relative to public/), else null (no og:image tag). About 1200x630 pixels works best. */ function theme_og_image(): ?string { if ($platform = platform_public_file('og-image.png')) { return $platform[1]; } $image = trim((string) theme_logo('og_image'), '/'); return $image !== '' && is_file(public_path($image)) ? asset($image) : null; } } if (!function_exists('theme_css_vars')) { /** * Generate CSS custom properties for the current theme * * @return string CSS custom properties as inline styles */ function theme_css_vars() { $themeConfig = theme(); $colors = $themeConfig['colors'] ?? []; $typography = $themeConfig['typography'] ?? []; $cssVars = []; // Process color variables foreach ($colors as $colorGroup => $colorValue) { if (is_array($colorValue)) { foreach ($colorValue as $shade => $hex) { $rgbValue = hexToRgb($hex); $cssVars["--color-{$colorGroup}-{$shade}"] = $rgbValue; } } else { $rgbValue = hexToRgb($colorValue); $cssVars["--color-{$colorGroup}"] = $rgbValue; } } // Themes without their own secondary-button colours keep the old look (secondary / text.primary) $cssVars['--color-button-secondary'] ??= $cssVars['--color-secondary'] ?? null; $cssVars['--color-button-secondary-text'] ??= $cssVars['--color-text-primary'] ?? null; $cssVars = array_filter($cssVars, fn ($value) => $value !== null); // Process typography variables foreach ($typography as $typographyKey => $typographyValue) { if (is_array($typographyValue)) { // Handle nested arrays (heading_sizes, font_sizes, font_weights) foreach ($typographyValue as $subKey => $subValue) { $cssVars["--{$typographyKey}-{$subKey}"] = $subValue; } } else { $cssVars["--{$typographyKey}"] = $typographyValue; } } // Convert to CSS string $cssString = ''; foreach ($cssVars as $property => $value) { $cssString .= "{$property}: {$value}; "; } return $cssString; } } if (!function_exists('hexToRgb')) { /** * Convert hex color to RGB values (space-separated for CSS custom properties) * * @param string $hex Hex color value * @return string RGB values separated by spaces */ function hexToRgb($hex) { $hex = ltrim($hex, '#'); if (strlen($hex) === 3) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } $r = hexdec(substr($hex, 0, 2)); $g = hexdec(substr($hex, 2, 2)); $b = hexdec(substr($hex, 4, 2)); return "{$r} {$g} {$b}"; } }