0.6 ? '#1e1e1e' : '#fff'; } public static function darken( string $hex, int $percent ): string { $rgb = self::rgb( $hex ); if ( null === $rgb ) { return '#005177'; } $factor = max( 0, 1 - min( 100, max( 0, $percent ) ) / 100 ); return sprintf( '#%02x%02x%02x', (int) ( $rgb[0] * $factor ), (int) ( $rgb[1] * $factor ), (int) ( $rgb[2] * $factor ) ); } public static function brand_style( string $hex ): string { $hex = (string) sanitize_hex_color( $hex ); if ( '' === $hex ) { return ''; } return '--rl-brand:' . $hex . ';--rl-brand-fg:' . self::readable_on( $hex ) . ';--rl-brand-2:' . self::darken( $hex, 15 ); } public const LOOKS_STYLE = 'rooster-lms-looks'; private static string $applied = ''; public static function applied_look(): string { return self::$applied; } public static function forget_look(): void { self::$applied = ''; } public static function look_attributes( string $key, string $brand = '' ): array { $look = Looks::get( $key ); if ( null === $look ) { $key = Looks::DEFAULT; $look = Looks::get( $key ) ?? array( 'tokens' => array(), 'dark' => false, 'style' => '', ); } self::$applied = $key; $css = ''; foreach ( $look['tokens'] as $name => $value ) { $css .= $name . ':' . $value . ';'; } if ( Looks::DEFAULT !== $key ) { self::enqueue_looks(); if ( '' !== $look['style'] && wp_style_is( $look['style'], 'registered' ) ) { wp_enqueue_style( $look['style'] ); } } $style = trim( $brand, ';' ); $style = '' === $style ? rtrim( $css, ';' ) : ( '' === $css ? $style : $style . ';' . rtrim( $css, ';' ) ); return array( 'data-rl-look' => $key, 'class' => $look['dark'] ? 'rooster-lms-dark rooster-lms-look-dark' : '', 'style' => $style, ); } public static function enqueue_looks(): void { if ( ! wp_style_is( self::LOOKS_STYLE, 'registered' ) ) { Assets::enqueue_part( Assets::UI_BASE ); wp_register_style( self::LOOKS_STYLE, plugins_url( 'assets/ui/looks.css', ROOSTER_LMS_FILE ), array( Assets::UI_BASE ), Assets::ver( 'looks.css' ) ); } wp_enqueue_style( self::LOOKS_STYLE ); } public static function overrides( bool $admin ): array { $out = array(); $primary = ! $admin && Theme::designs() ? self::DEFAULT_PRIMARY : (string) Settings::get( 'ui_primary' ); $hex = (string) sanitize_hex_color( $primary ); if ( '' !== $hex && strtolower( $hex ) !== self::DEFAULT_PRIMARY ) { $out['--rl-primary'] = $hex; $out['--rl-primary-fg'] = self::readable_on( $hex ); $out['--rl-primary-soft'] = 'color-mix(in srgb,' . $hex . ' 14%,#fff)'; $out['--rl-primary-soft-fg'] = 'color-mix(in srgb,' . $hex . ' 70%,#000)'; } elseif ( 'theme' === $primary && ! $admin ) { $out['--rl-primary'] = 'var(--wp--preset--color--primary,var(--wp--preset--color--accent-1,' . self::DEFAULT_PRIMARY . '))'; $theme = self::theme_primary(); if ( '' !== $theme ) { $out['--rl-primary-fg'] = self::readable_on( $theme ); } } if ( ! $admin ) { $max = (int) Settings::get( 'ui_shell_max' ); if ( $max > 0 ) { $out['--rl-shell-max'] = $max . 'px'; } $pad = (int) Settings::get( 'ui_shell_pad' ); if ( self::DEFAULT_SHELL_PAD !== $pad ) { $out['--rl-shell-pad'] = $pad . 'px'; } } $out = apply_filters( 'rooster_lms_ui_tokens', $out, $admin ); $clean = array(); foreach ( is_array( $out ) ? $out : array() as $name => $value ) { $name = (string) $name; $value = is_scalar( $value ) ? trim( (string) $value ) : ''; if ( 1 === preg_match( '/^--rl-[a-z0-9-]+$/', $name ) && '' !== $value && 1 !== preg_match( '/[;{}<>\\\\]|\/\*/', $value ) ) { $clean[ $name ] = $value; } } return $clean; } public static function inline_css( ?bool $admin = null ): string { $admin = $admin ?? is_admin(); $mapping = $admin ? '' : self::theme_mapping(); $out = self::overrides( $admin ); if ( array() === $out ) { return $mapping; } $css = ''; foreach ( $out as $name => $value ) { $css .= $name . ':' . $value . ';'; } return $mapping . ( '' === $mapping ? ':root' : self::C2_SELECTOR ) . '{' . rtrim( $css, ';' ) . '}'; } public const C2_SELECTOR = ':root,html[data-rooster-scheme="dark"]'; public const C2_PRESETS = array( '--rl-background' => 'bg', '--rl-card' => 'bg', '--rl-tab-active-bg' => 'bg', '--rl-foreground' => 'fg', '--rl-card-fg' => 'fg', '--rl-accent-fg' => 'fg', '--rl-secondary-fg' => 'fg', '--rl-secondary-soft-fg' => 'fg', '--rl-mono-soft-fg' => 'fg', '--rl-tab-fg' => 'fg', '--rl-muted-fg' => 'muted', '--rl-tab-edge' => 'muted', '--rl-muted' => 'subtle', '--rl-accent' => 'subtle', '--rl-secondary' => 'subtle', '--rl-secondary-soft' => 'subtle', '--rl-mono-soft' => 'subtle', '--rl-tab-bg' => 'subtle', '--rl-thead-bg' => 'neutral-50', '--rl-row-hover' => 'neutral-50', '--rl-border' => 'line', '--rl-input' => 'line', '--rl-mono' => 'fg', '--rl-mono-fg' => 'bg', '--rl-primary' => 'accent', '--rl-primary-fg' => 'on-accent', '--rl-primary-text' => 'accent-text', ); public const C2_V2 = array( '--rl-shape-button' => 'var(--wp--custom--rooster--shape--button,var(--rl-radius-md))', '--rl-shape-control' => 'var(--wp--custom--rooster--shape--control,var(--rl-radius-md))', '--rl-shape-card' => 'var(--wp--custom--rooster--shape--card,var(--rl-radius-lg))', '--rl-shape-panel' => 'var(--wp--custom--rooster--shape--panel,var(--rl-radius-lg))', '--rl-shape-media' => 'var(--wp--custom--rooster--shape--media,var(--rl-radius-lg))', '--rl-shape-chip' => 'var(--wp--custom--rooster--shape--chip,var(--rl-radius-pill))', '--rl-success' => 'var(--wp--preset--color--success,#22c55e)', '--rl-warning' => 'var(--wp--preset--color--warning,#facc15)', '--rl-destructive' => 'var(--wp--preset--color--danger,#dc2626)', '--rl-tone-success' => 'var(--wp--preset--color--success,#166534)', '--rl-tone-warning' => 'var(--wp--preset--color--warning,#a16207)', '--rl-tone-danger' => 'var(--wp--preset--color--danger,#b91c1c)', '--rl-tone-info' => 'var(--wp--preset--color--accent,#6d28d9)', '--rl-tone-success-fill' => 'var(--wp--preset--color--success,#22c55e)', '--rl-tone-warning-fill' => 'var(--wp--preset--color--warning,#facc15)', '--rl-tone-danger-fill' => 'var(--wp--preset--color--danger,#ef4444)', '--rl-tone-info-fill' => 'var(--wp--preset--color--accent,#8b5cf6)', '--rl-success-soft' => 'color-mix(in srgb,var(--wp--preset--color--success,#22c55e) 12%,var(--wp--preset--color--bg,#fff))', '--rl-warning-soft' => 'color-mix(in srgb,var(--wp--preset--color--warning,#facc15) 12%,var(--wp--preset--color--bg,#fff))', '--rl-destructive-soft' => 'color-mix(in srgb,var(--wp--preset--color--danger,#dc2626) 12%,var(--wp--preset--color--bg,#fff))', '--rl-info-soft' => 'color-mix(in srgb,var(--wp--preset--color--accent,#8b5cf6) 12%,var(--wp--preset--color--bg,#fff))', '--rl-tone-success-wash' => 'color-mix(in srgb,var(--wp--preset--color--success,#22c55e) 12%,var(--wp--preset--color--bg,#fff))', '--rl-tone-warning-wash' => 'color-mix(in srgb,var(--wp--preset--color--warning,#facc15) 12%,var(--wp--preset--color--bg,#fff))', '--rl-tone-danger-wash' => 'color-mix(in srgb,var(--wp--preset--color--danger,#dc2626) 12%,var(--wp--preset--color--bg,#fff))', '--rl-tone-info-wash' => 'color-mix(in srgb,var(--wp--preset--color--accent,#8b5cf6) 12%,var(--wp--preset--color--bg,#fff))', '--rl-tone-success-pale' => 'color-mix(in srgb,var(--wp--preset--color--success,#22c55e) 6%,var(--wp--preset--color--bg,#fff))', '--rl-tone-danger-pale' => 'color-mix(in srgb,var(--wp--preset--color--danger,#dc2626) 6%,var(--wp--preset--color--bg,#fff))', '--rl-success-soft-fg' => 'var(--wp--preset--color--fg,#09090b)', '--rl-warning-soft-fg' => 'var(--wp--preset--color--fg,#09090b)', '--rl-destructive-soft-fg' => 'var(--wp--preset--color--fg,#09090b)', '--rl-info-soft-fg' => 'var(--wp--preset--color--fg,#09090b)', '--rl-primary-soft' => 'color-mix(in srgb,var(--wp--preset--color--accent,#3b82f6) 10%,var(--wp--preset--color--bg,#fff))', '--rl-primary-soft-fg' => 'var(--wp--preset--color--accent-text,var(--wp--preset--color--accent,#3b82f6))', '--rl-track' => 'var(--rooster-lms-track,var(--wp--preset--color--line,#e4e4e7))', '--rl-tone-secondary' => 'var(--wp--preset--color--muted,#52525b)', '--rl-tone-muted' => 'var(--wp--preset--color--line,#a1a1aa)', '--rl-font' => 'var(--wp--preset--font-family--rooster-body,var(--wp--preset--font-family--body,inherit))', '--rl-shadow-sm' => 'var(--wp--custom--rooster--shadow--sm,0 1px 2px rgba(0,0,0,.05))', '--rl-ring' => 'var(--wp--preset--color--muted,#a1a1aa)', '--rl-field-focus' => 'inset 0 0 0 .5px var(--wp--preset--color--muted,#a1a1aa)', '--rl-focus' => 'var(--wp--custom--rooster--focus-ring,0 0 0 3px rgba(161,161,170,.3))', '--rl-check-size' => 'var(--wp--custom--rooster--field--check,1.25rem)', '--rl-shadow-md' => 'var(--wp--custom--rooster--shadow--md,0 8px 24px rgba(0,0,0,.12))', ); private const C2_FALLBACKS = array( 'bg' => '#fff', 'fg' => '#09090b', 'muted' => '#52525b', 'subtle' => '#f4f4f5', 'neutral-50' => '#fbfbfc', 'line' => '#ececee', 'accent' => self::DEFAULT_PRIMARY, 'on-accent' => 'var(--wp--preset--color--bg,#fff)', 'accent-text' => 'var(--wp--preset--color--accent,' . self::DEFAULT_PRIMARY . ')', ); public static function theme_mapping(): string { if ( ! Theme::designs() ) { return ''; } $css = ''; foreach ( self::C2_PRESETS as $token => $slug ) { $css .= $token . ':var(--wp--preset--color--' . $slug . ',' . self::C2_FALLBACKS[ $slug ] . ');'; } $sizes = ''; if ( Theme::version( 'C2' ) >= 2 ) { foreach ( self::C2_V2 as $token => $value ) { $css .= $token . ':' . $value . ';'; } $sizes = 'body:not(.wp-admin) .rooster-lms{--rl-text-base:var(--wp--preset--font-size--md,1rem);--rl-text-sm:var(--wp--preset--font-size--sm,.875rem);--rl-text-xs:var(--wp--preset--font-size--xs,.8125rem);--rl-control-h:var(--wp--custom--rooster--field--height,2.5rem)}'; } return self::C2_SELECTOR . '{' . rtrim( $css, ';' ) . '}' . $sizes; } public static function body_classes(): array { $docks = Theme::declares( 'C11' ) ? array( Dock::THEMED ) : array(); if ( Theme::designs() ) { return array_merge( array( 'rooster-lms-theme-buttons' ), $docks ); } $out = $docks; $dark = (string) Settings::get( 'ui_dark' ); if ( 'on' === $dark ) { $out[] = 'rooster-lms-dark'; } elseif ( 'auto' === $dark ) { $out[] = 'rooster-lms-dark-auto'; } if ( 'theme' === Settings::get( 'ui_buttons' ) ) { $out[] = 'rooster-lms-theme-buttons'; } return $out; } public static function theme_primary(): string { $palette = wp_get_global_settings( array( 'color', 'palette', 'theme' ) ); if ( ! is_array( $palette ) ) { return ''; } $found = array(); foreach ( $palette as $entry ) { if ( is_array( $entry ) && isset( $entry['slug'], $entry['color'] ) && is_string( $entry['color'] ) ) { $found[ (string) $entry['slug'] ] = (string) sanitize_hex_color( $entry['color'] ); } } foreach ( array( 'primary', 'accent-1', 'accent' ) as $slug ) { if ( ! empty( $found[ $slug ] ) ) { return $found[ $slug ]; } } return ''; } private static function rgb( string $hex ): ?array { $hex = ltrim( trim( $hex ), '#' ); if ( 3 === strlen( $hex ) ) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } if ( 6 !== strlen( $hex ) || ! ctype_xdigit( $hex ) ) { return null; } return array( (int) hexdec( substr( $hex, 0, 2 ) ), (int) hexdec( substr( $hex, 2, 2 ) ), (int) hexdec( substr( $hex, 4, 2 ) ) ); } }