['square' => 'Eckig', 'rounded' => 'Abgerundet', 'pill' => 'Pille'], 'appearance' => ['solid' => 'Gefüllt', 'outline' => 'Rahmen', 'soft' => 'Dezent', 'link' => 'Textlink'], 'size' => ['small' => 'Klein', 'normal' => 'Normal', 'large' => 'Groß'], ]; public const PRESETS = [ ['name' => 'Eckig', 'shape' => 'square', 'appearance' => 'solid'], ['name' => 'Abgerundet', 'shape' => 'rounded', 'appearance' => 'solid'], ['name' => 'Pille', 'shape' => 'pill', 'appearance' => 'solid'], ['name' => 'Rahmen · eckig', 'shape' => 'square', 'appearance' => 'outline'], ['name' => 'Rahmen · rund', 'shape' => 'rounded', 'appearance' => 'outline'], ['name' => 'Rahmen · Pille', 'shape' => 'pill', 'appearance' => 'outline'], ['name' => 'Dezent', 'shape' => 'rounded', 'appearance' => 'soft'], ['name' => 'Textlink', 'shape' => 'square', 'appearance' => 'link'], ]; public static function defaults(array $base): array { return ['shape' => 'rounded', 'appearance' => 'solid', 'size' => 'normal', 'fontFamily' => 'inherit', 'fontSize' => 14, 'fontWeight' => 600, 'letterSpacing' => 0, 'textTransform' => 'none', 'backgroundColor' => $base['color'], 'color' => $base['backgroundColor'], 'hoverBackgroundColor' => $base['color'], 'hoverColor' => $base['backgroundColor']]; } public static function validate(array $input, array $base, array $colors, array $fonts): array { $value = [...self::defaults($base), ...$input]; foreach ([...self::OPTIONS, 'textTransform' => ['none' => '', 'uppercase' => '']] as $key => $options) { if (!is_string($value[$key]) || !isset($options[$value[$key]])) { throw new InvalidPage('Unbekannte Button-Einstellung: '.$key); } } foreach (['fontSize' => [10, 72], 'fontWeight' => [100, 900], 'letterSpacing' => [-1, 8]] as $key => [$min, $max]) { if (!is_numeric($value[$key]) || $value[$key] < $min || $value[$key] > $max) { throw new InvalidPage('Bitte prüfe den Button-Wert: '.$key); } $value[$key] = (float) $value[$key]; } if (!in_array((int) $value['fontWeight'], Theme::WEIGHTS, true) || $value['fontWeight'] != (int) $value['fontWeight']) { throw new InvalidPage('Unbekannte Button-Schriftstärke.'); } if (!in_array($value['fontFamily'], ['inherit', ...array_column($fonts, 'key')], true)) { throw new InvalidPage('Die Button-Schrift ist nicht vorhanden.'); } foreach (['backgroundColor', 'color', 'hoverBackgroundColor', 'hoverColor'] as $key) { if (!is_string($value[$key]) || 1 !== preg_match(StyleSchema::COLOR, $value[$key]) || (str_starts_with($value[$key], '@') && !in_array(substr($value[$key], 1), array_column($colors, 'key'), true))) { throw new InvalidPage('Bitte prüfe die Button-Farbe: '.$key); } } return array_intersect_key($value, self::defaults($base)); } /** Palette references stay live; block variants use these same variables. */ public static function css(array $buttons): string { $color = static fn (string $v): string => str_starts_with($v, '@') ? 'var(--k-'.substr($v, 1).')' : $v; $out = [ '--kb-fill' => $color($buttons['backgroundColor']), '--kb-ink' => $color($buttons['color']), '--kb-hover-fill' => $color($buttons['hoverBackgroundColor']), '--kb-hover-ink' => $color($buttons['hoverColor']), '--kb-font' => 'inherit' === $buttons['fontFamily'] ? 'inherit' : 'var(--kf-'.$buttons['fontFamily'].')', '--kb-font-size' => $buttons['fontSize'].'px', '--kb-weight' => $buttons['fontWeight'], '--kb-tracking' => $buttons['letterSpacing'].'px', '--kb-transform' => $buttons['textTransform'], '--kb-radius' => ['square' => '0px', 'rounded' => '8px', 'pill' => '999px'][$buttons['shape']], '--kb-py' => ['small' => '10px', 'normal' => '15px', 'large' => '19px'][$buttons['size']], '--kb-px' => ['small' => '16px', 'normal' => '21px', 'large' => '30px'][$buttons['size']], ]; foreach (self::appearance($buttons['appearance']) as $key => $value) { $out['--kb-'.$key] = $value; } return ':root{'.implode(';', array_map(static fn ($key, $value): string => $key.':'.$value, array_keys($out), $out)).'}'; } public static function appearance(string $name): array { return match ($name) { 'outline' => ['bg' => 'transparent', 'fg' => 'var(--kb-fill)', 'border' => 'var(--kb-fill)', 'hover-bg' => 'var(--kb-fill)', 'hover-fg' => 'var(--kb-ink)'], 'soft' => ['bg' => 'color-mix(in srgb,var(--kb-fill) 12%,transparent)', 'fg' => 'var(--kb-fill)', 'border' => 'transparent', 'hover-bg' => 'var(--kb-fill)', 'hover-fg' => 'var(--kb-ink)'], 'link' => ['bg' => 'transparent', 'fg' => 'var(--kb-fill)', 'border' => 'transparent', 'hover-bg' => 'transparent', 'hover-fg' => 'var(--kb-hover-fill)'], default => ['bg' => 'var(--kb-fill)', 'fg' => 'var(--kb-ink)', 'border' => 'transparent', 'hover-bg' => 'var(--kb-hover-fill)', 'hover-fg' => 'var(--kb-hover-ink)'], }; } }