<?php
defined( 'ABSPATH' ) || exit;

/**
 * WooCommerce Product Categories.
 *
 * Bespoke element (not schema-driven): it runs a real product_cat term query,
 * carries per-breakpoint column settings, and offers a carousel layout — none
 * of which the generic schema path models.
 *
 * Contract, same as every other element: ONE root wrapper, scoped
 * `.jalpi-el.jalpi-{id}` CSS pushed through the renderer, every dynamic value
 * escaped, and free-text CSS run through self::css_value() (NOT bare
 * sanitize_css_value — see that method's docblock).
 *
 * Term querying lives in Jalpi_Woo_Terms so the filter logic has one home.
 */
class Jalpi_Element_Product_Categories extends Jalpi_Element {

	public static function type(): string { return 'product-categories'; }

	/**
	 * Strict validator for free-text CSS values (padding, margin, box-shadow,
	 * letter-spacing…).
	 *
	 * Jalpi_Sanitizer::sanitize_css_value() strips script vectors but PERMITS
	 * braces and semicolons, so a raw value could terminate its declaration
	 * block and inject page-wide rules. Everything this element interpolates
	 * into a stylesheet passes through here instead.
	 *
	 * @param string $val     Raw author value.
	 * @param string $default Returned when $val is empty or rejected.
	 */
	private static function css_value( string $val, string $default = '' ): string {
		$val = Jalpi_Sanitizer::sanitize_css_value( $val );
		if ( '' === $val ) {
			return $default;
		}
		if ( preg_match( '/[{};@\\\\<>]/', $val ) ) {
			return $default;
		}
		if ( false !== strpos( $val, '/*' ) || false !== strpos( $val, '*/' ) ) {
			return $default;
		}
		if ( ! preg_match( '/^[A-Za-z0-9_\-\s.,%#()\/]+$/', $val ) ) {
			return $default;
		}
		return $val;
	}

	public function render( array $node, Jalpi_Renderer $renderer ): string {
		$id       = sanitize_html_class( $node['id'] ?? '' );
		$content  = (array) ( $node['content']  ?? [] );
		$styles   = (array) ( $node['styles']   ?? [] );
		$settings = (array) ( $node['settings'] ?? [] );
		$a        = $renderer->get_css_attrs( $node );
		$sel      = '.jalpi-el.jalpi-' . $id;

		// ── Helpers ──────────────────────────────────────────────────────────
		$raw = static fn( string $k, string $d = '' ): string =>
			isset( $settings[ $k ] ) && '' !== $settings[ $k ] ? (string) $settings[ $k ] : $d;

		$px = static function ( $v, string $d = '' ): string {
			$v = (string) $v;
			if ( '' === $v ) return $d;
			if ( preg_match( '/^-?[\d.]+$/', $v ) ) return '0' === $v ? '0' : $v . 'px';
			return self::css_value( $v, $d );
		};
		$enum = static fn( string $k, array $allowed, string $d ): string =>
			in_array( $raw( $k, $d ), $allowed, true ) ? $raw( $k, $d ) : $d;

		// Read a content enum without an unguarded array read (the `?? x ? $c[x]`
		// idiom warns when the key is absent).
		$cenum = static function ( string $k, array $allowed, string $d ) use ( $content ): string {
			$v = (string) ( $content[ $k ] ?? $d );
			return in_array( $v, $allowed, true ) ? $v : $d;
		};
		$on = static fn( string $k, bool $d ): bool =>
			array_key_exists( $k, $content ) ? ! empty( $content[ $k ] ) : $d;

		// ── Layout ───────────────────────────────────────────────────────────
		$skin   = $cenum( 'skin',   [ 'classic', 'cards', 'overlay', 'minimal' ], 'classic' );
		$layout = $cenum( 'layout', [ 'grid', 'carousel', 'list' ], 'grid' );

		$cols_d = max( 1, min( 6, (int) $raw( 'pc_cols_desktop', '4' ) ) );
		$cols_t = max( 1, min( 6, (int) $raw( 'pc_cols_tablet',  '2' ) ) );
		$cols_m = max( 1, min( 6, (int) $raw( 'pc_cols_mobile',  '1' ) ) );

		$col_gap = $px( $raw( 'pc_col_gap', '24' ), '24px' );
		$row_gap = $px( $raw( 'pc_row_gap', '24' ), '24px' );
		$equal_h = $on( 'equalHeight', true );
		$align   = $enum( 'pc_align', [ 'left', 'center', 'right' ], 'center' );

		// ── Visible parts ────────────────────────────────────────────────────
		$el = [
			'image'  => $on( 'showImage',       true ),
			'name'   => $on( 'showName',        true ),
			'count'  => $on( 'showCount',       true ),
			'desc'   => $on( 'showDescription', false ),
			'button' => $on( 'showButton',      false ),
		];

		// ── Image settings ───────────────────────────────────────────────────
		$img_size  = sanitize_key( $raw( 'pc_img_size', 'woocommerce_thumbnail' ) ) ?: 'woocommerce_thumbnail';
		$img_ratio = $raw( 'pc_img_ratio', '1/1' );
		$img_ratio = ( preg_match( '#^\d+(\.\d+)?/\d+(\.\d+)?$#', $img_ratio ) || 'auto' === $img_ratio ) ? $img_ratio : '1/1';
		$img_fit   = $enum( 'pc_img_fit', [ 'cover', 'contain', 'fill', 'none', 'scale-down' ], 'cover' );
		$img_hover = $enum( 'pc_img_hover', [ 'none', 'zoom', 'fade', 'overlay', 'grayscale' ], 'zoom' );
		$lazy      = $on( 'lazyLoad', true );

		// ── Button settings ──────────────────────────────────────────────────
		$btn_text = sanitize_text_field( (string) ( $content['buttonText'] ?? '' ) )
			?: __( 'View Products', 'jalpi-page-builder' );
		$btn_icon = $enum( 'pc_btn_icon', [ 'none', 'arrow', 'chevron', 'plus', 'cart' ], 'none' );
		$btn_ipos = $enum( 'pc_btn_icon_pos', [ 'before', 'after' ], 'after' );
		$new_tab  = ! empty( $content['openNewWindow'] );

		// ── Carousel settings ────────────────────────────────────────────────
		$is_carousel = 'carousel' === $layout;
		$car = [
			'perView'  => max( 1, min( 6,    (int) ( $content['slidesPerView']  ?? $cols_d ) ) ),
			'scroll'   => max( 1, min( 6,    (int) ( $content['slidesToScroll'] ?? 1 ) ) ),
			'loop'     => $on( 'carouselLoop', true ),
			'autoplay' => max( 0, min( 30000, (int) ( $content['autoplay'] ?? 0 ) ) ),
			'speed'    => max( 100, min( 5000, (int) ( $content['carouselSpeed'] ?? 400 ) ) ),
			'pause'    => $on( 'pauseOnHover', true ),
			'arrows'   => $on( 'showArrows',   true ),
			'dots'     => $on( 'showDots',     true ),
		];

		// ── Query ────────────────────────────────────────────────────────────
		$number = max( 0, min( 100, (int) ( $content['number'] ?? 12 ) ) );
		$args   = Jalpi_Woo_Terms::build_args( $content );
		$terms  = Jalpi_Woo_Terms::run( $args, $number ?: 6 );

		// ── CSS ──────────────────────────────────────────────────────────────
		$this->emit_css( $renderer, $sel, $settings, [
			'cols_d' => $cols_d, 'cols_t' => $cols_t, 'cols_m' => $cols_m,
			'col_gap' => $col_gap, 'row_gap' => $row_gap,
			'equal_h' => $equal_h, 'align' => $align, 'skin' => $skin, 'layout' => $layout,
			'img_ratio' => $img_ratio, 'img_fit' => $img_fit, 'img_hover' => $img_hover,
			'carousel' => $car,
		], $raw, $px, $enum );

		$renderer->add_css( $sel . '{box-sizing:border-box;'
			. $renderer->bg_data_to_css( (array) ( $settings['bgNormal'] ?? [] ) )
			. $renderer->border_data_to_css( (array) ( $settings['borderNormal'] ?? [] ) )
			. $renderer->styles_to_css( $styles ) . '}' );

		$hover_css = $renderer->bg_data_to_css( (array) ( $settings['bgHover'] ?? [] ) )
			. $renderer->border_data_to_css( (array) ( $settings['borderHover'] ?? [] ) );
		if ( $hover_css ) {
			$renderer->add_css( $sel . ':hover{' . $hover_css . '}' );
		}

		if ( $is_carousel ) {
			$renderer->set_needs_js( 'generic' );
		}

		// ── Markup ───────────────────────────────────────────────────────────
		$card_opts = compact( 'el', 'skin', 'img_size', 'lazy', 'btn_text', 'btn_icon', 'btn_ipos', 'new_tab' );

		$items = '';
		foreach ( $terms as $term ) {
			$items .= $this->render_card( $term, $card_opts );
		}

		if ( '' === $items ) {
			return '<div class="jalpi-el jalpi-' . esc_attr( $id ) . $a['extra_cls'] . '"' . $a['id_attr'] . '>'
				. $this->render_empty_state( $content ) . '</div>';
		}

		$classes = 'jalpi-el jalpi-' . esc_attr( $id ) . ' jalpi-pc jalpi-pc--' . esc_attr( $skin )
			. ' jalpi-pc--' . esc_attr( $layout )
			. ( $equal_h ? ' jalpi-pc--equal' : '' )
			. $a['extra_cls'];

		$out = '<div class="' . $classes . '"' . $a['id_attr'];

		if ( $is_carousel ) {
			$cfg = [
				'perView'  => $car['perView'],
				'scroll'   => $car['scroll'],
				'loop'     => $car['loop'],
				'autoplay' => $car['autoplay'],
				'speed'    => $car['speed'],
				'pause'    => $car['pause'],
			];
			$out .= ' data-jalpi-pc-carousel="' . esc_attr( wp_json_encode( $cfg ) ) . '"';
		}
		$out .= '>';

		if ( $is_carousel ) {
			$out .= '<div class="jalpi-pc__viewport">';
			$out .= '<div class="jalpi-pc__track">' . $items . '</div>';
			$out .= '</div>';

			if ( $car['arrows'] ) {
				$out .= '<button type="button" class="jalpi-pc__arrow jalpi-pc__arrow--prev"'
					. ' aria-label="' . esc_attr__( 'Previous categories', 'jalpi-page-builder' ) . '">'
					. '<span aria-hidden="true">‹</span></button>';
				$out .= '<button type="button" class="jalpi-pc__arrow jalpi-pc__arrow--next"'
					. ' aria-label="' . esc_attr__( 'Next categories', 'jalpi-page-builder' ) . '">'
					. '<span aria-hidden="true">›</span></button>';
			}
			if ( $car['dots'] ) {
				$out .= '<div class="jalpi-pc__dots" role="tablist"></div>';
			}
		} else {
			$tag  = 'list' === $layout ? 'ul' : 'div';
			$out .= '<' . $tag . ' class="jalpi-pc__grid" role="list">' . $items . '</' . $tag . '>';
		}

		$out .= '</div>';
		return $out;
	}

	// ─────────────────────────────────────────────────────────────────────────
	// Card
	// ─────────────────────────────────────────────────────────────────────────

	/**
	 * Render one category card.
	 *
	 * @param WP_Term|Jalpi_Woo_Term_Placeholder $term
	 */
	private function render_card( $term, array $o ): string {
		$el   = $o['el'];
		$name = (string) $term->name;

		$link = ( $term instanceof Jalpi_Woo_Term_Placeholder )
			? '#'
			: (string) get_term_link( $term );
		// get_term_link() returns WP_Error for a broken term.
		if ( is_wp_error( $link ) ) {
			$link = '#';
		}

		$target = $o['new_tab'] ? ' target="_blank" rel="noopener noreferrer"' : '';

		$out  = '<div class="jalpi-pc__item" role="listitem"'
			. ' data-term-id="' . esc_attr( (string) ( $term->term_id ?? 0 ) ) . '">';
		$out .= '<a class="jalpi-pc__card" href="' . esc_url( $link ) . '"' . $target . '>';

		// ── Media ────────────────────────────────────────────────────────────
		if ( $el['image'] ) {
			$img = Jalpi_Woo_Terms::image_html( $term, $o['img_size'], $o['lazy'], 'jalpi-pc__img' );
			if ( $img ) {
				$out .= '<div class="jalpi-pc__media">' . $img;
				// The overlay skin stacks its text above the image.
				if ( 'overlay' === $o['skin'] ) {
					$out .= '<span class="jalpi-pc__scrim" aria-hidden="true"></span>';
				}
				$out .= '</div>';
			}
		}

		// ── Body ─────────────────────────────────────────────────────────────
		$out .= '<div class="jalpi-pc__body">';

		if ( $el['name'] ) {
			$out .= '<h3 class="jalpi-pc__title">' . esc_html( $name ) . '</h3>';
		}

		if ( $el['count'] ) {
			$count = (int) ( $term->count ?? 0 );
			$out  .= '<span class="jalpi-pc__count">'
				. esc_html( sprintf(
					/* translators: %s: number of products in the category */
					_n( '%s Product', '%s Products', $count, 'jalpi-page-builder' ),
					number_format_i18n( $count )
				) )
				. '</span>';
		}

		if ( $el['desc'] ) {
			$desc = (string) ( $term->description ?? '' );
			if ( '' !== $desc ) {
				$out .= '<div class="jalpi-pc__desc">' . esc_html( wp_trim_words( wp_strip_all_tags( $desc ), 18 ) ) . '</div>';
			}
		}

		if ( $el['button'] ) {
			$icon  = $this->button_icon( $o['btn_icon'] );
			$label = '<span class="jalpi-pc__btn-text">' . esc_html( $o['btn_text'] ) . '</span>';
			$inner = 'before' === $o['btn_ipos'] ? $icon . $label : $label . $icon;
			// The whole card is already a link, so this is a visual affordance
			// rather than a nested anchor (which would be invalid HTML).
			$out  .= '<span class="jalpi-pc__btn">' . $inner . '</span>';
		}

		$out .= '</div></a></div>';
		return $out;
	}

	private function button_icon( string $key ): string {
		if ( 'none' === $key ) {
			return '';
		}
		$glyphs = [ 'arrow' => '→', 'chevron' => '›', 'plus' => '＋', 'cart' => '🛒' ];
		$glyph  = $glyphs[ $key ] ?? '';
		return $glyph ? '<span class="jalpi-pc__btn-icon" aria-hidden="true">' . esc_html( $glyph ) . '</span>' : '';
	}

	private function render_empty_state( array $content ): string {
		$msg  = sanitize_text_field( (string) ( $content['emptyMessage'] ?? '' ) )
			?: __( 'No product categories found.', 'jalpi-page-builder' );
		$icon = sanitize_text_field( (string) ( $content['emptyIcon'] ?? '' ) ) ?: '🗂';

		return '<div class="jalpi-pc__empty">'
			. '<div class="jalpi-pc__empty-icon" aria-hidden="true">' . esc_html( $icon ) . '</div>'
			. '<p class="jalpi-pc__empty-text">' . esc_html( $msg ) . '</p>'
			. '</div>';
	}

	// ─────────────────────────────────────────────────────────────────────────
	// CSS
	// ─────────────────────────────────────────────────────────────────────────

	private function emit_css(
		Jalpi_Renderer $renderer, string $sel, array $settings,
		array $l, callable $raw, callable $px, callable $enum
	): void {
		$col = static fn( string $k, string $d = '' ): string =>
			Jalpi_Sanitizer::sanitize_color( (string) ( $settings[ $k ] ?? '' ) ) ?: $d;

		$gap     = $l['row_gap'] . ' ' . $l['col_gap'];
		$is_list = 'list' === $l['layout'];
		$is_car  = 'carousel' === $l['layout'];

		// ── Layout ───────────────────────────────────────────────────────────
		if ( $is_car ) {
			// Slides are sized by flex-basis from the per-view count; the track
			// translates by whole slides in JS.
			$renderer->add_css( $sel . '{position:relative;}' );
			$renderer->add_css( $sel . ' .jalpi-pc__viewport{overflow:hidden;}' );
			$renderer->add_css( $sel . ' .jalpi-pc__track{display:flex;gap:' . $l['col_gap']
				. ';transition:transform ' . (int) $l['carousel']['speed'] . 'ms ease;will-change:transform;}' );
			$renderer->add_css( $sel . ' .jalpi-pc__item{flex:0 0 calc((100% - (' . $l['col_gap']
				. ' * ' . ( $l['carousel']['perView'] - 1 ) . ')) / ' . $l['carousel']['perView'] . ');}' );
			// Tablet / mobile fall back to the responsive column counts.
			$renderer->add_tablet_css( $sel . ' .jalpi-pc__item{flex-basis:calc((100% - (' . $l['col_gap']
				. ' * ' . ( $l['cols_t'] - 1 ) . ')) / ' . $l['cols_t'] . ');}' );
			$renderer->add_mobile_css( $sel . ' .jalpi-pc__item{flex-basis:calc((100% - (' . $l['col_gap']
				. ' * ' . ( $l['cols_m'] - 1 ) . ')) / ' . $l['cols_m'] . ');}' );
		} elseif ( $is_list ) {
			$renderer->add_css( $sel . ' .jalpi-pc__grid{display:flex;flex-direction:column;gap:'
				. $l['row_gap'] . ';list-style:none;margin:0;padding:0;}' );
			// In list mode the card runs horizontally: image beside the text.
			$renderer->add_css( $sel . ' .jalpi-pc__card{display:flex;align-items:center;gap:' . $l['col_gap'] . ';}' );
			$renderer->add_css( $sel . ' .jalpi-pc__media{flex:0 0 auto;width:'
				. $px( $raw( 'pc_img_width', '96' ), '96px' ) . ';}' );
			$renderer->add_css( $sel . ' .jalpi-pc__body{flex:1 1 auto;}' );
		} else {
			$renderer->add_css( $sel . ' .jalpi-pc__grid{display:grid;grid-template-columns:repeat('
				. $l['cols_d'] . ',minmax(0,1fr));gap:' . $gap . ';}' );
			$renderer->add_tablet_css( $sel . ' .jalpi-pc__grid{grid-template-columns:repeat('
				. $l['cols_t'] . ',minmax(0,1fr));}' );
			$renderer->add_mobile_css( $sel . ' .jalpi-pc__grid{grid-template-columns:repeat('
				. $l['cols_m'] . ',minmax(0,1fr));}' );
		}

		// ── Card ─────────────────────────────────────────────────────────────
		$radius     = $px( $raw( 'pc_card_radius', '12' ), '12px' );
		$transition = max( 0, min( 3000, (int) $raw( 'pc_card_transition', '250' ) ) );
		$bd_type    = $enum( 'pc_card_bd_type', [ 'none', 'solid', 'dashed', 'dotted', 'double' ], 'none' );

		$card  = 'position:relative;display:block;overflow:hidden;text-decoration:none;color:inherit;';
		$card .= 'box-sizing:border-box;text-align:' . $l['align'] . ';';
		$card .= 'border-radius:' . $radius . ';';
		$card .= 'transition:background-color ' . $transition . 'ms ease,box-shadow ' . $transition
			. 'ms ease,border-color ' . $transition . 'ms ease,transform ' . $transition . 'ms ease;';

		if ( ! $is_list ) {
			// A horizontal list card manages its own flex layout above.
			$card .= $l['equal_h'] ? 'display:flex;flex-direction:column;height:100%;' : '';
		}
		if ( $col( 'pc_card_bg' ) )  $card .= 'background:' . $col( 'pc_card_bg' ) . ';';
		$card_pad = self::css_value( $raw( 'pc_card_pad', '' ) );
		if ( $card_pad )             $card .= 'padding:' . $card_pad . ';';
		$card_mar = self::css_value( $raw( 'pc_card_margin', '' ) );
		if ( $card_mar )             $card .= 'margin:' . $card_mar . ';';
		$shadow = self::css_value( $raw( 'pc_card_shadow', '' ) );
		if ( $shadow )               $card .= 'box-shadow:' . $shadow . ';';
		if ( 'none' !== $bd_type ) {
			$card .= 'border-style:' . $bd_type . ';';
			$card .= 'border-width:' . ( $px( $raw( 'pc_card_bd_width', '' ) ) ?: '1px' ) . ';';
			$card .= 'border-color:' . ( $col( 'pc_card_bd_color' ) ?: '#e5e7eb' ) . ';';
		}
		$renderer->add_css( $sel . ' .jalpi-pc__card{' . $card . '}' );

		$hover  = '';
		if ( $col( 'pc_card_hover_bg' ) )       $hover .= 'background:' . $col( 'pc_card_hover_bg' ) . ';';
		if ( $col( 'pc_card_hover_bd_color' ) ) $hover .= 'border-color:' . $col( 'pc_card_hover_bd_color' ) . ';';
		$hshadow = self::css_value( $raw( 'pc_card_hover_shadow', '' ) );
		if ( $hshadow )                         $hover .= 'box-shadow:' . $hshadow . ';';
		$lift = $px( $raw( 'pc_card_hover_lift', '' ) );
		if ( '' !== $lift )                     $hover .= 'transform:translateY(-' . $lift . ');';
		if ( $hover ) {
			$renderer->add_css( $sel . ' .jalpi-pc__card:hover,' . $sel . ' .jalpi-pc__card:focus-visible{' . $hover . '}' );
		}

		// ── Media ────────────────────────────────────────────────────────────
		$media = 'position:relative;overflow:hidden;line-height:0;';
		if ( ! $is_list && 'auto' !== $l['img_ratio'] ) {
			$media .= 'aspect-ratio:' . $l['img_ratio'] . ';';
		}
		$img_radius = $px( $raw( 'pc_img_radius', '' ) );
		if ( '' !== $img_radius ) $media .= 'border-radius:' . $img_radius . ';';
		$img_h = $px( $raw( 'pc_img_height', '' ) );
		if ( '' !== $img_h )      $media .= 'height:' . $img_h . ';';
		if ( ! $is_list ) {
			$img_w = $px( $raw( 'pc_img_width', '' ) );
			if ( '' !== $img_w )  $media .= 'width:' . $img_w . ';';
		}
		$renderer->add_css( $sel . ' .jalpi-pc__media{' . $media . '}' );

		$renderer->add_css( $sel . ' .jalpi-pc__img{width:100%;height:100%;display:block;object-fit:'
			. $l['img_fit'] . ';transition:transform 400ms ease,opacity 300ms ease,filter 300ms ease;}' );

		switch ( $l['img_hover'] ) {
			case 'zoom':
				$renderer->add_css( $sel . ' .jalpi-pc__card:hover .jalpi-pc__img{transform:scale(1.08);}' );
				break;
			case 'fade':
				$renderer->add_css( $sel . ' .jalpi-pc__card:hover .jalpi-pc__img{opacity:.72;}' );
				break;
			case 'grayscale':
				$renderer->add_css( $sel . ' .jalpi-pc__img{filter:grayscale(1);}' );
				$renderer->add_css( $sel . ' .jalpi-pc__card:hover .jalpi-pc__img{filter:grayscale(0);}' );
				break;
			case 'overlay':
				$ov = $col( 'pc_img_overlay_color', 'rgba(0,0,0,0.35)' );
				$renderer->add_css( $sel . ' .jalpi-pc__media::after{content:"";position:absolute;inset:0;'
					. 'background:' . $ov . ';opacity:0;transition:opacity 300ms ease;pointer-events:none;}' );
				$renderer->add_css( $sel . ' .jalpi-pc__card:hover .jalpi-pc__media::after{opacity:1;}' );
				break;
		}

		// ── Overlay skin: body sits on top of the image ──────────────────────
		if ( 'overlay' === $l['skin'] ) {
			$renderer->add_css( $sel . ' .jalpi-pc__card{display:block;}' );
			$renderer->add_css( $sel . ' .jalpi-pc__scrim{position:absolute;inset:0;pointer-events:none;'
				. 'background:linear-gradient(to top,' . $col( 'pc_overlay_color', 'rgba(0,0,0,.55)' )
				. ' 0%,transparent 70%);}' );
			$renderer->add_css( $sel . ' .jalpi-pc__body{position:absolute;left:0;right:0;bottom:0;z-index:2;'
				. 'padding:' . ( self::css_value( $raw( 'pc_body_pad', '' ) ) ?: '16px' ) . ';}' );
			// Text must stay legible over the image regardless of theme color.
			$renderer->add_css( $sel . ' .jalpi-pc__title,' . $sel . ' .jalpi-pc__count,'
				. $sel . ' .jalpi-pc__desc{color:' . $col( 'pc_title_color', '#ffffff' ) . ';}' );
		} else {
			$renderer->add_css( $sel . ' .jalpi-pc__body{padding:'
				. ( self::css_value( $raw( 'pc_body_pad', '' ) ) ?: '14px 0 0' ) . ';}' );
			if ( $l['equal_h'] && ! $is_list ) {
				$renderer->add_css( $sel . ' .jalpi-pc__body{display:flex;flex-direction:column;flex:1 1 auto;}' );
				$renderer->add_css( $sel . ' .jalpi-pc__btn{margin-top:auto;}' );
			}
		}

		// ── Title ────────────────────────────────────────────────────────────
		$title  = 'margin:' . ( self::css_value( $raw( 'pc_title_margin', '' ) ) ?: '0 0 4px' ) . ';';
		$title .= 'font-size:' . $px( $raw( 'pc_title_size', '17' ), '17px' ) . ';';
		$title .= 'font-weight:' . ( (int) $raw( 'pc_title_weight', '600' ) ?: 600 ) . ';';
		$title .= 'line-height:' . ( is_numeric( $raw( 'pc_title_lh', '1.4' ) ) ? $raw( 'pc_title_lh', '1.4' ) : '1.4' ) . ';';
		$tt     = $enum( 'pc_title_transform', [ 'none', 'uppercase', 'lowercase', 'capitalize' ], 'none' );
		if ( 'none' !== $tt ) $title .= 'text-transform:' . $tt . ';';
		$ls = $px( $raw( 'pc_title_ls', '' ) );
		if ( '' !== $ls )     $title .= 'letter-spacing:' . $ls . ';';
		$ff = self::css_value( $raw( 'pc_title_font', '' ) );
		if ( $ff )            $title .= 'font-family:' . $ff . ';';
		if ( 'overlay' !== $l['skin'] && $col( 'pc_title_color' ) ) {
			$title .= 'color:' . $col( 'pc_title_color' ) . ';';
		}
		$renderer->add_css( $sel . ' .jalpi-pc__title{' . $title . '}' );

		if ( $col( 'pc_title_hover_color' ) ) {
			$renderer->add_css( $sel . ' .jalpi-pc__card:hover .jalpi-pc__title{color:'
				. $col( 'pc_title_hover_color' ) . ';}' );
		}

		// ── Count ────────────────────────────────────────────────────────────
		$count  = 'display:inline-block;font-size:' . $px( $raw( 'pc_count_size', '13' ), '13px' ) . ';';
		$count .= 'line-height:1.5;';
		if ( 'overlay' !== $l['skin'] ) {
			$count .= 'color:' . ( $col( 'pc_count_color' ) ?: 'inherit' ) . ';';
			if ( ! $col( 'pc_count_color' ) ) $count .= 'opacity:.72;';
		}
		if ( $col( 'pc_count_bg' ) ) {
			$count .= 'background:' . $col( 'pc_count_bg' ) . ';';
			$count .= 'padding:' . ( self::css_value( $raw( 'pc_count_pad', '' ) ) ?: '2px 8px' ) . ';';
			$count .= 'border-radius:' . $px( $raw( 'pc_count_radius', '999' ), '999px' ) . ';';
		}
		$renderer->add_css( $sel . ' .jalpi-pc__count{' . $count . '}' );

		// ── Description ──────────────────────────────────────────────────────
		$desc  = 'font-size:' . $px( $raw( 'pc_desc_size', '13' ), '13px' ) . ';line-height:1.6;';
		$desc .= 'margin-top:' . $px( $raw( 'pc_desc_spacing', '6' ), '6px' ) . ';';
		if ( 'overlay' !== $l['skin'] ) {
			$desc .= 'color:' . ( $col( 'pc_desc_color' ) ?: 'inherit' ) . ';';
			if ( ! $col( 'pc_desc_color' ) ) $desc .= 'opacity:.8;';
		}
		$da = $enum( 'pc_desc_align', [ 'inherit', 'left', 'center', 'right' ], 'inherit' );
		if ( 'inherit' !== $da ) $desc .= 'text-align:' . $da . ';';
		$renderer->add_css( $sel . ' .jalpi-pc__desc{' . $desc . '}' );

		// ── Button ───────────────────────────────────────────────────────────
		$btn  = 'display:inline-flex;align-items:center;justify-content:center;gap:6px;';
		$btn .= 'margin-top:' . $px( $raw( 'pc_btn_spacing', '12' ), '12px' ) . ';';
		$btn .= 'padding:' . ( self::css_value( $raw( 'pc_btn_pad', '' ) ) ?: '8px 16px' ) . ';';
		$btn .= 'font-size:' . $px( $raw( 'pc_btn_size', '13' ), '13px' ) . ';';
		$btn .= 'font-weight:' . ( (int) $raw( 'pc_btn_weight', '600' ) ?: 600 ) . ';';
		$btn .= 'color:' . ( $col( 'pc_btn_color' ) ?: '#ffffff' ) . ';';
		$btn .= 'background:' . ( $col( 'pc_btn_bg' ) ?: '#111827' ) . ';';
		$btn .= 'border-radius:' . $px( $raw( 'pc_btn_radius', '8' ), '8px' ) . ';';
		$btn .= 'transition:background 200ms ease,color 200ms ease,border-color 200ms ease,transform 200ms ease;';

		$btn_bd = $enum( 'pc_btn_bd_type', [ 'none', 'solid', 'dashed', 'dotted' ], 'none' );
		if ( 'none' !== $btn_bd ) {
			$btn .= 'border-style:' . $btn_bd . ';';
			$btn .= 'border-width:' . ( $px( $raw( 'pc_btn_bd_width', '' ) ) ?: '1px' ) . ';';
			$btn .= 'border-color:' . ( $col( 'pc_btn_bd_color' ) ?: 'currentColor' ) . ';';
		} else {
			$btn .= 'border:none;';
		}
		$renderer->add_css( $sel . ' .jalpi-pc__btn{' . $btn . '}' );

		$bh = '';
		if ( $col( 'pc_btn_hover_color' ) ) $bh .= 'color:' . $col( 'pc_btn_hover_color' ) . ';';
		if ( $col( 'pc_btn_hover_bg' ) )    $bh .= 'background:' . $col( 'pc_btn_hover_bg' ) . ';';
		if ( $col( 'pc_btn_hover_bd' ) )    $bh .= 'border-color:' . $col( 'pc_btn_hover_bd' ) . ';';
		$anim = $enum( 'pc_btn_hover_anim', [ 'none', 'grow', 'shrink', 'float', 'sink' ], 'none' );
		$tfm  = [ 'grow' => 'scale(1.05)', 'shrink' => 'scale(.95)', 'float' => 'translateY(-3px)', 'sink' => 'translateY(3px)' ];
		if ( isset( $tfm[ $anim ] ) ) $bh .= 'transform:' . $tfm[ $anim ] . ';';
		if ( $bh ) {
			$renderer->add_css( $sel . ' .jalpi-pc__card:hover .jalpi-pc__btn{' . $bh . '}' );
		}

		// ── Carousel chrome ──────────────────────────────────────────────────
		if ( $is_car ) {
			$asize = $px( $raw( 'pc_arrow_size', '18' ), '18px' );
			$renderer->add_css( $sel . ' .jalpi-pc__arrow{position:absolute;top:50%;transform:translateY(-50%);'
				. 'z-index:3;display:inline-flex;align-items:center;justify-content:center;'
				. 'width:2.2em;height:2.2em;font-size:' . $asize . ';line-height:1;cursor:pointer;'
				. 'border:none;border-radius:50%;background:' . ( $col( 'pc_arrow_bg' ) ?: '#ffffff' )
				. ';color:' . ( $col( 'pc_arrow_color' ) ?: '#111827' )
				. ';box-shadow:0 1px 6px rgba(0,0,0,.18);transition:opacity 200ms ease;}' );
			$renderer->add_css( $sel . ' .jalpi-pc__arrow--prev{left:-8px;}' );
			$renderer->add_css( $sel . ' .jalpi-pc__arrow--next{right:-8px;}' );
			$renderer->add_css( $sel . ' .jalpi-pc__arrow[disabled]{opacity:.35;cursor:default;}' );

			$renderer->add_css( $sel . ' .jalpi-pc__dots{display:flex;justify-content:center;gap:'
				. $px( $raw( 'pc_dot_spacing', '8' ), '8px' ) . ';margin-top:16px;}' );
			$renderer->add_css( $sel . ' .jalpi-pc__dot{width:9px;height:9px;padding:0;border:none;'
				. 'border-radius:50%;cursor:pointer;background:' . ( $col( 'pc_dot_color' ) ?: '#d1d5db' )
				. ';transition:background 200ms ease,transform 200ms ease;}' );
			$renderer->add_css( $sel . ' .jalpi-pc__dot.is-active{background:'
				. ( $col( 'pc_dot_active_color' ) ?: '#111827' ) . ';transform:scale(1.25);}' );
		}

		// ── Empty state ──────────────────────────────────────────────────────
		$renderer->add_css( $sel . ' .jalpi-pc__empty{grid-column:1/-1;text-align:center;padding:44px 16px;'
			. 'color:' . ( $col( 'pc_empty_color' ) ?: '#6b7280' ) . ';}' );
		$renderer->add_css( $sel . ' .jalpi-pc__empty-icon{font-size:32px;margin-bottom:10px;opacity:.7;}' );
		$renderer->add_css( $sel . ' .jalpi-pc__empty-text{margin:0;font-size:15px;}' );
	}
}
