assets
1 week ago
BuilderPanel.php
1 week ago
Compiler.php
1 week ago
Engine.php
1 week ago
FrontendEnqueue.php
1 week ago
Migrator.php
1 week ago
Palettes.php
1 week ago
PreviewDraft.php
1 week ago
RestController.php
1 week ago
Sanitizer.php
1 week ago
Schema.php
1 week ago
Templates.php
1 week ago
Schema.php
853 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Style Customizer v2 — token Schema (single source of truth for tokens, controls, CSS vars, |
| 4 | * the sanitizer, and legacy migration). Strict subset of the bundled v1 customizer: 0 net-new |
| 5 | * settings; responsive Desktop/Tablet/Mobile applies to margin/padding only. |
| 6 | * |
| 7 | * @package EverestForms\Addons\StyleCustomizer\V2 |
| 8 | * @since x.x.x |
| 9 | */ |
| 10 | |
| 11 | namespace EverestForms\Addons\StyleCustomizer\V2; |
| 12 | |
| 13 | defined( 'ABSPATH' ) || exit; |
| 14 | |
| 15 | /** |
| 16 | * Style schema registry. |
| 17 | */ |
| 18 | final class Schema { |
| 19 | |
| 20 | /** |
| 21 | * Schema version. Persisted on each saved record as `schema_version`; the presence of |
| 22 | * that key is what flips a form onto the v2 engine (no separate option/meta). |
| 23 | */ |
| 24 | const VERSION = 1; |
| 25 | |
| 26 | /** |
| 27 | * Memoized, normalized + filtered token list. |
| 28 | * |
| 29 | * @var array|null |
| 30 | */ |
| 31 | protected static $tokens = null; |
| 32 | |
| 33 | /** |
| 34 | * Memoized key => token index. |
| 35 | * |
| 36 | * @var array|null |
| 37 | */ |
| 38 | protected static $index = null; |
| 39 | |
| 40 | /** |
| 41 | * Current schema version. |
| 42 | * |
| 43 | * @return int |
| 44 | */ |
| 45 | public static function version() { |
| 46 | return self::VERSION; |
| 47 | } |
| 48 | |
| 49 | /** |
| 50 | * Reset the memoized cache (tests / after a runtime filter change). |
| 51 | */ |
| 52 | public static function flush() { |
| 53 | self::$tokens = null; |
| 54 | self::$index = null; |
| 55 | } |
| 56 | |
| 57 | /* --------------------------------------------------------------------- * |
| 58 | * Public accessors |
| 59 | * --------------------------------------------------------------------- */ |
| 60 | |
| 61 | /** |
| 62 | * Section metadata — drives the drill-down list order, titles and variant/state tabs. |
| 63 | * |
| 64 | * @return array |
| 65 | */ |
| 66 | public static function sections() { |
| 67 | $sections = array( |
| 68 | 'form' => array( |
| 69 | 'title' => __( 'Form', 'everest-forms' ), |
| 70 | 'hint' => __( 'The canvas everything sits on — plus the form-wide font.', 'everest-forms' ), |
| 71 | ), |
| 72 | 'text' => array( |
| 73 | 'title' => __( 'Text', 'everest-forms' ), |
| 74 | 'hint' => __( 'One typography system, four roles.', 'everest-forms' ), |
| 75 | 'variants' => array( 'label', 'sublabel', 'desc', 'title' ), |
| 76 | ), |
| 77 | 'fields' => array( |
| 78 | 'title' => __( 'Inputs', 'everest-forms' ), |
| 79 | 'hint' => __( 'Text boxes, dropdowns, textareas, date pickers — and the upload area.', 'everest-forms' ), |
| 80 | 'states' => array( 'normal', 'focus' ), |
| 81 | ), |
| 82 | 'choice' => array( |
| 83 | 'title' => __( 'Choices', 'everest-forms' ), |
| 84 | 'hint' => __( 'One style set for every choice field: radios, checkboxes, Yes/No, image choices, ratings, Likert rows, and payment pickers.', 'everest-forms' ), |
| 85 | ), |
| 86 | 'button' => array( |
| 87 | 'title' => __( 'Button', 'everest-forms' ), |
| 88 | 'hint' => __( 'Styles Submit — Multi-Part and file-upload buttons follow automatically.', 'everest-forms' ), |
| 89 | 'states' => array( 'normal', 'hover' ), |
| 90 | ), |
| 91 | 'messages' => array( |
| 92 | 'title' => __( 'Messages', 'everest-forms' ), |
| 93 | 'hint' => __( 'The success, error and validation messages shown to visitors.', 'everest-forms' ), |
| 94 | 'states' => array( 'success', 'error', 'validation' ), |
| 95 | ), |
| 96 | ); |
| 97 | |
| 98 | // Product policy: every design section is Pro; only the palette picker + Custom CSS are free. |
| 99 | foreach ( $sections as &$section ) { |
| 100 | $section['tier'] = 'pro'; |
| 101 | } |
| 102 | unset( $section ); |
| 103 | |
| 104 | /** |
| 105 | * Filter the style-customizer section metadata (order, titles, variant/state tabs, tier). |
| 106 | * Pro/add-ons can add sections or adjust an existing section's tier here. |
| 107 | * |
| 108 | * @param array $sections Section definitions keyed by section id. |
| 109 | */ |
| 110 | return apply_filters( 'evf_style_sections', $sections ); |
| 111 | } |
| 112 | |
| 113 | /** |
| 114 | * The full, normalized, filterable token list. Free/Pro tiers and any add-on sections are |
| 115 | * layered in via the `evf_style_schema` filter. |
| 116 | * |
| 117 | * @return array List of token definition arrays. |
| 118 | */ |
| 119 | public static function tokens() { |
| 120 | if ( null === self::$tokens ) { |
| 121 | /** |
| 122 | * Filter the v2 style token registry. |
| 123 | * |
| 124 | * @param array $tokens Raw token definitions. |
| 125 | * @param int $version Schema version. |
| 126 | */ |
| 127 | $tokens = apply_filters( 'evf_style_schema', self::raw_tokens(), self::VERSION ); |
| 128 | $tokens = array_map( array( __CLASS__, 'normalize' ), $tokens ); |
| 129 | self::$tokens = self::apply_tier_policy( $tokens ); |
| 130 | } |
| 131 | return self::$tokens; |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * Stamp each token's tier: only the palette-driven tokens ({@see free_token_keys()}) are |
| 136 | * tier=free, everything else (including Pro/add-on contributions) is tier=pro. The sanitizer |
| 137 | * and compiler refuse to persist/emit a pro token unless {@see Engine::pro_active()}. |
| 138 | * |
| 139 | * @param array $tokens Normalized tokens. |
| 140 | * @return array |
| 141 | */ |
| 142 | protected static function apply_tier_policy( $tokens ) { |
| 143 | $free = self::free_token_keys(); |
| 144 | foreach ( $tokens as &$token ) { |
| 145 | $token['tier'] = isset( $free[ $token['key'] ] ) ? 'free' : 'pro'; |
| 146 | } |
| 147 | unset( $token ); |
| 148 | return $tokens; |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * Token keys that stay FREE — the palette-driven colours plus the derived button-hover shade. |
| 153 | * |
| 154 | * @return array Map of free token key => true. |
| 155 | */ |
| 156 | protected static function free_token_keys() { |
| 157 | $keys = array(); |
| 158 | foreach ( self::palette_map() as $token_keys ) { |
| 159 | foreach ( (array) $token_keys as $key ) { |
| 160 | $keys[ $key ] = true; |
| 161 | } |
| 162 | } |
| 163 | $keys['btn.bgHover'] = true; |
| 164 | |
| 165 | /** |
| 166 | * Filter the token keys that stay in the FREE tier (all others are Pro). |
| 167 | * |
| 168 | * @param array $keys Map of free token key => true. |
| 169 | */ |
| 170 | return apply_filters( 'evf_style_free_token_keys', $keys ); |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * Look up a single token by key. |
| 175 | * |
| 176 | * @param string $key Token key (e.g. `wrap.pad`). |
| 177 | * @return array|null |
| 178 | */ |
| 179 | public static function get( $key ) { |
| 180 | if ( null === self::$index ) { |
| 181 | self::$index = array(); |
| 182 | foreach ( self::tokens() as $token ) { |
| 183 | self::$index[ $token['key'] ] = $token; |
| 184 | } |
| 185 | } |
| 186 | return isset( self::$index[ $key ] ) ? self::$index[ $key ] : null; |
| 187 | } |
| 188 | |
| 189 | /** |
| 190 | * key => default value, for building an unstyled (default) record and for |
| 191 | * `array_replace_recursive`-style merges in the compiler. |
| 192 | * |
| 193 | * @return array |
| 194 | */ |
| 195 | public static function defaults() { |
| 196 | $out = array(); |
| 197 | foreach ( self::tokens() as $token ) { |
| 198 | $out[ $token['key'] ] = $token['default']; |
| 199 | } |
| 200 | return $out; |
| 201 | } |
| 202 | |
| 203 | /** |
| 204 | * key => CSS custom property, for the compiler (skips meta-only tokens with no var). |
| 205 | * |
| 206 | * @return array |
| 207 | */ |
| 208 | public static function css_var_map() { |
| 209 | $out = array(); |
| 210 | foreach ( self::tokens() as $token ) { |
| 211 | if ( ! empty( $token['var'] ) ) { |
| 212 | $out[ $token['key'] ] = $token['var']; |
| 213 | } |
| 214 | } |
| 215 | return $out; |
| 216 | } |
| 217 | |
| 218 | /** |
| 219 | * All 11 named colour palettes (2 free + 9 Pro), each with the old customizer's 6 colours. |
| 220 | * The 9 Pro palettes' preview metadata ships here too (unlike other pro tokens) so the panel |
| 221 | * can render a real swatch + name for all 11 and lock the Pro ones instead of hiding them; |
| 222 | * applying one is still gated by {@see Sanitizer::sanitize_palette_id()}. |
| 223 | * |
| 224 | * @return array |
| 225 | */ |
| 226 | public static function palettes() { |
| 227 | $palettes = array( |
| 228 | array( 'id' => 'classic', 'name' => __( 'Classic', 'everest-forms' ), 'is_pro' => false, 'colors' => array( 'form_background' => '#ffffff', 'field_background' => '#f6f6f6', 'field_label' => '#081d2b', 'field_sublabel' => '#0f3a57', 'button_text' => '#ffffff', 'button_background' => '#3951a5' ) ), |
| 229 | array( 'id' => 'mono', 'name' => __( 'Monochrome', 'everest-forms' ), 'is_pro' => false, 'colors' => array( 'form_background' => '#ffffff', 'field_background' => '#f7f7f7', 'field_label' => '#262626', 'field_sublabel' => '#666666', 'button_text' => '#ffffff', 'button_background' => '#1a1a1a' ) ), |
| 230 | array( 'id' => 'autumn', 'name' => __( 'Autumn Blaze', 'everest-forms' ), 'is_pro' => true, 'colors' => array( 'form_background' => '#fffafa', 'field_background' => '#fff5f5', 'field_label' => '#330300', 'field_sublabel' => '#4d0500', 'button_text' => '#ffffff', 'button_background' => '#ff5d52' ) ), |
| 231 | array( 'id' => 'sunset', 'name' => __( 'Sunset Glow', 'everest-forms' ), 'is_pro' => true, 'colors' => array( 'form_background' => '#fffdfa', 'field_background' => '#fff9f0', 'field_label' => '#664000', 'field_sublabel' => '#805100', 'button_text' => '#ffffff', 'button_background' => '#ffa305' ) ), |
| 232 | array( 'id' => 'majestic', 'name' => __( 'Majestic', 'everest-forms' ), 'is_pro' => true, 'colors' => array( 'form_background' => '#fcfbfe', 'field_background' => '#f7f4fb', 'field_label' => '#3a225d', 'field_sublabel' => '#5d3795', 'button_text' => '#ffffff', 'button_background' => '#7545bb' ) ), |
| 233 | array( 'id' => 'greenery', 'name' => __( 'Fresh Greenery', 'everest-forms' ), 'is_pro' => true, 'colors' => array( 'form_background' => '#f9fdf6', 'field_background' => '#e9f6ea', 'field_label' => '#334745', 'field_sublabel' => '#557773', 'button_text' => '#ffffff', 'button_background' => '#405956' ) ), |
| 234 | array( 'id' => 'cloudy', 'name' => __( 'Cloudy Sky', 'everest-forms' ), 'is_pro' => true, 'colors' => array( 'form_background' => '#f2f3f8', 'field_background' => '#e4e7f1', 'field_label' => '#252b41', 'field_sublabel' => '#2e3651', 'button_text' => '#ffffff', 'button_background' => '#445079' ) ), |
| 235 | array( 'id' => 'earthy', 'name' => __( 'Earthy Warm', 'everest-forms' ), 'is_pro' => true, 'colors' => array( 'form_background' => '#f7f6f0', 'field_background' => '#f1efe4', 'field_label' => '#474648', 'field_sublabel' => '#616062', 'button_text' => '#ffffff', 'button_background' => '#463700' ) ), |
| 236 | array( 'id' => 'blossom', 'name' => __( 'Blushing Blossom', 'everest-forms' ), 'is_pro' => true, 'colors' => array( 'form_background' => '#fdf7fa', 'field_background' => '#fbeff5', 'field_label' => '#532f42', 'field_sublabel' => '#824a68', 'button_text' => '#ffffff', 'button_background' => '#46102c' ) ), |
| 237 | array( 'id' => 'thunder', 'name' => __( 'Thunder', 'everest-forms' ), 'is_pro' => true, 'colors' => array( 'form_background' => '#ededed', 'field_background' => '#f7f7f7', 'field_label' => '#333333', 'field_sublabel' => '#595959', 'button_text' => '#ffffff', 'button_background' => '#1a1a1a' ) ), |
| 238 | array( 'id' => 'midnight', 'name' => __( 'Midnight Charm', 'everest-forms' ), 'is_pro' => true, 'colors' => array( 'form_background' => '#363636', 'field_background' => '#3d3d3d', 'field_label' => '#ffffff', 'field_sublabel' => '#f2f2f2', 'button_text' => '#1a1a1a', 'button_background' => '#ffffff' ) ), |
| 239 | ); |
| 240 | |
| 241 | /** |
| 242 | * Filter the available colour palettes. |
| 243 | * |
| 244 | * @param array $palettes Palette definitions. |
| 245 | */ |
| 246 | return apply_filters( 'evf_style_palettes', $palettes ); |
| 247 | } |
| 248 | |
| 249 | /** |
| 250 | * Palette slot => token key(s) it drives. `button_background` also derives `btn.bgHover`. |
| 251 | * |
| 252 | * @return array |
| 253 | */ |
| 254 | public static function palette_map() { |
| 255 | return array( |
| 256 | 'form_background' => array( 'wrap.bg' ), |
| 257 | 'field_background' => array( 'input.bg' ), |
| 258 | 'field_label' => array( 'label.color', 'title.color' ), |
| 259 | 'field_sublabel' => array( 'sub.color', 'desc.color' ), |
| 260 | 'button_text' => array( 'btn.color' ), |
| 261 | 'button_background' => array( 'btn.bg', 'input.focusBorder', 'choice.checked', 'file.icon' ), |
| 262 | ); |
| 263 | } |
| 264 | |
| 265 | /** |
| 266 | * The free-tier token VALUES a registered palette id implies (the six palette-driven slots, |
| 267 | * plus the derived hover shade) — the single source of truth {@see Sanitizer::sanitize_record()} |
| 268 | * uses to DERIVE (never trust raw from the client) a non-Pro site's palette-driven tokens. |
| 269 | * |
| 270 | * Without this, a non-Pro site's "free" token keys only exist so the 2-swatch palette picker |
| 271 | * has something to write to — the sanitizer only ever validated their VALUE TYPE (a well-formed |
| 272 | * colour), not that the colour actually came from picking one of the 2 free palettes. A crafted |
| 273 | * request (an AI response, a hand-built REST call) could set any hex it liked on these exact |
| 274 | * keys and reconstruct a full Pro-palette look with no Pro required (see EVF-2708). |
| 275 | * |
| 276 | * @param string $palette_id Palette id (see {@see self::palettes()}). |
| 277 | * @return array Token key => hex value (bare, not a device bag); empty if the id is unknown. |
| 278 | */ |
| 279 | public static function palette_token_values( $palette_id ) { |
| 280 | foreach ( self::palettes() as $palette ) { |
| 281 | if ( $palette['id'] !== $palette_id ) { |
| 282 | continue; |
| 283 | } |
| 284 | $colors = isset( $palette['colors'] ) && is_array( $palette['colors'] ) ? $palette['colors'] : array(); |
| 285 | $out = array(); |
| 286 | foreach ( self::palette_map() as $slot => $keys ) { |
| 287 | if ( ! isset( $colors[ $slot ] ) ) { |
| 288 | continue; |
| 289 | } |
| 290 | foreach ( (array) $keys as $key ) { |
| 291 | $out[ $key ] = $colors[ $slot ]; |
| 292 | } |
| 293 | } |
| 294 | if ( ! empty( $colors['button_background'] ) ) { |
| 295 | $out['btn.bgHover'] = self::mix_hex( $colors['button_background'], '#000000', 0.14 ); |
| 296 | } |
| 297 | return $out; |
| 298 | } |
| 299 | return array(); |
| 300 | } |
| 301 | |
| 302 | /** |
| 303 | * Linear-interpolate two hex colours — PHP port of `mixHex()` in constants.ts (kept local |
| 304 | * rather than reusing {@see Migrator}'s copy, so this class has no cross-class coupling). |
| 305 | * |
| 306 | * @param string $a First hex colour (`#rgb` or `#rrggbb`). |
| 307 | * @param string $b Second hex colour. |
| 308 | * @param float $t Mix factor, 0 = $a, 1 = $b. |
| 309 | * @return string `#rrggbb`. |
| 310 | */ |
| 311 | protected static function mix_hex( $a, $b, $t ) { |
| 312 | $parse = static function ( $hex ) { |
| 313 | $s = ltrim( (string) $hex, '#' ); |
| 314 | if ( 3 === strlen( $s ) ) { |
| 315 | $s = $s[0] . $s[0] . $s[1] . $s[1] . $s[2] . $s[2]; |
| 316 | } |
| 317 | return array( hexdec( substr( $s, 0, 2 ) ), hexdec( substr( $s, 2, 2 ) ), hexdec( substr( $s, 4, 2 ) ) ); |
| 318 | }; |
| 319 | $from = $parse( $a ); |
| 320 | $to = $parse( $b ); |
| 321 | $out = '#'; |
| 322 | foreach ( array( 0, 1, 2 ) as $i ) { |
| 323 | $out .= str_pad( dechex( (int) round( $from[ $i ] + ( $to[ $i ] - $from[ $i ] ) * $t ) ), 2, '0', STR_PAD_LEFT ); |
| 324 | } |
| 325 | return $out; |
| 326 | } |
| 327 | |
| 328 | /* --------------------------------------------------------------------- * |
| 329 | * Token assembly |
| 330 | * --------------------------------------------------------------------- */ |
| 331 | |
| 332 | /** |
| 333 | * Raw (un-normalized) token list, in panel display order. |
| 334 | * |
| 335 | * @return array |
| 336 | */ |
| 337 | protected static function raw_tokens() { |
| 338 | $border = self::border_options(); |
| 339 | |
| 340 | $form = array( |
| 341 | array( 'key' => 'fonts.theme', 'section' => 'form', 'group' => 'Typography', 'label' => __( 'Use theme fonts', 'everest-forms' ), 'type' => 'toggle', 'default' => false, 'hidden' => true, 'keywords' => array( 'inherit', 'typeface', 'theme font' ) ), |
| 342 | array( 'key' => 'fonts.family', 'section' => 'form', 'group' => 'Typography', 'label' => __( 'Font family', 'everest-forms' ), 'type' => 'select', 'var' => '--evf-font', 'default' => '', 'source' => 'google_fonts', 'options' => self::font_options(), 'keywords' => array( 'typeface', 'typography', 'google font' ) ), |
| 343 | // Palette-driven by default (form_background slot); editing it directly here unlinks |
| 344 | // this form from the palette for just this token — see store.ts's paletteMap unlink. |
| 345 | array( 'key' => 'wrap.bg', 'section' => 'form', 'group' => 'Background', 'label' => __( 'Background color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-wrap-bg', 'default' => '#ffffff', 'gradientable' => true, 'keywords' => array( 'fill', 'color', 'gradient' ) ), |
| 346 | array( 'key' => 'wrap.bgImage', 'section' => 'form', 'group' => 'Background', 'label' => __( 'Background image', 'everest-forms' ), 'type' => 'media', 'var' => '--evf-wrap-image', 'default' => '', 'advanced' => true, 'keywords' => array( 'image', 'photo', 'picture' ) ), |
| 347 | array( 'key' => 'wrap.bgPreset', 'section' => 'form', 'group' => 'Background', 'label' => __( 'Background preset', 'everest-forms' ), 'type' => 'select', 'default' => 'custom', 'options' => self::opts( array( 'custom' => __( 'Custom', 'everest-forms' ), 'fill' => __( 'Fill', 'everest-forms' ), 'fit' => __( 'Fit', 'everest-forms' ), 'repeat' => __( 'Repeat', 'everest-forms' ), 'center' => __( 'Center', 'everest-forms' ) ) ), 'advanced' => true, 'show_when_image' => true, 'keywords' => array( 'image', 'background' ) ), |
| 348 | array( 'key' => 'wrap.bgSize', 'section' => 'form', 'group' => 'Background', 'label' => __( 'Image size', 'everest-forms' ), 'type' => 'select', 'var' => '--evf-wrap-bg-size', 'default' => 'cover', 'options' => self::opts( array( 'cover' => __( 'Cover', 'everest-forms' ), 'contain' => __( 'Contain', 'everest-forms' ), 'auto' => __( 'Auto', 'everest-forms' ) ) ), 'advanced' => true, 'show_when_image' => true, 'keywords' => array( 'image', 'background' ) ), |
| 349 | array( 'key' => 'wrap.bgPosition', 'section' => 'form', 'group' => 'Background', 'label' => __( 'Image position', 'everest-forms' ), 'type' => 'select', 'var' => '--evf-wrap-bg-position', 'default' => 'center', 'options' => self::opts( array( 'center' => __( 'Center', 'everest-forms' ), 'top' => __( 'Top', 'everest-forms' ), 'bottom' => __( 'Bottom', 'everest-forms' ), 'left' => __( 'Left', 'everest-forms' ), 'right' => __( 'Right', 'everest-forms' ) ) ), 'advanced' => true, 'show_when_image' => true, 'keywords' => array( 'image', 'background' ) ), |
| 350 | array( 'key' => 'wrap.bgRepeat', 'section' => 'form', 'group' => 'Background', 'label' => __( 'Repeat background image', 'everest-forms' ), 'type' => 'select', 'var' => '--evf-wrap-bg-repeat', 'default' => 'no-repeat', 'options' => self::opts( array( 'no-repeat' => __( 'No repeat', 'everest-forms' ), 'repeat' => __( 'Repeat', 'everest-forms' ), 'repeat-x' => __( 'Repeat X', 'everest-forms' ), 'repeat-y' => __( 'Repeat Y', 'everest-forms' ) ) ), 'advanced' => true, 'show_when_image' => true, 'keywords' => array( 'image', 'tile' ) ), |
| 351 | array( 'key' => 'wrap.bgAttachment', 'section' => 'form', 'group' => 'Background', 'label' => __( 'Scroll with page', 'everest-forms' ), 'type' => 'select', 'var' => '--evf-wrap-bg-attachment', 'default' => 'scroll', 'options' => self::opts( array( 'scroll' => __( 'Scroll', 'everest-forms' ), 'fixed' => __( 'Fixed', 'everest-forms' ) ) ), 'advanced' => true, 'show_when_image' => true, 'keywords' => array( 'parallax', 'image' ) ), |
| 352 | array( 'key' => 'wrap.bgOpacity', 'section' => 'form', 'group' => 'Background', 'label' => __( 'Image opacity', 'everest-forms' ), 'type' => 'slider', 'var' => '--evf-wrap-bg-opacity', 'default' => 1, 'min' => 0, 'max' => 1, 'step' => 0.1, 'unit' => '', 'advanced' => true, 'show_when_image' => true, 'keywords' => array( 'image', 'transparency' ) ), |
| 353 | array( 'key' => 'wrap.width', 'section' => 'form', 'group' => 'Layout', 'label' => __( 'Width', 'everest-forms' ), 'type' => 'slider', 'var' => '--evf-wrap-width', 'default' => 100, 'min' => 40, 'max' => 100, 'step' => 1, 'unit' => '%', 'advanced' => true, 'keywords' => array( 'size', 'narrow' ) ), |
| 354 | self::box( 'wrap.margin', __( 'Form margin', 'everest-forms' ), '--evf-wrap-margin', array( 0, 0, 0, 0 ), 'form', null, -100, 100 ), |
| 355 | self::box( 'wrap.pad', __( 'Form padding', 'everest-forms' ), '--evf-wrap-pad', array( 32, 32, 32, 32 ), 'form', null, 0, 100 ), |
| 356 | array( 'key' => 'wrap.borderStyle', 'section' => 'form', 'group' => 'Border', 'label' => __( 'Border type', 'everest-forms' ), 'type' => 'select', 'var' => '--evf-wrap-border-style', 'default' => 'solid', 'options' => $border, 'deps' => array( 'wrap.bw', 'wrap.borderC' ), 'advanced' => true, 'keywords' => array( 'outline' ) ), |
| 357 | self::bwidth( 'wrap.bw', '--evf-wrap-bw', 1, 'form', null, 'Border', true ), |
| 358 | array( 'key' => 'wrap.borderC', 'section' => 'form', 'group' => 'Border', 'label' => __( 'Border color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-wrap-border-c', 'default' => '#ececf1', 'advanced' => true ), |
| 359 | self::radius( 'wrap.radius', '--evf-wrap-radius', 14, 'form', null, 'Border', false ), |
| 360 | ); |
| 361 | |
| 362 | $fields = array( |
| 363 | // Palette-driven by default (field_background slot); editing it directly here unlinks |
| 364 | // this form from the palette for just this token — see store.ts's paletteMap unlink. |
| 365 | array( 'key' => 'input.bg', 'section' => 'fields', 'group' => 'Surface', 'label' => __( 'Background', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-input-bg', 'default' => '#ffffff', 'gradientable' => true, 'keywords' => array( 'fill', 'color', 'gradient' ) ), |
| 366 | array( 'key' => 'input.borderC', 'section' => 'fields', 'group' => 'Surface', 'label' => __( 'Border color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-input-border-c', 'default' => '#969696' ), |
| 367 | self::radius( 'input.radius', '--evf-input-radius', 3, 'fields', null, 'Surface', false ), |
| 368 | array( 'key' => 'input.size', 'section' => 'fields', 'group' => 'Typography', 'label' => __( 'Font size', 'everest-forms' ), 'type' => 'slider', 'var' => '--evf-input-size', 'default' => 14, 'min' => 11, 'max' => 22, 'step' => 1, 'unit' => 'px', 'advanced' => true, 'keywords' => array( 'text' ) ), |
| 369 | // Not gradientable: this rule's element already spends its one `background` on |
| 370 | // input.bg — background-clip:text for the text colour would collide with it. |
| 371 | array( 'key' => 'input.color', 'section' => 'fields', 'group' => 'Typography', 'label' => __( 'Text color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-input-color', 'default' => '#575757', 'advanced' => true ), |
| 372 | array( 'key' => 'input.ph', 'section' => 'fields', 'group' => 'Typography', 'label' => __( 'Placeholder color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-input-ph', 'default' => '#c6ccd7', 'advanced' => true, 'keywords' => array( 'hint' ) ), |
| 373 | self::fstyle( 'input.fstyle', 'input', 'fields', null, '400' ), |
| 374 | self::letter_spacing( 'input.ls', '--evf-input-ls', 'fields', null ), |
| 375 | self::talign( 'input.align', '--evf-input-align', 'fields', null ), |
| 376 | array( 'key' => 'input.borderStyle', 'section' => 'fields', 'group' => 'Border', 'label' => __( 'Border type', 'everest-forms' ), 'type' => 'select', 'var' => '--evf-input-border-style', 'default' => 'solid', 'options' => $border, 'deps' => array( 'input.bw', 'input.borderC' ), 'advanced' => true ), |
| 377 | self::bwidth( 'input.bw', '--evf-input-bw', 1, 'fields', null, 'Border', true ), |
| 378 | array( 'key' => 'input.pad', 'section' => 'fields', 'group' => 'Spacing', 'label' => __( 'Field padding', 'everest-forms' ), 'type' => 'box4', 'var' => '--evf-input-pad', 'default' => array( 6, 12, 6, 12 ), 'responsive' => true, 'advanced' => true, 'keywords' => array( 'inner' ) ), |
| 379 | array( 'key' => 'field.margin', 'section' => 'fields', 'group' => 'Spacing', 'label' => __( 'Field margin', 'everest-forms' ), 'type' => 'box4', 'var' => '--evf-field-margin', 'default' => array( 0, 0, 10, 0 ), 'responsive' => true, 'advanced' => true, 'keywords' => array( 'gap', 'between' ) ), |
| 380 | array( 'key' => 'file.size', 'section' => 'fields', 'group' => 'Upload area', 'label' => __( 'Font size', 'everest-forms' ), 'type' => 'slider', 'var' => '--evf-file-size', 'default' => 14, 'min' => 10, 'max' => 22, 'step' => 1, 'unit' => 'px', 'advanced' => true, 'keywords' => array( 'upload', 'file' ) ), |
| 381 | array( 'key' => 'file.color', 'section' => 'fields', 'group' => 'Upload area', 'label' => __( 'Font color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-file-color', 'default' => '#494d50', 'advanced' => true, 'gradientable' => true, 'keywords' => array( 'upload', 'file', 'text', 'gradient' ) ), |
| 382 | array( 'key' => 'file.bg', 'section' => 'fields', 'group' => 'Upload area', 'label' => __( 'Background', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-file-bg', 'default' => 'rgba(255,255,255,0.99)', 'advanced' => true, 'gradientable' => true, 'keywords' => array( 'upload', 'dropzone', 'file', 'gradient' ) ), |
| 383 | array( 'key' => 'file.iconBg', 'section' => 'fields', 'group' => 'Upload area', 'label' => __( 'Icon background', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-file-iconbg', 'default' => 'rgba(255,255,255,0.99)', 'advanced' => true, 'gradientable' => true, 'keywords' => array( 'upload', 'file', 'gradient' ) ), |
| 384 | array( 'key' => 'file.icon', 'section' => 'fields', 'group' => 'Upload area', 'label' => __( 'Icon color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-file-icon', 'default' => '#494d50', 'advanced' => true, 'keywords' => array( 'upload', 'file' ) ), |
| 385 | array( 'key' => 'file.borderStyle', 'section' => 'fields', 'group' => 'Upload area', 'label' => __( 'Border type', 'everest-forms' ), 'type' => 'select', 'var' => '--evf-file-border-style', 'default' => 'dashed', 'options' => $border, 'advanced' => true, 'keywords' => array( 'upload', 'file' ) ), |
| 386 | self::bwidth( 'file.bw', '--evf-file-bw', 1, 'fields', null, 'Upload area', true ), |
| 387 | array( 'key' => 'file.border', 'section' => 'fields', 'group' => 'Upload area', 'label' => __( 'Border color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-file-border', 'default' => '#8e98a2', 'advanced' => true, 'keywords' => array( 'upload', 'file' ) ), |
| 388 | self::radius( 'file.radius', '--evf-file-radius', 0, 'fields', null, 'Upload area', true ), |
| 389 | array( 'key' => 'file.margin', 'section' => 'fields', 'group' => 'Upload area', 'label' => __( 'Margin', 'everest-forms' ), 'type' => 'box4', 'var' => '--evf-file-margin', 'default' => array( 0, 0, 10, 0 ), 'responsive' => true, 'advanced' => true, 'keywords' => array( 'upload', 'file', 'spacing' ) ), |
| 390 | array( 'key' => 'file.pad', 'section' => 'fields', 'group' => 'Upload area', 'label' => __( 'Padding', 'everest-forms' ), 'type' => 'box4', 'var' => '--evf-file-pad', 'default' => array( 6, 12, 6, 12 ), 'responsive' => true, 'advanced' => true, 'keywords' => array( 'upload', 'file', 'spacing', 'inner' ) ), |
| 391 | // Applies only when no palette is set — a palette still derives this per palette_map(). |
| 392 | array( 'key' => 'input.focusBorder', 'section' => 'fields', 'group' => '', 'label' => __( 'Focus border color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-input-focus-border', 'default' => '#7ca8eb', 'state' => 'focus', 'keywords' => array( 'outline', 'ring', 'active' ) ), |
| 393 | ); |
| 394 | |
| 395 | $choice = array( |
| 396 | array( 'key' => 'choice.checked', 'section' => 'choice', 'group' => '', 'label' => __( 'Selected color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-choice-checked', 'default' => '#575757', 'keywords' => array( 'checked', 'radio', 'checkbox', 'rating', 'scale', 'likert', 'payment', 'yes no', 'image choice', 'star' ) ), |
| 397 | array( 'key' => 'choice.size', 'section' => 'choice', 'group' => '', 'label' => __( 'Mark size', 'everest-forms' ), 'type' => 'slider', 'var' => '--evf-choice-size', 'default' => 16, 'min' => 12, 'max' => 28, 'step' => 1, 'unit' => 'px', 'keywords' => array( 'radio', 'checkbox' ) ), |
| 398 | array( 'key' => 'choice.border', 'section' => 'choice', 'group' => '', 'label' => __( 'Unselected border', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-choice-border', 'default' => '#d8dae2', 'keywords' => array( 'radio', 'checkbox', 'unchecked' ) ), |
| 399 | array( 'key' => 'choice.variation', 'section' => 'choice', 'group' => '', 'label' => __( 'Style variation', 'everest-forms' ), 'type' => 'select', 'default' => 'default', 'options' => self::opts( array( 'default' => __( 'Default', 'everest-forms' ), 'filled' => __( 'Filled', 'everest-forms' ), 'outline' => __( 'Outline', 'everest-forms' ) ) ), 'advanced' => true, 'special' => 'cvar', 'keywords' => array( 'modern', 'inline' ) ), |
| 400 | array( 'key' => 'choice.fsize', 'section' => 'choice', 'group' => '', 'label' => __( 'Option font size', 'everest-forms' ), 'type' => 'slider', 'var' => '--evf-choice-fsize', 'default' => 14, 'min' => 11, 'max' => 22, 'step' => 1, 'unit' => 'px', 'advanced' => true, 'keywords' => array( 'text' ) ), |
| 401 | array( 'key' => 'choice.color', 'section' => 'choice', 'group' => '', 'label' => __( 'Option text color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-choice-color', 'default' => '#575757', 'advanced' => true, 'gradientable' => true, 'keywords' => array( 'text', 'gradient' ) ), |
| 402 | self::fstyle( 'choice.fstyle', 'choice', 'choice', null, '400' ), |
| 403 | self::letter_spacing( 'choice.ls', '--evf-choice-ls', 'choice', null ), |
| 404 | self::talign( 'choice.align', '--evf-choice-align', 'choice', null ), |
| 405 | self::box( 'choice.margin', __( 'Margin', 'everest-forms' ), '--evf-choice-margin', array( 0, 0, 5, 0 ), 'choice', null ), |
| 406 | ); |
| 407 | |
| 408 | $button = array( |
| 409 | // Palette-driven by default (button_background/button_text slots); editing either |
| 410 | // directly here unlinks this form from the palette for just that one token. |
| 411 | array( 'key' => 'btn.bg', 'section' => 'button', 'group' => '', 'label' => __( 'Button color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-btn-bg', 'default' => '#0073aa', 'gradientable' => true, 'keywords' => array( 'submit', 'background', 'color', 'gradient' ) ), |
| 412 | // Not gradientable: shares its rule's `background` with btn.bg (see input.color). |
| 413 | array( 'key' => 'btn.color', 'section' => 'button', 'group' => '', 'label' => __( 'Text color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-btn-color', 'default' => '#ffffff', 'keywords' => array( 'submit', 'color' ) ), |
| 414 | self::radius( 'btn.radius', '--evf-btn-radius', 3, 'button', null, '', false ), |
| 415 | // No 'var': like choice.variation, this drives a wrapper class (`evf-btn-width-fill`) |
| 416 | // instead of a CSS variable — see FrontendEnqueue::container_class() (real frontend) |
| 417 | // and PreviewBridge.ts (live preview). New property, no v1 equivalent, so old records |
| 418 | // simply get the default ('fit') — today's exact behavior — no migration needed. |
| 419 | array( 'key' => 'btn.widthMode', 'section' => 'button', 'group' => '', 'label' => __( 'Button width', 'everest-forms' ), 'type' => 'select', 'default' => 'fit', 'options' => self::opts( array( 'fit' => __( 'Fit content', 'everest-forms' ), 'fill' => __( 'Fill width', 'everest-forms' ) ) ), 'keywords' => array( 'full width', 'block', 'stretch', 'size' ) ), |
| 420 | array( 'key' => 'btn.size', 'section' => 'button', 'group' => '', 'label' => __( 'Font size', 'everest-forms' ), 'type' => 'slider', 'var' => '--evf-btn-size', 'default' => 14, 'min' => 11, 'max' => 24, 'step' => 1, 'unit' => 'px', 'advanced' => true, 'keywords' => array( 'submit' ) ), |
| 421 | self::fstyle( 'btn.fstyle', 'btn', 'button', null, '400' ), |
| 422 | self::letter_spacing( 'btn.ls', '--evf-btn-ls', 'button', null ), |
| 423 | // 'left', not 'center': matches both v1's own implicit default and the built-in |
| 424 | // "Default" template's explicit value — a legacy form that never touched button |
| 425 | // alignment has no token for it at all (see Migrator), so this default IS its |
| 426 | // rendered alignment; 'center' silently flipped it on migration (EVF-2732). |
| 427 | self::talign( 'btn.align', '--evf-btn-align', 'button', null, 'left' ), |
| 428 | self::line( 'btn.line', '--evf-btn-lh', 1.5, 'button', null ), |
| 429 | self::box( 'btn.margin', __( 'Margin', 'everest-forms' ), '--evf-btn-margin', array( 0, 0, 0, 0 ), 'button', null ), |
| 430 | array( 'key' => 'btn.pad', 'section' => 'button', 'group' => '', 'label' => __( 'Padding', 'everest-forms' ), 'type' => 'box4', 'var' => '--evf-btn-pad', 'default' => array( 10, 15, 10, 15 ), 'responsive' => true, 'advanced' => true, 'keywords' => array( 'size' ) ), |
| 431 | array( 'key' => 'btn.borderStyle', 'section' => 'button', 'group' => '', 'label' => __( 'Border type', 'everest-forms' ), 'type' => 'select', 'var' => '--evf-btn-border-style', 'default' => 'solid', 'options' => $border, 'deps' => array( 'btn.bw', 'btn.borderC' ), 'advanced' => true, 'keywords' => array( 'outline' ) ), |
| 432 | self::bwidth( 'btn.bw', '--evf-btn-bw', 1, 'button', null, '', true ), |
| 433 | array( 'key' => 'btn.borderC', 'section' => 'button', 'group' => '', 'label' => __( 'Border color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-btn-border-c', 'default' => '#cccccc', 'advanced' => true ), |
| 434 | // Palette-driven; the default only shows on a no-palette form. |
| 435 | array( 'key' => 'btn.bgHover', 'section' => 'button', 'group' => '', 'label' => __( 'Button color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-btn-bg-hover', 'default' => '#eeeeee', 'state' => 'hover', 'gradientable' => true, 'keywords' => array( 'mouse over', 'gradient' ) ), |
| 436 | array( 'key' => 'btn.colorHover', 'section' => 'button', 'group' => '', 'label' => __( 'Text color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-btn-color-hover', 'default' => '#23282d', 'state' => 'hover', 'keywords' => array( 'mouse over', 'hover font' ) ), |
| 437 | array( 'key' => 'btn.borderCHover', 'section' => 'button', 'group' => '', 'label' => __( 'Border color', 'everest-forms' ), 'type' => 'color', 'var' => '--evf-btn-border-c-hover', 'default' => '#cccccc', 'state' => 'hover', 'keywords' => array( 'mouse over', 'hover border' ) ), |
| 438 | ); |
| 439 | |
| 440 | // Messages (tier=pro) live in the Pro plugin, injected via `evf_style_schema` only when |
| 441 | // Pro is active — a free schema physically has no pro tokens to sanitize or compile. |
| 442 | return array_merge( |
| 443 | $form, |
| 444 | self::text_role_tokens(), |
| 445 | $fields, |
| 446 | $choice, |
| 447 | $button |
| 448 | ); |
| 449 | } |
| 450 | |
| 451 | /** |
| 452 | * The four Text roles (Label / Sublabel / Description / Section Title), 7 controls each. |
| 453 | * Label & sublabel colour are palette-driven (hidden). Margin & padding are responsive. |
| 454 | * |
| 455 | * @return array |
| 456 | */ |
| 457 | protected static function text_role_tokens() { |
| 458 | $roles = array( |
| 459 | // state key => [ var-prefix, size, min, max, color, neutral-weight, margin, line, pad, color-hidden ]. |
| 460 | // label/sublabel color: palette-driven by default (field_label/field_sublabel slots); |
| 461 | // editing directly here unlinks this form from the palette for just that one token — |
| 462 | // same as title/desc below, which have always worked this way. |
| 463 | 'label' => array( 'sub' => 'label', 'size' => 14, 'min' => 10, 'max' => 24, 'color' => '#333333', 'nw' => '600', 'margin' => array( 0, 0, 10, 0 ), 'line' => 1.7, 'pad' => array( 0, 0, 0, 0 ), 'color_hidden' => false ), |
| 464 | 'sublabel' => array( 'sub' => 'sub', 'size' => 12, 'min' => 9, 'max' => 20, 'color' => '#666666', 'nw' => '400', 'margin' => array( 0, 0, 10, 0 ), 'line' => 1.5, 'pad' => array( 0, 0, 0, 0 ), 'color_hidden' => false ), |
| 465 | 'desc' => array( 'sub' => 'desc', 'size' => 14, 'min' => 9, 'max' => 20, 'color' => '#575757', 'nw' => '400', 'margin' => array( 0, 0, 10, 0 ), 'line' => 1.7, 'pad' => array( 0, 0, 0, 0 ), 'color_hidden' => false ), |
| 466 | 'title' => array( 'sub' => 'title', 'size' => 16, 'min' => 12, 'max' => 34, 'color' => '#575757', 'nw' => '700', 'margin' => array( 25, 0, 25, 0 ), 'line' => 1.5, 'pad' => array( 0, 0, 0, 0 ), 'color_hidden' => false ), |
| 467 | ); |
| 468 | |
| 469 | $out = array(); |
| 470 | foreach ( $roles as $state => $r ) { |
| 471 | $vp = $r['sub']; |
| 472 | $out[] = array( 'key' => "{$vp}.size", 'section' => 'text', 'state' => $state, 'group' => '', 'label' => __( 'Font size', 'everest-forms' ), 'type' => 'slider', 'var' => "--evf-{$vp}-size", 'default' => $r['size'], 'min' => $r['min'], 'max' => $r['max'], 'step' => 1, 'unit' => 'px', 'keywords' => array( 'text', $state ) ); |
| 473 | $out[] = array( 'key' => "{$vp}.color", 'section' => 'text', 'state' => $state, 'group' => '', 'label' => __( 'Color', 'everest-forms' ), 'type' => 'color', 'var' => "--evf-{$vp}-color", 'default' => $r['color'], 'hidden' => $r['color_hidden'], 'gradientable' => true, 'keywords' => array( 'text', $state, 'gradient' ) ); |
| 474 | $out[] = self::fstyle( "{$vp}.fstyle", $vp, 'text', $state, $r['nw'] ); |
| 475 | $out[] = self::letter_spacing( "{$vp}.ls", "--evf-{$vp}-ls", 'text', $state ); |
| 476 | $out[] = self::talign( "{$vp}.align", "--evf-{$vp}-align", 'text', $state ); |
| 477 | $out[] = self::line( "{$vp}.line", "--evf-{$vp}-lh", $r['line'], 'text', $state ); |
| 478 | $out[] = self::box( "{$vp}.margin", __( 'Margin', 'everest-forms' ), "--evf-{$vp}-margin", $r['margin'], 'text', $state ); |
| 479 | $out[] = self::box( "{$vp}.pad", __( 'Padding', 'everest-forms' ), "--evf-{$vp}-pad", $r['pad'], 'text', $state ); |
| 480 | } |
| 481 | return $out; |
| 482 | } |
| 483 | |
| 484 | /* --------------------------------------------------------------------- * |
| 485 | * Token builders (mixins) — shared shape factory, also used by Pro/add-ons |
| 486 | * contributing tier=pro tokens through the `evf_style_schema` filter. |
| 487 | * --------------------------------------------------------------------- */ |
| 488 | |
| 489 | /** |
| 490 | * Font-style control (Bold / Italic / Underline / Uppercase, plus an optional Font weight). |
| 491 | * |
| 492 | * `weight` (an explicit numeric weight, e.g. '600') is an ADDITIVE sub-property alongside the |
| 493 | * original four flags — old stored values simply don't have this key, `!empty($v['weight'])` |
| 494 | * is false for them, and font-weight keeps deriving from `bold`/`neutral_weight` exactly as |
| 495 | * before (see Compiler::declarations() and cssVars.ts's tokenDeclarations()). No migration |
| 496 | * step needed: a missing key is indistinguishable from '' (the "Auto" default here), by design. |
| 497 | * |
| 498 | * @param string $key Token key. |
| 499 | * @param string $base CSS var base (e.g. `label`, `input`, `msg-success`). |
| 500 | * @param string $section Section key. |
| 501 | * @param string $state Variant/state (nullable). |
| 502 | * @param string $nw Neutral (non-bold) font-weight. |
| 503 | * @return array |
| 504 | */ |
| 505 | public static function fstyle( $key, $base, $section, $state, $nw ) { |
| 506 | return array( |
| 507 | 'key' => $key, |
| 508 | 'section' => $section, |
| 509 | 'state' => $state, |
| 510 | 'group' => '', |
| 511 | 'label' => __( 'Font style', 'everest-forms' ), |
| 512 | 'type' => 'fontstyle', |
| 513 | 'vars' => array( |
| 514 | 'weight' => "--evf-{$base}-weight", |
| 515 | 'style' => "--evf-{$base}-fs", |
| 516 | 'decoration' => "--evf-{$base}-td", |
| 517 | 'transform' => "--evf-{$base}-tt", |
| 518 | ), |
| 519 | 'neutral_weight' => $nw, |
| 520 | 'weight_options' => self::weight_options(), |
| 521 | 'default' => array( |
| 522 | 'bold' => false, |
| 523 | 'italic' => false, |
| 524 | 'underline' => false, |
| 525 | 'uppercase' => false, |
| 526 | 'weight' => '', |
| 527 | ), |
| 528 | 'advanced' => true, |
| 529 | 'keywords' => array( 'bold', 'italic', 'underline', 'uppercase', 'font style', 'font weight', 'weight' ), |
| 530 | ); |
| 531 | } |
| 532 | |
| 533 | /** |
| 534 | * Selectable font-weight values for the `fstyle` control's Font weight dropdown. |
| 535 | * '' ("Auto") keeps the original Bold-toggle-only behavior. |
| 536 | * |
| 537 | * @return array value => label. |
| 538 | */ |
| 539 | protected static function weight_options() { |
| 540 | // EVF-2721: this used to offer all 9 standard CSS weight steps (100-900), but a form's |
| 541 | // font — a specific Google Font, or "Theme default" falling back to the visitor's system |
| 542 | // font stack — only actually HAS a handful of real weight faces. A numeric weight with no |
| 543 | // matching face just gets browser-synthesized (inconsistently, sometimes identically to a |
| 544 | // neighboring value), so half the dropdown silently did nothing. Trimmed to the 3 steps |
| 545 | // virtually every font (Google or system) truly ships, and {@see evfsc_enqueue_fonts}/ |
| 546 | // PreviewBridge now request exactly these weights so a chosen Google Font's real files |
| 547 | // load instead of relying on synthesis. |
| 548 | // |
| 549 | // Built directly (not via self::opts(), which takes an associative array — PHP would |
| 550 | // coerce these numeric-string keys to ints) so every value is guaranteed a string. |
| 551 | return array( |
| 552 | array( 'value' => '', 'label' => __( 'Auto', 'everest-forms' ) ), |
| 553 | array( 'value' => '300', 'label' => __( 'Light', 'everest-forms' ) ), |
| 554 | array( 'value' => '400', 'label' => __( 'Regular', 'everest-forms' ) ), |
| 555 | array( 'value' => '700', 'label' => __( 'Bold', 'everest-forms' ) ), |
| 556 | ); |
| 557 | } |
| 558 | |
| 559 | /** |
| 560 | * Line-height slider (1–3, single value). |
| 561 | * |
| 562 | * @param string $key Token key. |
| 563 | * @param string $var CSS var. |
| 564 | * @param float $default Default line-height. |
| 565 | * @param string $section Section key. |
| 566 | * @param string $state Variant/state (nullable). |
| 567 | * @return array |
| 568 | */ |
| 569 | protected static function line( $key, $var, $default, $section, $state ) { |
| 570 | return array( |
| 571 | 'key' => $key, |
| 572 | 'section' => $section, |
| 573 | 'state' => $state, |
| 574 | 'group' => '', |
| 575 | 'label' => __( 'Line height', 'everest-forms' ), |
| 576 | 'type' => 'slider', |
| 577 | 'var' => $var, |
| 578 | 'default' => $default, |
| 579 | 'min' => 1, |
| 580 | 'max' => 3, |
| 581 | 'step' => 0.1, |
| 582 | 'unit' => '', |
| 583 | 'advanced' => true, |
| 584 | 'keywords' => array( 'leading', 'line height' ), |
| 585 | ); |
| 586 | } |
| 587 | |
| 588 | /** |
| 589 | * Letter-spacing slider (-2px to 10px, single value). New, additive sibling of `line()` — |
| 590 | * every caller of `self::fstyle()` gets one alongside it, so it's added wherever font-weight |
| 591 | * was. No v1 equivalent ever existed, so a missing value here just means "not customized yet" |
| 592 | * — the schema default (0, i.e. `normal`) — same as any other brand-new token; no migration |
| 593 | * step needed. |
| 594 | * |
| 595 | * @param string $key Token key. |
| 596 | * @param string $var CSS var. |
| 597 | * @param string $section Section key. |
| 598 | * @param string $state Variant/state (nullable). |
| 599 | * @return array |
| 600 | */ |
| 601 | protected static function letter_spacing( $key, $var, $section, $state ) { |
| 602 | return array( |
| 603 | 'key' => $key, |
| 604 | 'section' => $section, |
| 605 | 'state' => $state, |
| 606 | 'group' => '', |
| 607 | 'label' => __( 'Letter spacing', 'everest-forms' ), |
| 608 | 'type' => 'slider', |
| 609 | 'var' => $var, |
| 610 | 'default' => 0, |
| 611 | 'min' => -2, |
| 612 | 'max' => 10, |
| 613 | 'step' => 0.1, |
| 614 | 'unit' => 'px', |
| 615 | 'advanced' => true, |
| 616 | 'keywords' => array( 'tracking', 'letter spacing', 'kerning' ), |
| 617 | ); |
| 618 | } |
| 619 | |
| 620 | /** |
| 621 | * Alignment control (left/center/right). Single value — never responsive. |
| 622 | * |
| 623 | * @param string $key Token key. |
| 624 | * @param string $var CSS var. |
| 625 | * @param string $section Section key. |
| 626 | * @param string $state Variant/state (nullable). |
| 627 | * @param string $default Default alignment. |
| 628 | * @return array |
| 629 | */ |
| 630 | public static function talign( $key, $var, $section, $state, $default = 'left' ) { |
| 631 | return array( |
| 632 | 'key' => $key, |
| 633 | 'section' => $section, |
| 634 | 'state' => $state, |
| 635 | 'group' => '', |
| 636 | 'label' => __( 'Alignment', 'everest-forms' ), |
| 637 | 'type' => 'align', |
| 638 | 'var' => $var, |
| 639 | 'default' => $default, |
| 640 | 'advanced' => true, |
| 641 | 'keywords' => array( 'left', 'center', 'right', 'alignment' ), |
| 642 | ); |
| 643 | } |
| 644 | |
| 645 | /** |
| 646 | * Margin / padding box (4-side). THE responsive control type — Desktop/Tablet/Mobile. |
| 647 | * |
| 648 | * @param string $key Token key. |
| 649 | * @param string $label Control label. |
| 650 | * @param string $var CSS var. |
| 651 | * @param array $default [top,right,bottom,left]. |
| 652 | * @param string $section Section key. |
| 653 | * @param string $state Variant/state (nullable). |
| 654 | * @param int $min Optional per-side lower bound (null = type default). |
| 655 | * @param int $max Optional per-side upper bound (null = type default). |
| 656 | * @return array |
| 657 | */ |
| 658 | protected static function box( $key, $label, $var, $default, $section, $state, $min = null, $max = null ) { |
| 659 | $out = array( |
| 660 | 'key' => $key, |
| 661 | 'section' => $section, |
| 662 | 'state' => $state, |
| 663 | 'group' => '', |
| 664 | 'label' => $label, |
| 665 | 'type' => 'box4', |
| 666 | 'var' => $var, |
| 667 | 'default' => $default, |
| 668 | 'responsive' => true, |
| 669 | 'advanced' => true, |
| 670 | 'keywords' => array( 'spacing' ), |
| 671 | ); |
| 672 | if ( null !== $min ) { |
| 673 | $out['min'] = $min; |
| 674 | } |
| 675 | if ( null !== $max ) { |
| 676 | $out['max'] = $max; |
| 677 | } |
| 678 | return $out; |
| 679 | } |
| 680 | |
| 681 | /** |
| 682 | * Border-width box (4-side, single value — border width was never responsive). |
| 683 | * |
| 684 | * @param string $key Token key. |
| 685 | * @param string $var CSS var. |
| 686 | * @param int $d Default (applied to all four sides). |
| 687 | * @param string $section Section key. |
| 688 | * @param string $state Variant/state (nullable). |
| 689 | * @param string $group Group label. |
| 690 | * @param bool $advanced Whether behind the "advanced" reveal. |
| 691 | * @return array |
| 692 | */ |
| 693 | public static function bwidth( $key, $var, $d, $section, $state, $group, $advanced = true ) { |
| 694 | return array( |
| 695 | 'key' => $key, |
| 696 | 'section' => $section, |
| 697 | 'state' => $state, |
| 698 | 'group' => $group, |
| 699 | 'label' => __( 'Border width', 'everest-forms' ), |
| 700 | 'type' => 'box4', |
| 701 | 'var' => $var, |
| 702 | 'default' => array( $d, $d, $d, $d ), |
| 703 | 'max' => 12, |
| 704 | 'advanced' => $advanced, |
| 705 | 'keywords' => array( 'thickness', 'border' ), |
| 706 | ); |
| 707 | } |
| 708 | |
| 709 | /** |
| 710 | * Border-radius box (4-corner + px/% unit, single value). |
| 711 | * |
| 712 | * @param string $key Token key. |
| 713 | * @param string $var CSS var. |
| 714 | * @param int $d Default (applied to all four corners). |
| 715 | * @param string $section Section key. |
| 716 | * @param string $state Variant/state (nullable). |
| 717 | * @param string $group Group label. |
| 718 | * @param bool $advanced Whether behind the "advanced" reveal. |
| 719 | * @return array |
| 720 | */ |
| 721 | public static function radius( $key, $var, $d, $section, $state, $group, $advanced ) { |
| 722 | return array( |
| 723 | 'key' => $key, |
| 724 | 'section' => $section, |
| 725 | 'state' => $state, |
| 726 | 'group' => $group, |
| 727 | 'label' => __( 'Border radius', 'everest-forms' ), |
| 728 | 'type' => 'box4', |
| 729 | 'var' => $var, |
| 730 | 'default' => array( $d, $d, $d, $d ), |
| 731 | 'max' => 60, |
| 732 | 'units' => array( 'px', '%' ), |
| 733 | 'corners' => true, |
| 734 | 'advanced' => $advanced, |
| 735 | 'keywords' => array( 'corner', 'rounded' ), |
| 736 | ); |
| 737 | } |
| 738 | |
| 739 | /* --------------------------------------------------------------------- * |
| 740 | * Helpers |
| 741 | * --------------------------------------------------------------------- */ |
| 742 | |
| 743 | /** |
| 744 | * Shared border-type options — the full set the legacy customizer offered. |
| 745 | * |
| 746 | * @return array |
| 747 | */ |
| 748 | public static function border_options() { |
| 749 | return self::opts( |
| 750 | array( |
| 751 | 'none' => __( 'None', 'everest-forms' ), |
| 752 | 'hidden' => __( 'Hidden', 'everest-forms' ), |
| 753 | 'dotted' => __( 'Dotted', 'everest-forms' ), |
| 754 | 'dashed' => __( 'Dashed', 'everest-forms' ), |
| 755 | 'solid' => __( 'Solid', 'everest-forms' ), |
| 756 | 'double' => __( 'Double', 'everest-forms' ), |
| 757 | 'groove' => __( 'Groove', 'everest-forms' ), |
| 758 | 'ridge' => __( 'Ridge', 'everest-forms' ), |
| 759 | 'inset' => __( 'Inset', 'everest-forms' ), |
| 760 | 'outset' => __( 'Outset', 'everest-forms' ), |
| 761 | 'initial' => __( 'Initial', 'everest-forms' ), |
| 762 | 'inherit' => __( 'Inherit', 'everest-forms' ), |
| 763 | ) |
| 764 | ); |
| 765 | } |
| 766 | |
| 767 | /** |
| 768 | * Font-family options. Only "Theme default" is baked in; the full Google Fonts list is |
| 769 | * supplied to the panel at runtime via the REST payload's `google_fonts`. |
| 770 | * |
| 771 | * @return array |
| 772 | */ |
| 773 | protected static function font_options() { |
| 774 | return self::opts( |
| 775 | array( |
| 776 | '' => __( 'Theme default', 'everest-forms' ), |
| 777 | ) |
| 778 | ); |
| 779 | } |
| 780 | |
| 781 | /** |
| 782 | * Convert a value=>label map into the panel's [{value,label}] option shape. |
| 783 | * |
| 784 | * @param array $map value => label. |
| 785 | * @return array |
| 786 | */ |
| 787 | protected static function opts( $map ) { |
| 788 | $out = array(); |
| 789 | foreach ( $map as $value => $label ) { |
| 790 | $out[] = array( |
| 791 | 'value' => $value, |
| 792 | 'label' => $label, |
| 793 | ); |
| 794 | } |
| 795 | return $out; |
| 796 | } |
| 797 | |
| 798 | /** |
| 799 | * Fill every token with its default keys so consumers can rely on the full shape. |
| 800 | * |
| 801 | * @param array $token Raw token. |
| 802 | * @return array |
| 803 | */ |
| 804 | protected static function normalize( $token ) { |
| 805 | $token = array_merge( |
| 806 | array( |
| 807 | 'key' => '', |
| 808 | 'section' => '', |
| 809 | 'group' => '', |
| 810 | 'label' => '', |
| 811 | 'type' => '', |
| 812 | 'var' => null, |
| 813 | 'default' => null, |
| 814 | 'state' => null, |
| 815 | 'responsive' => false, |
| 816 | 'advanced' => false, |
| 817 | 'hidden' => false, |
| 818 | 'tier' => 'free', |
| 819 | 'keywords' => array(), |
| 820 | ), |
| 821 | $token |
| 822 | ); |
| 823 | |
| 824 | // Box defaults are authored compactly as [t,r,b,l]; store/emit them as the associative |
| 825 | // {top,right,bottom,left} shape the legacy engine uses, so migration is an identity copy. |
| 826 | if ( 'box4' === $token['type'] && is_array( $token['default'] ) && isset( $token['default'][0] ) ) { |
| 827 | $token['default'] = self::to_dim( $token['default'], ! empty( $token['units'] ) ? $token['units'][0] : null ); |
| 828 | } |
| 829 | |
| 830 | return $token; |
| 831 | } |
| 832 | |
| 833 | /** |
| 834 | * [t,r,b,l] → { top, right, bottom, left [, unit ] }. |
| 835 | * |
| 836 | * @param array $box Indexed sides. |
| 837 | * @param string|null $unit Optional unit (radius). |
| 838 | * @return array |
| 839 | */ |
| 840 | protected static function to_dim( $box, $unit = null ) { |
| 841 | $dim = array( |
| 842 | 'top' => isset( $box[0] ) ? $box[0] : 0, |
| 843 | 'right' => isset( $box[1] ) ? $box[1] : 0, |
| 844 | 'bottom' => isset( $box[2] ) ? $box[2] : 0, |
| 845 | 'left' => isset( $box[3] ) ? $box[3] : 0, |
| 846 | ); |
| 847 | if ( null !== $unit ) { |
| 848 | $dim['unit'] = $unit; |
| 849 | } |
| 850 | return $dim; |
| 851 | } |
| 852 | } |
| 853 |