<?php
/**
 * 外部追加コーデの検証・保存・CSS生成を担当します。
 *
 * 追加ファイルは宣言的なJSONデータだけを受け付け、任意のCSS、HTML、
 * JavaScript、PHP、URLは保存も実行もしません。
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

final class AICHI_Palette_Coordinates {
	const OPTION_KEY     = 'aichi_palette_custom_coordinates_v1';
	const MAX_FILE_BYTES = 1572864;
	const MAX_ACTIVE     = 40;
	const FORMAT_NAME    = 'ai-chiichan-palette-coordinate';
	const SCHEMA_VERSION = 3;
	const ASSET_SCHEMA_VERSION = 2;
	const LEGACY_SCHEMA_VERSION = 1;
	const MAX_ASSET_BYTES       = 49152;
	const MAX_ASSET_DIMENSION   = 512;
	const MAX_BACKGROUND_ASSET_BYTES     = 524288;
	const MAX_BACKGROUND_ASSET_DIMENSION = 2400;
	const UPLOAD_SUBDIR = 'ai-chiichan-palette/coordinates';

	/**
	 * REST APIを登録します。
	 */
	public static function boot() {
		add_action( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) );
		add_filter( 'body_class', array( __CLASS__, 'filter_body_classes' ) );
	}

	/**
	 * コーデの追加・削除は、サイト設定を管理できるユーザーだけに許可します。
	 */
	public static function can_manage() {
		return current_user_can( 'manage_options' );
	}

	/**
	 * 追加済みコーデを保存するREST APIです。
	 */
	public static function register_rest_routes() {
		register_rest_route(
			'aichi-palette/v1',
			'/coordinates',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( __CLASS__, 'rest_add_coordinate' ),
				'permission_callback' => array( __CLASS__, 'can_manage' ),
			)
		);

		register_rest_route(
			'aichi-palette/v1',
			'/coordinates/(?P<id>[a-z0-9-]+)',
			array(
				'methods'             => WP_REST_Server::DELETABLE,
				'callback'            => array( __CLASS__, 'rest_archive_coordinate' ),
				'permission_callback' => array( __CLASS__, 'can_manage' ),
				'args'                => array(
					'id' => array(
						'type'              => 'string',
						'required'          => true,
						'sanitize_callback' => 'sanitize_key',
					),
				),
			)
		);
	}

	/**
	 * 有効な追加コーデだけを編集画面へ渡します。
	 */
	public static function get_active_public_coordinates() {
		$public = array();

		foreach ( self::get_all_coordinates() as $coordinate ) {
			if ( empty( $coordinate['active'] ) ) {
				continue;
			}
			$public[] = self::to_public_coordinate( $coordinate );
		}

		return $public;
	}

	/**
	 * 追加コーデを登録します。
	 */
	public static function rest_add_coordinate( WP_REST_Request $request ) {
		$input = $request->get_param( 'coordinate' );

		if ( ! is_array( $input ) ) {
			return self::error( 'invalid_coordinate', 'このファイルはAIちいちゃんパレット用コーデとして読み込めません。', 400 );
		}

		$encoded = wp_json_encode( $input, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
		if ( false === $encoded || strlen( $encoded ) > self::MAX_FILE_BYTES ) {
			return self::error( 'coordinate_too_large', 'コーデファイルのサイズが大きすぎます。', 413 );
		}

		$coordinate = self::validate_coordinate( $input, $encoded );
		if ( is_wp_error( $coordinate ) ) {
			return $coordinate;
		}

		$coordinate = self::persist_background_assets( $coordinate );
		if ( is_wp_error( $coordinate ) ) {
			return $coordinate;
		}

		$coordinates = self::get_all_coordinates();
		$is_reactivation = isset( $coordinates[ $coordinate['id'] ] ) && empty( $coordinates[ $coordinate['id'] ]['active'] );
		if ( isset( $coordinates[ $coordinate['id'] ] ) && ! $is_reactivation ) {
			return self::error( 'coordinate_exists', 'このコーデはすでに追加されています。', 409 );
		}

		$active_count = 0;
		foreach ( $coordinates as $stored_coordinate ) {
			if ( ! empty( $stored_coordinate['active'] ) ) {
				$active_count++;
			}
		}
		if ( $active_count >= self::MAX_ACTIVE ) {
			return self::error( 'coordinate_limit', '追加できるコーデ数の上限に達しています。', 400 );
		}

		$coordinate['active']   = true;
		$coordinate['added_at'] = gmdate( 'c' );
		$coordinates[ $coordinate['id'] ] = $coordinate;

		if ( ! update_option( self::OPTION_KEY, $coordinates, false ) ) {
			$saved = get_option( self::OPTION_KEY, array() );
			if ( ! is_array( $saved ) || ! isset( $saved[ $coordinate['id'] ] ) ||
				empty( $saved[ $coordinate['id'] ]['active'] ) ||
				empty( $saved[ $coordinate['id'] ]['version'] ) ||
				$coordinate['version'] !== $saved[ $coordinate['id'] ]['version'] ) {
				return self::error( 'coordinate_save_failed', 'コーデを保存できませんでした。もう一度お試しください。', 500 );
			}
		}

		return rest_ensure_response(
			array(
				'message'    => $is_reactivation ? '🎉 コーデを再追加しました' : '🎉 コーデを追加しました',
				'coordinate' => self::to_public_coordinate( $coordinate ),
				'css'        => self::get_editor_css( absint( $request->get_param( 'postId' ) ) ),
			)
		);
	}

	/**
	 * パレットからは非表示にしますが、既存記事を守るためCSS定義は保持します。
	 */
	public static function rest_archive_coordinate( WP_REST_Request $request ) {
		$id = (string) $request->get_param( 'id' );
		if ( ! preg_match( '/^chiichan-[a-z0-9][a-z0-9-]{1,54}$/', $id ) ) {
			return self::error( 'invalid_coordinate_id', '削除するコーデを確認できませんでした。', 400 );
		}

		$coordinates = self::get_all_coordinates();
		if ( ! isset( $coordinates[ $id ] ) || empty( $coordinates[ $id ]['active'] ) ) {
			return self::error( 'coordinate_not_found', 'この追加コーデは見つかりませんでした。', 404 );
		}

		$coordinates[ $id ]['active']      = false;
		$coordinates[ $id ]['archived_at'] = gmdate( 'c' );
		if ( ! update_option( self::OPTION_KEY, $coordinates, false ) ) {
			$saved = get_option( self::OPTION_KEY, array() );
			if ( ! is_array( $saved ) || ! isset( $saved[ $id ] ) || ! empty( $saved[ $id ]['active'] ) ) {
				return self::error( 'coordinate_delete_failed', 'コーデを削除できませんでした。もう一度お試しください。', 500 );
			}
		}

		return rest_ensure_response(
			array(
				'message' => 'コーデを一覧から削除しました。既存記事の装飾は維持されます。',
				'id'      => $id,
				'css'     => self::get_editor_css( absint( $request->get_param( 'postId' ) ), array( $id ) ),
			)
		);
	}

	/**
	 * 保存済みの全コーデから、公開画面と編集画面で使うCSSを生成します。
	 * アーカイブ済みも含め、過去の記事の見た目を維持します。
	 */
	public static function get_all_css() {
		return self::generate_css( self::get_all_coordinates() );
	}

	/**
	 * 編集画面は、一覧に表示する有効コーデと編集中の記事が使用中の
	 * アーカイブ済みコーデだけを読み込みます。
	 */
	public static function get_editor_css( $post_id = 0, $extra_ids = array() ) {
		$coordinates = self::get_all_coordinates();
		$selected    = array();
		$used_ids    = array_values( array_unique( array_merge( self::coordinate_ids_from_post( $post_id ), (array) $extra_ids ) ) );

		foreach ( $coordinates as $id => $coordinate ) {
			if ( ! empty( $coordinate['active'] ) || in_array( $id, $used_ids, true ) ) {
				$selected[ $id ] = $coordinate;
			}
		}
		return self::generate_css( $selected );
	}

	/**
	 * 公開画面は、そのリクエスト内の記事で実際に使用している
	 * 追加コーデだけを読み込みます。
	 */
	public static function get_front_css() {
		global $wp_query;
		$ids = array();
		if ( isset( $wp_query->posts ) && is_array( $wp_query->posts ) ) {
			foreach ( $wp_query->posts as $post ) {
				if ( $post instanceof WP_Post ) {
					$ids = array_merge( $ids, self::coordinate_ids_from_content( $post->post_content ) );
				}
			}
		}
		$ids         = array_values( array_unique( $ids ) );
		$coordinates = self::get_all_coordinates();
		$selected    = array();
		foreach ( $ids as $id ) {
			if ( isset( $coordinates[ $id ] ) ) {
				$selected[ $id ] = $coordinates[ $id ];
			}
		}
		return self::generate_css( $selected );
	}

	/**
	 * 記事内へ保存したページコーデ識別クラスをbodyへ引き継ぎます。
	 */
	public static function filter_body_classes( $classes ) {
		if ( ! is_singular() ) {
			return $classes;
		}
		$post = get_queried_object();
		if ( ! $post instanceof WP_Post ) {
			return $classes;
		}
		if ( preg_match( '/(?:^|[\s"\'])aichi-ai-page-set-(chiichan-[a-z0-9][a-z0-9-]{1,54})(?=[\s"\']|$)/', $post->post_content, $match ) ) {
			$classes[] = 'aichi-ai-page-set-' . sanitize_html_class( $match[1] );
		}
		return array_values( array_unique( $classes ) );
	}

	/**
	 * JSONの形式と値を厳密に検証し、安全な項目だけへ正規化します。
	 */
	private static function validate_coordinate( $input, $encoded ) {
		$allowed_top_keys = array(
			'format',
			'schemaVersion',
			'id',
			'version',
			'name',
			'description',
			'icon',
			'style',
			'fontBody',
			'fontHeading',
			'headingDesign',
			'assets',
			'pageBackground',
		);
		$unknown_keys = array_diff( array_keys( $input ), $allowed_top_keys );
		if ( $unknown_keys ) {
			return self::error( 'unknown_coordinate_field', '許可されていない項目が含まれているため、このコーデは追加できません。', 400 );
		}

		if ( preg_match( '/<\s*script|<\?php|javascript\s*:|data\s*:\s*text\/html|on[a-z]+\s*=/i', $encoded ) ) {
			return self::error( 'dangerous_coordinate_data', '危険なコードが含まれているため、このコーデは追加できません。', 400 );
		}

		if ( ! isset( $input['format'] ) || self::FORMAT_NAME !== $input['format'] ) {
			return self::error( 'invalid_coordinate_format', 'このファイルはAIちいちゃんパレット用コーデとして読み込めません。', 400 );
		}
		$schema_version = isset( $input['schemaVersion'] ) ? (int) $input['schemaVersion'] : 0;
		if ( ! in_array( $schema_version, array( self::LEGACY_SCHEMA_VERSION, self::ASSET_SCHEMA_VERSION, self::SCHEMA_VERSION ), true ) ) {
			return self::error( 'unsupported_coordinate_schema', 'このコーデファイルの形式には対応していません。', 400 );
		}
		if ( self::LEGACY_SCHEMA_VERSION === $schema_version && isset( $input['assets'] ) ) {
			return self::error( 'unsupported_coordinate_assets', '画像素材を含むコーデには新しいファイル形式が必要です。', 400 );
		}

		$id = isset( $input['id'] ) && is_string( $input['id'] ) ? $input['id'] : '';
		if ( ! preg_match( '/^chiichan-[a-z0-9][a-z0-9-]{1,54}$/', $id ) ) {
			return self::error( 'invalid_coordinate_id', 'コーデIDが正しくありません。', 400 );
		}

		$version = isset( $input['version'] ) && is_string( $input['version'] ) ? $input['version'] : '';
		if ( ! preg_match( '/^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z0-9.-]+)?$/i', $version ) || strlen( $version ) > 32 ) {
			return self::error( 'invalid_coordinate_version', 'コーデのバージョンが正しくありません。', 400 );
		}

		$name        = self::plain_text( isset( $input['name'] ) ? $input['name'] : '', 120 );
		$description = self::plain_text( isset( $input['description'] ) ? $input['description'] : '', 480 );
		$icon        = self::plain_text( isset( $input['icon'] ) ? $input['icon'] : '', 32 );
		if ( is_wp_error( $name ) || '' === $name ) {
			return self::error( 'invalid_coordinate_name', 'コーデ名が正しくありません。', 400 );
		}
		if ( is_wp_error( $description ) || '' === $description ) {
			return self::error( 'invalid_coordinate_description', 'コーデの説明が正しくありません。', 400 );
		}
		if ( is_wp_error( $icon ) || '' === $icon ) {
			return self::error( 'invalid_coordinate_icon', 'コーデのアイコンが正しくありません。', 400 );
		}

		$style = self::validate_style( isset( $input['style'] ) ? $input['style'] : null );
		if ( is_wp_error( $style ) ) {
			return $style;
		}

		$font_body    = self::validate_font( isset( $input['fontBody'] ) ? $input['fontBody'] : null );
		$font_heading = self::validate_font( isset( $input['fontHeading'] ) ? $input['fontHeading'] : null );
		if ( false === $font_body || false === $font_heading ) {
			return self::error( 'invalid_coordinate_font', '指定された書体には対応していません。', 400 );
		}

		$heading_design = self::validate_heading_design( isset( $input['headingDesign'] ) ? $input['headingDesign'] : null );
		if ( false === $heading_design ) {
			return self::error( 'invalid_coordinate_heading_design', '指定された見出しデザインには対応していません。', 400 );
		}

		$assets = self::validate_assets( isset( $input['assets'] ) ? $input['assets'] : null, $schema_version );
		if ( is_wp_error( $assets ) ) {
			return $assets;
		}

		$page_background = self::validate_page_background(
			isset( $input['pageBackground'] ) ? $input['pageBackground'] : null,
			$assets,
			$schema_version
		);
		if ( is_wp_error( $page_background ) ) {
			return $page_background;
		}

		return array(
			'format'        => self::FORMAT_NAME,
			'schemaVersion' => $schema_version,
			'id'            => $id,
			'version'       => $version,
			'name'          => $name,
			'description'   => $description,
			'icon'          => $icon,
			'style'         => $style,
			'fontBody'      => $font_body,
			'fontHeading'   => $font_heading,
			'headingDesign' => $heading_design,
			'assets'        => $assets,
			'pageBackground' => $page_background,
		);
	}

	/**
	 * 任意CSSは受け取らず、決められた6色と角丸値だけを許可します。
	 */
	private static function validate_style( $style ) {
		$color_keys = array( 'primary', 'secondary', 'accent', 'background', 'surface', 'text' );
		$allowed    = array_merge( $color_keys, array( 'radius' ) );

		if ( ! is_array( $style ) || array_diff( array_keys( $style ), $allowed ) ) {
			return self::error( 'invalid_coordinate_style', 'コーデのスタイル情報が正しくありません。', 400 );
		}

		$normalized = array();
		foreach ( $color_keys as $key ) {
			$value = isset( $style[ $key ] ) && is_string( $style[ $key ] ) ? strtolower( $style[ $key ] ) : '';
			if ( ! preg_match( '/^#[0-9a-f]{6}$/', $value ) ) {
				return self::error( 'invalid_coordinate_color', 'コーデに使用できない色が含まれています。', 400 );
			}
			$normalized[ $key ] = $value;
		}

		$radius = isset( $style['radius'] ) ? filter_var( $style['radius'], FILTER_VALIDATE_INT ) : 16;
		if ( false === $radius || $radius < 0 || $radius > 32 ) {
			return self::error( 'invalid_coordinate_radius', '角丸の値が正しくありません。', 400 );
		}
		$normalized['radius'] = (int) $radius;

		return $normalized;
	}

	/**
	 * 既存の安全な書体IDだけを許可します。noneは現在の書体を維持します。
	 */
	private static function validate_font( $font ) {
		if ( null === $font || '' === $font || 'none' === $font ) {
			return null;
		}
		if ( ! is_string( $font ) || ! in_array( $font, array( 'gothic', 'rounded', 'mincho', 'original' ), true ) ) {
			return false;
		}
		return $font;
	}

	/**
	 * 任意CSSは受け取らず、パレット本体が用意した固定見出しデザインだけを許可します。
	 * 未指定の追加コーデは、これまでどおり標準テンプレートを使用します。
	 */
	private static function validate_heading_design( $design ) {
		if ( null === $design || '' === $design || 'default' === $design ) {
			return null;
		}
		if ( ! is_string( $design ) || ! in_array( $design, array( 'nordic-christmas-h2-v1', 'nordic-christmas-h2-v2', 'nordic-christmas-h2-v3', 'christmas-card-h2-v2', 'christmas-card-h2-v2-finish', 'christmas-card-h2-v2-refinish', 'christmas-card-h2-v2-final', 'christmas-card-h2-h3-v1', 'christmas-card-complete-v1' ), true ) ) {
			return false;
		}
		return $design;
	}

	/**
	 * コーデに同梱できる画像は、固定の装飾枠・背景枠のPNG／WebP／JPGだけです。
	 * URL、SVG、ファイルパス、HTMLは受け付けません。
	 */
	private static function validate_assets( $assets, $schema_version ) {
		if ( null === $assets ) {
			return array();
		}
		$standard_slots   = array( 'heading', 'accent', 'subheading', 'special', 'qa' );
		$background_slots = array( 'backgroundDesktop', 'backgroundMobile' );
		$allowed_slots    = self::SCHEMA_VERSION === $schema_version
			? array_merge( $standard_slots, $background_slots )
			: $standard_slots;
		if ( ! is_array( $assets ) || array_diff( array_keys( $assets ), $allowed_slots ) ) {
			return self::error( 'invalid_coordinate_assets', 'コーデの画像素材情報が正しくありません。', 400 );
		}

		$allowed_mimes = array( 'image/png', 'image/webp', 'image/jpeg' );
		$normalized    = array();
		foreach ( $assets as $slot => $asset ) {
			if ( ! is_array( $asset ) || array_diff( array_keys( $asset ), array( 'mime', 'data' ) ) ) {
				return self::error( 'invalid_coordinate_asset', 'コーデの画像素材が正しくありません。', 400 );
			}
			$mime = isset( $asset['mime'] ) && is_string( $asset['mime'] ) ? strtolower( $asset['mime'] ) : '';
			$data = isset( $asset['data'] ) && is_string( $asset['data'] ) ? $asset['data'] : '';
			if ( ! in_array( $mime, $allowed_mimes, true ) || '' === $data || ! preg_match( '/^[A-Za-z0-9+\/=]+$/', $data ) ) {
				return self::error( 'invalid_coordinate_asset_format', '使用できない画像形式が含まれています。', 400 );
			}

			$is_background = in_array( $slot, $background_slots, true );
			$max_bytes     = $is_background ? self::MAX_BACKGROUND_ASSET_BYTES : self::MAX_ASSET_BYTES;
			$max_dimension = $is_background ? self::MAX_BACKGROUND_ASSET_DIMENSION : self::MAX_ASSET_DIMENSION;
			$binary = base64_decode( $data, true );
			if ( false === $binary || 0 === strlen( $binary ) || strlen( $binary ) > $max_bytes ) {
				return self::error( 'coordinate_asset_too_large', 'コーデの画像素材が大きすぎるか、壊れています。', 400 );
			}

			$image_info = function_exists( 'getimagesizefromstring' ) ? @getimagesizefromstring( $binary ) : false;
			if ( false === $image_info || empty( $image_info['mime'] ) || $mime !== strtolower( $image_info['mime'] ) ) {
				return self::error( 'invalid_coordinate_asset_binary', '画像素材の内容を確認できませんでした。', 400 );
			}
			if ( $image_info[0] < 1 || $image_info[1] < 1 || $image_info[0] > $max_dimension || $image_info[1] > $max_dimension ) {
				return self::error( 'invalid_coordinate_asset_size', '画像素材の縦横サイズが上限を超えています。', 400 );
			}

			$normalized[ $slot ] = array(
				'mime' => $mime,
				'data' => base64_encode( $binary ),
			);
		}

		return $normalized;
	}

	/**
	 * ページ背景は任意CSSを受け取らず、表示崩れを起こしにくい固定値だけを許可します。
	 */
	private static function validate_page_background( $background, $assets, $schema_version ) {
		if ( null === $background ) {
			return null;
		}
		if ( self::SCHEMA_VERSION !== $schema_version || ! is_array( $background ) ) {
			return self::error( 'invalid_page_background', 'ページ背景を含むコーデには新しいファイル形式が必要です。', 400 );
		}
		$allowed = array(
			'baseColor', 'contentColor', 'contentOpacity',
			'desktopSize', 'desktopPosition', 'desktopRepeat',
			'mobileSize', 'mobilePosition', 'mobileRepeat',
		);
		if ( array_diff( array_keys( $background ), $allowed ) || empty( $assets['backgroundDesktop'] ) ) {
			return self::error( 'invalid_page_background', 'ページ背景の設定またはPC用背景画像を確認できませんでした。', 400 );
		}
		$base_color    = isset( $background['baseColor'] ) ? strtolower( (string) $background['baseColor'] ) : '';
		$content_color = isset( $background['contentColor'] ) ? strtolower( (string) $background['contentColor'] ) : '';
		if ( ! preg_match( '/^#[0-9a-f]{6}$/', $base_color ) || ! preg_match( '/^#[0-9a-f]{6}$/', $content_color ) ) {
			return self::error( 'invalid_page_background_color', 'ページ背景に使用できない色が含まれています。', 400 );
		}
		$opacity = isset( $background['contentOpacity'] ) ? filter_var( $background['contentOpacity'], FILTER_VALIDATE_INT ) : 96;
		if ( false === $opacity || $opacity < 82 || $opacity > 100 ) {
			return self::error( 'invalid_page_background_opacity', '本文背景の透明度は82〜100で指定してください。', 400 );
		}

		$sizes     = array( 'cover', 'contain', 'auto' );
		$positions = array( 'top-left', 'top-center', 'top-right', 'center-center' );
		$repeats   = array( 'no-repeat', 'repeat', 'repeat-x', 'repeat-y' );
		$normalized = array(
			'baseColor'       => $base_color,
			'contentColor'    => $content_color,
			'contentOpacity'  => (int) $opacity,
			'desktopSize'     => self::enum_value( isset( $background['desktopSize'] ) ? $background['desktopSize'] : 'cover', $sizes, 'cover' ),
			'desktopPosition' => self::enum_value( isset( $background['desktopPosition'] ) ? $background['desktopPosition'] : 'top-center', $positions, 'top-center' ),
			'desktopRepeat'   => self::enum_value( isset( $background['desktopRepeat'] ) ? $background['desktopRepeat'] : 'no-repeat', $repeats, 'no-repeat' ),
			'mobileSize'      => self::enum_value( isset( $background['mobileSize'] ) ? $background['mobileSize'] : 'cover', $sizes, 'cover' ),
			'mobilePosition'  => self::enum_value( isset( $background['mobilePosition'] ) ? $background['mobilePosition'] : 'top-center', $positions, 'top-center' ),
			'mobileRepeat'    => self::enum_value( isset( $background['mobileRepeat'] ) ? $background['mobileRepeat'] : 'no-repeat', $repeats, 'no-repeat' ),
		);
		foreach ( array( 'desktopSize', 'desktopPosition', 'desktopRepeat', 'mobileSize', 'mobilePosition', 'mobileRepeat' ) as $key ) {
			if ( null === $normalized[ $key ] ) {
				return self::error( 'invalid_page_background_value', 'ページ背景の表示方法に対応していない値が含まれています。', 400 );
			}
		}
		return $normalized;
	}

	private static function enum_value( $value, $allowed, $default ) {
		$value = is_string( $value ) && '' !== $value ? $value : $default;
		return in_array( $value, $allowed, true ) ? $value : null;
	}

	/**
	 * 大きなページ背景だけはbase64のままDBへ保存せず、uploads内へ画像として保管します。
	 */
	private static function persist_background_assets( $coordinate ) {
		if ( empty( $coordinate['pageBackground'] ) ) {
			return $coordinate;
		}
		$upload = wp_upload_dir();
		if ( ! empty( $upload['error'] ) || empty( $upload['basedir'] ) ) {
			return self::error( 'coordinate_upload_unavailable', '背景画像をWordPressへ保存できませんでした。アップロード設定を確認してください。', 500 );
		}
		$relative_dir = self::UPLOAD_SUBDIR . '/' . $coordinate['id'];
		$target_dir   = trailingslashit( $upload['basedir'] ) . $relative_dir;
		if ( ! wp_mkdir_p( $target_dir ) ) {
			return self::error( 'coordinate_upload_directory_failed', '背景画像の保存フォルダを作成できませんでした。', 500 );
		}

		foreach ( array( 'backgroundDesktop', 'backgroundMobile' ) as $slot ) {
			if ( empty( $coordinate['assets'][ $slot ]['data'] ) || empty( $coordinate['assets'][ $slot ]['mime'] ) ) {
				continue;
			}
			$mime      = $coordinate['assets'][ $slot ]['mime'];
			$binary    = base64_decode( $coordinate['assets'][ $slot ]['data'], true );
			$extension = self::mime_extension( $mime );
			if ( false === $binary || ! $extension ) {
				return self::error( 'coordinate_background_write_failed', '背景画像を保存できませんでした。', 500 );
			}
			$filename = strtolower( $slot ) . '-' . substr( hash( 'sha256', $binary ), 0, 20 ) . '.' . $extension;
			$path     = trailingslashit( $target_dir ) . $filename;
			if ( ! file_exists( $path ) && strlen( $binary ) !== @file_put_contents( $path, $binary, LOCK_EX ) ) {
				return self::error( 'coordinate_background_write_failed', '背景画像をWordPressへ保存できませんでした。', 500 );
			}
			$coordinate['assets'][ $slot ] = array(
				'mime' => $mime,
				'file' => $relative_dir . '/' . $filename,
			);
		}
		return $coordinate;
	}

	private static function mime_extension( $mime ) {
		$map = array(
			'image/png'  => 'png',
			'image/webp' => 'webp',
			'image/jpeg' => 'jpg',
		);
		return isset( $map[ $mime ] ) ? $map[ $mime ] : '';
	}

	/**
	 * HTMLを一切許可しない短い表示文字列です。
	 */
	private static function plain_text( $value, $max_bytes ) {
		if ( ! is_string( $value ) || strlen( $value ) > $max_bytes || preg_match( '/[<>]/', $value ) ) {
			return self::error( 'invalid_text', '表示文字が正しくありません。', 400 );
		}
		$sanitized = sanitize_text_field( $value );
		if ( $sanitized !== $value ) {
			return self::error( 'invalid_text', '表示文字が正しくありません。', 400 );
		}
		return $sanitized;
	}

	/**
	 * JavaScriptへ渡す必要最小限のデータです。
	 */
	private static function to_public_coordinate( $coordinate ) {
		return array(
			'id'          => $coordinate['id'],
			'version'     => $coordinate['version'],
			'name'        => $coordinate['name'],
			'description' => $coordinate['description'],
			'icon'        => $coordinate['icon'],
			'fontBody'    => $coordinate['fontBody'],
			'fontHeading' => $coordinate['fontHeading'],
			'headingDesign' => isset( $coordinate['headingDesign'] ) ? $coordinate['headingDesign'] : null,
			'hasPageBackground' => ! empty( $coordinate['pageBackground'] ),
		);
	}

	/**
	 * サイト単位のWordPress optionから読み込みます。
	 */
	private static function get_all_coordinates() {
		$coordinates = get_option( self::OPTION_KEY, array() );
		return is_array( $coordinates ) ? $coordinates : array();
	}

	private static function coordinate_ids_from_post( $post_id ) {
		$post = $post_id ? get_post( $post_id ) : null;
		return $post instanceof WP_Post ? self::coordinate_ids_from_content( $post->post_content ) : array();
	}

	private static function coordinate_ids_from_content( $content ) {
		if ( ! is_string( $content ) || '' === $content ) {
			return array();
		}
		preg_match_all( '/aichi-ai-(?:page-)?set-(chiichan-[a-z0-9][a-z0-9-]{1,54})/', $content, $matches );
		return empty( $matches[1] ) ? array() : array_values( array_unique( $matches[1] ) );
	}

	/**
	 * 許可済みの色データだけから固定テンプレートのCSSを生成します。
	 */
	private static function generate_css( $coordinates ) {
		$css = '';
		foreach ( $coordinates as $coordinate ) {
			if ( ! is_array( $coordinate ) || empty( $coordinate['id'] ) || empty( $coordinate['style'] ) ) {
				continue;
			}
			$id = $coordinate['id'];
			if ( ! preg_match( '/^chiichan-[a-z0-9][a-z0-9-]{1,54}$/', $id ) ) {
				continue;
			}

			$style = $coordinate['style'];
			$valid = true;
			foreach ( array( 'primary', 'secondary', 'accent', 'background', 'surface', 'text' ) as $color_key ) {
				if ( empty( $style[ $color_key ] ) || ! preg_match( '/^#[0-9a-f]{6}$/', $style[ $color_key ] ) ) {
					$valid = false;
				}
			}
			if ( ! $valid ) {
				continue;
			}

			$radius    = isset( $style['radius'] ) ? max( 0, min( 32, (int) $style['radius'] ) ) : 16;
			$primary   = $style['primary'];
			$secondary = $style['secondary'];
			$accent    = $style['accent'];
			$background = $style['background'];
			$surface    = $style['surface'];
			$text       = $style['text'];
			$suffix     = 'custom-' . $id;

			$heading = '.aichi-palette-heading-' . $suffix;
			$list    = '.aichi-palette-list-' . $suffix;
			$qa      = '.aichi-palette-qa-' . $suffix;
			$box     = '.aichi-palette-box-' . $suffix;
			$box_point   = $box . '.aichi-ai-box-kind-point';
			$box_special = $box . '.aichi-ai-box-kind-special';
			$button  = '.aichi-palette-button-' . $suffix;
			$heading_asset = self::asset_data_uri( $coordinate, 'heading' );
			$accent_asset  = self::asset_data_uri( $coordinate, 'accent' );
			$subheading_asset = self::asset_data_uri( $coordinate, 'subheading' );
			$special_asset = self::asset_data_uri( $coordinate, 'special' );
			$qa_asset      = self::asset_data_uri( $coordinate, 'qa' );
			$heading_design = isset( $coordinate['headingDesign'] ) ? $coordinate['headingDesign'] : null;
			$css .= self::generate_page_background_css( $coordinate );

			/*
			 * クリスマス Ver.1 H2試作。
			 * H3以下や他パーツのCSSは生成せず、H2だけを一枚の紙カードとして仕上げます。
			 * ヒイラギは左上へ小さく収め、右側の本文幅を削りません。
			 */
			if ( 'nordic-christmas-h2-v1' === $heading_design ) {
				$css .= 'h2' . $heading . '{box-sizing:border-box!important;position:relative!important;isolation:isolate!important;max-width:100%!important;min-height:92px!important;margin:2.2em 0 1.05em!important;padding:24px 28px 28px 82px!important;overflow:hidden!important;overflow-wrap:anywhere!important;word-break:auto-phrase!important;text-wrap:pretty;color:' . $text . '!important;background:radial-gradient(circle at 91% 22%,rgba(255,255,255,.96) 0 2px,transparent 2.8px),radial-gradient(circle at 95% 39%,rgba(255,255,255,.88) 0 3px,transparent 3.8px),radial-gradient(circle at 86% 46%,rgba(255,255,255,.82) 0 1.5px,transparent 2.3px),linear-gradient(138deg,' . $surface . ' 0%,' . $background . ' 100%)!important;border:1px solid ' . $primary . '!important;border-top:3px solid ' . $secondary . '!important;border-radius:20px 9px 18px 12px!important;box-shadow:0 12px 28px rgba(38,74,55,.12),inset 0 0 0 4px rgba(255,255,255,.48)!important;font-size:clamp(1.35rem,2.2vw,1.76rem)!important;font-weight:850!important;line-height:1.55!important;letter-spacing:.035em!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{content:"";position:absolute;left:15px;top:14px;width:58px;height:44px;background:url("' . $heading_asset . '") left top/contain no-repeat;pointer-events:none}';
				}
				$css .= 'h2' . $heading . '::after{content:"";position:absolute;left:70px;right:20px;bottom:11px;height:10px;background:linear-gradient(177deg,transparent 0 34%,' . $secondary . ' 35% 49%,transparent 50%),linear-gradient(182deg,transparent 0 61%,' . $primary . ' 62% 75%,transparent 76%);border-radius:48% 52% 45% 55%;opacity:.9;transform:rotate(-.25deg);pointer-events:none}';
				$css .= '@media(max-width:782px){h2' . $heading . '{min-height:78px!important;margin:1.9em 0 .95em!important;padding:19px 15px 24px 61px!important;border-radius:16px 8px 15px 10px!important;font-size:clamp(1.2rem,5.2vw,1.42rem)!important;line-height:1.58!important;letter-spacing:.02em!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{left:11px;top:12px;width:44px;height:34px}';
				}
				$css .= 'h2' . $heading . '::after{left:55px;right:12px;bottom:8px;height:9px}}';
				$css .= '@media(max-width:380px){h2' . $heading . '{padding-left:55px!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{left:9px;width:40px;height:31px}';
				}
				$css .= 'h2' . $heading . '::after{left:49px}}';
				continue;
			}

			/*
			 * クリスマス Ver.1 H2第2案。
			 * 深緑のタイトル帯そのものを季節グラフィックにし、上部のガーランドと
			 * 下部の赤いクレヨン線を一体化します。H3以下にはCSSを生成しません。
			 */
			if ( 'nordic-christmas-h2-v2' === $heading_design ) {
				$css .= 'h2' . $heading . '{box-sizing:border-box!important;position:relative!important;isolation:isolate!important;max-width:100%!important;min-height:98px!important;margin:2.25em 0 1.05em!important;padding:40px 42px 29px 34px!important;overflow:hidden!important;overflow-wrap:anywhere!important;word-break:auto-phrase!important;text-wrap:pretty;color:' . $text . '!important;background:radial-gradient(circle at 93% 24%,rgba(255,255,255,.88) 0 2px,transparent 2.8px),radial-gradient(circle at 88% 43%,rgba(255,255,255,.68) 0 1.4px,transparent 2.2px),radial-gradient(circle at 96% 58%,rgba(255,255,255,.78) 0 2.5px,transparent 3.3px),linear-gradient(90deg,' . $secondary . ' 0 10px,transparent 10px),linear-gradient(104deg,' . $primary . ' 0%,' . $surface . ' 58%,' . $primary . ' 100%)!important;border:0!important;border-radius:16px 5px 18px 7px!important;clip-path:polygon(0 8%,1.2% 4%,96.6% 0,100% 13%,98.5% 50%,100% 87%,96.7% 100%,1.1% 96%,0 88%,.9% 50%)!important;box-shadow:inset 0 0 0 1px rgba(255,255,255,.16)!important;filter:drop-shadow(0 9px 11px rgba(25,65,46,.22))!important;font-family:"Hiragino Maru Gothic ProN","Hiragino Sans","Yu Gothic",YuGothic,Meiryo,"Noto Sans JP",sans-serif!important;font-size:clamp(1.35rem,2.2vw,1.76rem)!important;font-weight:850!important;line-height:1.52!important;letter-spacing:.035em!important;text-shadow:0 1px 0 rgba(15,50,34,.28)!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{content:"";position:absolute;left:12px;right:14px;top:0;height:38px;background:url("' . $heading_asset . '") center top/100% 100% no-repeat;pointer-events:none}';
				}
				$css .= 'h2' . $heading . '::after{content:"";position:absolute;left:31px;right:42px;bottom:10px;height:12px;background:radial-gradient(circle at 98% 50%,' . $accent . ' 0 3px,transparent 3.8px),linear-gradient(176deg,transparent 0 27%,' . $secondary . ' 28% 62%,transparent 63%),linear-gradient(183deg,transparent 0 56%,rgba(255,248,232,.58) 57% 67%,transparent 68%);border-radius:48% 52% 44% 56%;opacity:.96;transform:rotate(-.35deg);pointer-events:none}';
				$css .= '@media(max-width:782px){h2' . $heading . '{min-height:92px!important;margin:1.95em 0 .95em!important;padding:38px 20px 27px!important;border-radius:13px 4px 15px 6px!important;clip-path:polygon(0 7%,1% 4%,97% 0,100% 12%,98.7% 50%,100% 88%,97% 100%,1% 96%,0 89%,.8% 50%)!important;font-size:clamp(1.19rem,5.1vw,1.42rem)!important;line-height:1.56!important;letter-spacing:.02em!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{left:8px;right:8px;height:36px;background-size:112% 100%;background-position:center top}';
				}
				$css .= 'h2' . $heading . '::after{left:19px;right:28px;bottom:8px;height:11px}}';
				$css .= '@media(max-width:380px){h2' . $heading . '{padding-left:18px!important;padding-right:18px!important;font-size:1.17rem!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{background-size:125% 100%}';
				}
				$css .= '}';
				continue;
			}

			/*
			 * クリスマス Ver.1 H2第3案。
			 * 温かい朱赤の手描きリボンへ、吊り下げ飾り、雪、白いブラシ線、
			 * 右端のツリーを一体化します。H3以下にはCSSを生成しません。
			 */
			if ( 'nordic-christmas-h2-v3' === $heading_design ) {
				$css .= 'h2' . $heading . '{box-sizing:border-box!important;position:relative!important;isolation:isolate!important;max-width:100%!important;min-height:112px!important;margin:2.25em 0 1.05em!important;padding:43px 108px 31px 34px!important;overflow:hidden!important;overflow-wrap:anywhere!important;word-break:auto-phrase!important;text-wrap:pretty;color:' . $text . '!important;background:radial-gradient(circle at 75% 29%,rgba(255,255,255,.94) 0 1.6px,transparent 2.4px),radial-gradient(circle at 83% 58%,rgba(255,255,255,.82) 0 2.3px,transparent 3.1px),radial-gradient(circle at 69% 70%,rgba(255,247,232,.78) 0 1.3px,transparent 2.1px),linear-gradient(177deg,transparent 0 77%,rgba(255,247,232,.96) 78% 81%,transparent 82%),linear-gradient(182deg,transparent 0 85%,' . $accent . ' 86% 88%,transparent 89%),linear-gradient(90deg,rgba(126,37,34,.34) 0 12px,transparent 12px),linear-gradient(104deg,' . $primary . ' 0%,' . $surface . ' 61%,' . $primary . ' 100%)!important;border:0!important;border-radius:0!important;clip-path:polygon(0 8%,1.2% 3%,96.5% 0,100% 10%,98.6% 49%,100% 89%,96.8% 100%,1.1% 96%,0 89%,.8% 50%)!important;box-shadow:inset 0 0 0 1px rgba(255,247,232,.18)!important;filter:drop-shadow(0 9px 11px rgba(108,35,31,.24))!important;font-family:"Hiragino Mincho ProN","Yu Mincho",YuMincho,"Noto Serif JP",Georgia,serif!important;font-size:clamp(1.36rem,2.25vw,1.82rem)!important;font-weight:800!important;line-height:1.5!important;letter-spacing:.045em!important;-webkit-text-stroke:.2px rgba(40,88,62,.64)!important;text-shadow:0 2px 0 rgba(40,88,62,.46),0 3px 8px rgba(89,29,27,.22)!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{content:"";position:absolute;left:13px;right:86px;top:0;height:40px;background:url("' . $heading_asset . '") center top/100% 100% no-repeat;pointer-events:none}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{content:"";position:absolute;z-index:1;right:10px;bottom:5px;width:86px;height:103px;background:url("' . $accent_asset . '") right bottom/contain no-repeat;pointer-events:none}';
				}
				$css .= '@media(max-width:782px){h2' . $heading . '{min-height:104px!important;margin:1.95em 0 .95em!important;padding:39px 72px 29px 20px!important;clip-path:polygon(0 7%,1% 3%,97% 0,100% 11%,98.8% 49%,100% 89%,97% 100%,1% 96%,0 89%,.8% 50%)!important;font-size:clamp(1.18rem,5vw,1.4rem)!important;line-height:1.56!important;letter-spacing:.025em!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{left:8px;right:57px;height:36px;background-size:124% 100%;background-position:left top}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{right:6px;bottom:5px;width:59px;height:79px}';
				}
				$css .= '}';
				$css .= '@media(max-width:380px){h2' . $heading . '{padding:38px 61px 28px 17px!important;font-size:1.16rem!important;line-height:1.58!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{right:50px;background-size:145% 100%}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{right:4px;width:52px;height:72px}';
				}
				$css .= '}';
				continue;
			}

			/*
			 * クリスマス Ver.1 H2第2案・完成イメージ版。
			 * アイボリーの紙カードへ、上下で形の異なる手描きフレーム、
			 * ヒイラギ、吊り飾り、ギフトタグ、プレゼントを統合します。
			 * H3以下や他パーツのCSSは生成しません。
			 */
			if ( 'christmas-card-h2-v2' === $heading_design ) {
				$css .= 'h2' . $heading . '{box-sizing:border-box!important;position:relative!important;isolation:isolate!important;max-width:100%!important;min-height:132px!important;margin:2.25em 0 1.1em!important;padding:58px 90px 44px!important;overflow:hidden!important;overflow-wrap:anywhere!important;word-break:auto-phrase!important;white-space:normal!important;text-wrap:balance;text-align:center!important;color:' . $text . '!important;background:linear-gradient(91deg,transparent 0 27%,' . $primary . ' 28% 61%,transparent 62%) left 4px top 50px/8px calc(100% - 88px) no-repeat,linear-gradient(89deg,transparent 0 38%,' . $primary . ' 39% 72%,transparent 73%) right 4px top 50px/8px calc(100% - 88px) no-repeat,radial-gradient(circle at 21% 38%,rgba(212,178,106,.13) 0 1px,transparent 1.8px),radial-gradient(circle at 76% 64%,rgba(46,107,78,.09) 0 1.2px,transparent 2px),linear-gradient(142deg,' . $background . ' 0%,' . $surface . ' 52%,' . $background . ' 100%)!important;border:0!important;border-radius:23px 15px 24px 18px!important;box-shadow:inset 0 0 0 1px rgba(212,178,106,.1)!important;filter:drop-shadow(0 8px 12px rgba(93,59,31,.13))!important;font-family:"Hiragino Maru Gothic ProN","Hiragino Sans","Yu Gothic",YuGothic,Meiryo,"Noto Sans JP",sans-serif!important;font-size:clamp(1.34rem,2.25vw,1.78rem)!important;font-weight:850!important;line-height:1.56!important;letter-spacing:.035em!important;text-shadow:1px 1px 0 rgba(46,107,78,.16)!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{content:"";position:absolute;z-index:0;left:0;right:0;top:0;height:72px;background:url("' . $heading_asset . '") left top/100% 72px no-repeat;pointer-events:none}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{content:"";position:absolute;z-index:1;left:0;right:0;bottom:0;height:54px;background:url("' . $accent_asset . '") left bottom/100% 54px no-repeat;pointer-events:none}';
				}
				$css .= '@media(max-width:782px){h2' . $heading . '{min-height:126px!important;margin:1.95em 0 .98em!important;padding:54px 54px 42px!important;background:linear-gradient(91deg,transparent 0 27%,' . $primary . ' 28% 61%,transparent 62%) left 3px top 46px/7px calc(100% - 82px) no-repeat,linear-gradient(89deg,transparent 0 38%,' . $primary . ' 39% 72%,transparent 73%) right 3px top 46px/7px calc(100% - 82px) no-repeat,radial-gradient(circle at 21% 38%,rgba(212,178,106,.13) 0 1px,transparent 1.8px),radial-gradient(circle at 76% 64%,rgba(46,107,78,.09) 0 1.2px,transparent 2px),linear-gradient(142deg,' . $background . ' 0%,' . $surface . ' 52%,' . $background . ' 100%)!important;border-radius:20px 13px 21px 15px!important;font-size:clamp(1.16rem,5vw,1.38rem)!important;line-height:1.58!important;letter-spacing:.018em!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{height:64px;background-size:100% 64px}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{height:50px;background-size:100% 50px}';
				}
				$css .= '}';
				$css .= '@media(max-width:380px){h2' . $heading . '{padding:52px 48px 41px!important;font-size:1.15rem!important;line-height:1.6!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{height:61px;background-size:100% 61px}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{height:47px;background-size:100% 47px}';
				}
				$css .= '}';
				continue;
			}

			/*
			 * クリスマス Ver.1 H2第2案・仕上げ版。
			 * 完成イメージ版の構造と装飾は維持し、H2文字の主役感だけを強化。
			 * 右下素材はプレゼントから同じ手描きタッチのツリーへ変更します。
			 */
			if ( 'christmas-card-h2-v2-finish' === $heading_design ) {
				$css .= 'h2' . $heading . '{box-sizing:border-box!important;position:relative!important;isolation:isolate!important;max-width:100%!important;min-height:136px!important;margin:2.25em 0 1.1em!important;padding:60px 90px 45px!important;overflow:hidden!important;overflow-wrap:anywhere!important;word-break:auto-phrase!important;white-space:normal!important;text-wrap:balance;text-align:center!important;color:' . $text . '!important;background:linear-gradient(91deg,transparent 0 27%,' . $primary . ' 28% 61%,transparent 62%) left 4px top 50px/8px calc(100% - 88px) no-repeat,linear-gradient(89deg,transparent 0 38%,' . $primary . ' 39% 72%,transparent 73%) right 4px top 50px/8px calc(100% - 88px) no-repeat,radial-gradient(circle at 21% 38%,rgba(212,178,106,.13) 0 1px,transparent 1.8px),radial-gradient(circle at 76% 64%,rgba(46,107,78,.09) 0 1.2px,transparent 2px),linear-gradient(142deg,' . $background . ' 0%,' . $surface . ' 52%,' . $background . ' 100%)!important;border:0!important;border-radius:23px 15px 24px 18px!important;box-shadow:inset 0 0 0 1px rgba(212,178,106,.1)!important;filter:drop-shadow(0 8px 12px rgba(93,59,31,.13))!important;font-family:"Hiragino Maru Gothic ProN","Hiragino Sans","Yu Gothic",YuGothic,Meiryo,"Noto Sans JP",sans-serif!important;font-size:clamp(1.5rem,2.55vw,2.06rem)!important;font-weight:900!important;line-height:1.54!important;letter-spacing:.035em!important;text-shadow:1px 1px 0 rgba(46,107,78,.16)!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{content:"";position:absolute;z-index:0;left:0;right:0;top:0;height:72px;background:url("' . $heading_asset . '") left top/100% 72px no-repeat;pointer-events:none}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{content:"";position:absolute;z-index:1;left:0;right:0;bottom:0;height:54px;background:url("' . $accent_asset . '") left bottom/100% 54px no-repeat;pointer-events:none}';
				}
				$css .= '@media(max-width:782px){h2' . $heading . '{min-height:132px!important;margin:1.95em 0 .98em!important;padding:56px 54px 43px!important;background:linear-gradient(91deg,transparent 0 27%,' . $primary . ' 28% 61%,transparent 62%) left 3px top 46px/7px calc(100% - 82px) no-repeat,linear-gradient(89deg,transparent 0 38%,' . $primary . ' 39% 72%,transparent 73%) right 3px top 46px/7px calc(100% - 82px) no-repeat,radial-gradient(circle at 21% 38%,rgba(212,178,106,.13) 0 1px,transparent 1.8px),radial-gradient(circle at 76% 64%,rgba(46,107,78,.09) 0 1.2px,transparent 2px),linear-gradient(142deg,' . $background . ' 0%,' . $surface . ' 52%,' . $background . ' 100%)!important;border-radius:20px 13px 21px 15px!important;font-size:clamp(1.28rem,5.5vw,1.53rem)!important;font-weight:900!important;line-height:1.56!important;letter-spacing:.018em!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{height:64px;background-size:100% 64px}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{height:50px;background-size:100% 50px}';
				}
				$css .= '}';
				$css .= '@media(max-width:380px){h2' . $heading . '{padding:54px 48px 42px!important;font-size:1.27rem!important;line-height:1.58!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{height:61px;background-size:100% 61px}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{height:47px;background-size:100% 47px}';
				}
				$css .= '}';
				continue;
			}

			/*
			 * クリスマス Ver.1 H2第2案・再仕上げ版。
			 * 完成済みのフレームと装飾は維持し、文字の存在感と右下ツリーだけを
			 * 明確に強化します。H3以下や他パーツのCSSは生成しません。
			 */
			if ( 'christmas-card-h2-v2-refinish' === $heading_design ) {
				$css .= 'h2' . $heading . '{box-sizing:border-box!important;position:relative!important;isolation:isolate!important;max-width:100%!important;min-height:136px!important;margin:2.25em 0 1.1em!important;padding:60px 90px 45px!important;overflow:hidden!important;overflow-wrap:anywhere!important;word-break:auto-phrase!important;white-space:normal!important;text-wrap:balance;text-align:center!important;color:' . $text . '!important;background:linear-gradient(91deg,transparent 0 27%,' . $primary . ' 28% 61%,transparent 62%) left 4px top 50px/8px calc(100% - 88px) no-repeat,linear-gradient(89deg,transparent 0 38%,' . $primary . ' 39% 72%,transparent 73%) right 4px top 50px/8px calc(100% - 88px) no-repeat,radial-gradient(circle at 21% 38%,rgba(212,178,106,.13) 0 1px,transparent 1.8px),radial-gradient(circle at 76% 64%,rgba(46,107,78,.09) 0 1.2px,transparent 2px),linear-gradient(142deg,' . $background . ' 0%,' . $surface . ' 52%,' . $background . ' 100%)!important;border:0!important;border-radius:23px 15px 24px 18px!important;box-shadow:inset 0 0 0 1px rgba(212,178,106,.1)!important;filter:drop-shadow(0 8px 12px rgba(93,59,31,.13))!important;font-family:"M PLUS Rounded 1c","Hiragino Maru Gothic ProN","Arial Rounded MT Bold","Hiragino Sans","Yu Gothic",YuGothic,Meiryo,"Noto Sans JP",sans-serif!important;font-size:clamp(1.95rem,3.45vw,2.75rem)!important;font-weight:900!important;font-kerning:normal!important;font-feature-settings:"palt" 1,"kern" 1!important;font-variant-east-asian:proportional-width!important;line-height:1.42!important;letter-spacing:-.055em!important;-webkit-text-stroke:.55px rgba(201,71,61,.82)!important;paint-order:stroke fill!important;text-shadow:.45px .7px 0 rgba(46,107,78,.14)!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{content:"";position:absolute;z-index:0;left:0;right:0;top:0;height:72px;background:url("' . $heading_asset . '") left top/100% 72px no-repeat;pointer-events:none}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{content:"";position:absolute;z-index:1;left:0;right:0;bottom:0;height:80px;background:url("' . $accent_asset . '") left bottom/100% 80px no-repeat;pointer-events:none}';
				}
				$css .= '@media(max-width:782px){h2' . $heading . '{min-height:132px!important;margin:1.95em 0 .98em!important;padding:56px 54px 43px!important;background:linear-gradient(91deg,transparent 0 27%,' . $primary . ' 28% 61%,transparent 62%) left 3px top 46px/7px calc(100% - 82px) no-repeat,linear-gradient(89deg,transparent 0 38%,' . $primary . ' 39% 72%,transparent 73%) right 3px top 46px/7px calc(100% - 82px) no-repeat,radial-gradient(circle at 21% 38%,rgba(212,178,106,.13) 0 1px,transparent 1.8px),radial-gradient(circle at 76% 64%,rgba(46,107,78,.09) 0 1.2px,transparent 2px),linear-gradient(142deg,' . $background . ' 0%,' . $surface . ' 52%,' . $background . ' 100%)!important;border-radius:20px 13px 21px 15px!important;font-size:clamp(1.6rem,6.9vw,1.95rem)!important;font-weight:900!important;line-height:1.48!important;letter-spacing:-.05em!important;-webkit-text-stroke:.45px rgba(201,71,61,.82)!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{height:64px;background-size:100% 64px}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{height:76px;background-size:100% 76px}';
				}
				$css .= '}';
				$css .= '@media(max-width:380px){h2' . $heading . '{padding:54px 48px 42px!important;font-size:1.72rem!important;line-height:1.5!important;letter-spacing:-.052em!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{height:61px;background-size:100% 61px}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{height:72px;background-size:100% 72px}';
				}
				$css .= '}';
				continue;
			}

			/*
			 * クリスマス Ver.1 H2第2案・最終再修正版。
			 * 完成済みのカード装飾はそのままに、実データを同梱した表示書体と
			 * 右下ツリーの大きさだけを変更します。H3以下には適用しません。
			 */
			if ( in_array( $heading_design, array( 'christmas-card-h2-v2-final', 'christmas-card-h2-h3-v1', 'christmas-card-complete-v1' ), true ) ) {
				$font_url = esc_url_raw( AICHI_PALETTE_URL . 'assets/fonts/MochiyPopOne-Regular.ttf' );
				$css .= '@font-face{font-family:"AICHI Christmas Pop";src:url("' . $font_url . '") format("truetype");font-style:normal;font-weight:400;font-display:swap}';
				$css .= 'h2' . $heading . '{box-sizing:border-box!important;position:relative!important;isolation:isolate!important;max-width:100%!important;min-height:136px!important;margin:2.25em 0 1.1em!important;padding:56px 115px 28px 90px!important;overflow:hidden!important;overflow-wrap:anywhere!important;word-break:auto-phrase!important;white-space:normal!important;text-wrap:balance;text-align:center!important;color:' . $text . '!important;background:linear-gradient(91deg,transparent 0 27%,' . $primary . ' 28% 61%,transparent 62%) left 4px top 50px/8px calc(100% - 88px) no-repeat,linear-gradient(89deg,transparent 0 38%,' . $primary . ' 39% 72%,transparent 73%) right 4px top 50px/8px calc(100% - 88px) no-repeat,radial-gradient(circle at 21% 38%,rgba(212,178,106,.13) 0 1px,transparent 1.8px),radial-gradient(circle at 76% 64%,rgba(46,107,78,.09) 0 1.2px,transparent 2px),linear-gradient(142deg,' . $background . ' 0%,' . $surface . ' 52%,' . $background . ' 100%)!important;border:0!important;border-radius:23px 15px 24px 18px!important;box-shadow:inset 0 0 0 1px rgba(212,178,106,.1)!important;filter:drop-shadow(0 8px 12px rgba(93,59,31,.13))!important;font-family:"AICHI Christmas Pop",sans-serif!important;font-size:clamp(2.62rem,4.6vw,3.7rem)!important;font-weight:400!important;font-kerning:normal!important;font-feature-settings:"palt" 1,"kern" 1!important;font-variant-east-asian:proportional-width!important;line-height:1.25!important;letter-spacing:-.07em!important;text-shadow:none!important;-webkit-text-stroke:0!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{content:"";position:absolute;z-index:0;left:0;right:0;top:0;height:72px;background:url("' . $heading_asset . '") left top/100% 72px no-repeat;pointer-events:none}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{content:"";position:absolute;z-index:1;left:0;right:0;bottom:0;height:112px;background:url("' . $accent_asset . '") left bottom/100% 112px no-repeat;pointer-events:none}';
				}
				$css .= '@media(max-width:782px){h2' . $heading . '{min-height:132px!important;margin:1.95em 0 .98em!important;padding:54px 70px 26px 54px!important;background:linear-gradient(91deg,transparent 0 27%,' . $primary . ' 28% 61%,transparent 62%) left 3px top 46px/7px calc(100% - 82px) no-repeat,linear-gradient(89deg,transparent 0 38%,' . $primary . ' 39% 72%,transparent 73%) right 3px top 46px/7px calc(100% - 82px) no-repeat,radial-gradient(circle at 21% 38%,rgba(212,178,106,.13) 0 1.2px,transparent 2px),radial-gradient(circle at 76% 64%,rgba(46,107,78,.09) 0 1.2px,transparent 2px),linear-gradient(142deg,' . $background . ' 0%,' . $surface . ' 52%,' . $background . ' 100%)!important;border-radius:20px 13px 21px 15px!important;font-size:clamp(2.1rem,8.8vw,2.55rem)!important;font-weight:400!important;line-height:1.27!important;letter-spacing:-.07em!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{height:64px;background-size:100% 64px}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{height:104px;background-size:100% 104px}';
				}
				$css .= '}';
				$css .= '@media(max-width:380px){h2' . $heading . '{padding:52px 62px 26px 48px!important;font-size:2.2rem!important;line-height:1.28!important;letter-spacing:-.07em!important}';
				if ( $heading_asset ) {
					$css .= 'h2' . $heading . '::before{height:61px;background-size:100% 61px}';
				}
				if ( $accent_asset ) {
					$css .= 'h2' . $heading . '::after{height:100px;background-size:100% 100px}';
				}
				$css .= '}';
				if ( in_array( $heading_design, array( 'christmas-card-h2-h3-v1', 'christmas-card-complete-v1' ), true ) ) {
					$css .= 'h3' . $heading . '{box-sizing:border-box!important;position:relative!important;isolation:isolate!important;max-width:100%!important;min-height:78px!important;margin:1.75em 0 .9em!important;padding:18px 36px 18px 94px!important;overflow:hidden!important;overflow-wrap:anywhere!important;word-break:auto-phrase!important;white-space:normal!important;text-wrap:balance;color:' . $background . '!important;background:radial-gradient(circle at 76% 27%,rgba(212,178,106,.72) 0 2px,transparent 2.8px),radial-gradient(circle at 88% 67%,rgba(255,248,240,.6) 0 1.6px,transparent 2.4px),linear-gradient(177deg,#397b5b 0%,' . $secondary . ' 54%,#24573f 100%)!important;border:0!important;border-radius:0!important;clip-path:polygon(0 14%,4% 18%,3% 4%,47% 8%,78% 3%,97% 9%,96% 20%,100% 14%,98% 50%,100% 86%,96% 80%,97% 94%,62% 91%,31% 97%,3% 93%,4% 81%,0 87%,2% 50%)!important;filter:drop-shadow(0 6px 7px rgba(31,72,51,.2))!important;font-family:"AICHI Christmas Pop",sans-serif!important;font-size:clamp(1.52rem,2.55vw,2rem)!important;font-weight:400!important;font-kerning:normal!important;font-feature-settings:"palt" 1,"kern" 1!important;font-variant-east-asian:proportional-width!important;line-height:1.38!important;letter-spacing:-.055em!important;text-shadow:0 1px 0 rgba(18,59,41,.2)!important;-webkit-text-stroke:0!important}';
					$css .= 'h3' . $heading . '::after{content:"";position:absolute;z-index:0;left:4px;top:50%;width:58px;height:58px;transform:translateY(-50%) rotate(-2deg);background:' . $primary . ';clip-path:polygon(0 12%,100% 0,80% 50%,100% 100%,0 88%,18% 50%);opacity:.98;pointer-events:none}';
					if ( $subheading_asset ) {
						$css .= 'h3' . $heading . '::before{content:"";position:absolute;z-index:1;left:10px;top:50%;width:72px;height:62px;transform:translateY(-50%) rotate(-2deg);background:url("' . $subheading_asset . '") center/contain no-repeat;pointer-events:none}';
					}
					$css .= '@media(max-width:782px){h3' . $heading . '{min-height:70px!important;margin:1.55em 0 .82em!important;padding:15px 20px 15px 72px!important;font-size:clamp(1.3rem,5.8vw,1.62rem)!important;line-height:1.42!important;letter-spacing:-.052em!important;clip-path:polygon(0 13%,4% 18%,3% 4%,48% 8%,79% 3%,97% 9%,96% 20%,100% 14%,98% 50%,100% 86%,96% 80%,97% 94%,61% 91%,30% 97%,3% 93%,4% 81%,0 87%,2% 50%)!important}h3' . $heading . '::after{left:3px;width:46px;height:50px}';
					if ( $subheading_asset ) {
						$css .= 'h3' . $heading . '::before{left:7px;width:58px;height:52px}';
					}
					$css .= '}';
					$css .= '@media(max-width:380px){h3' . $heading . '{padding:14px 16px 14px 66px!important;font-size:1.36rem!important;line-height:1.43!important}h3' . $heading . '::after{width:43px;height:47px}';
					if ( $subheading_asset ) {
						$css .= 'h3' . $heading . '::before{left:6px;width:54px;height:49px}';
					}
						$css .= '}';
					}
					if ( 'christmas-card-complete-v1' === $heading_design ) {
						$css .= self::generate_complete_christmas_css(
							$heading,
							$list,
							$qa,
							$box,
							$box_point,
							$box_special,
							$button,
							$primary,
							$secondary,
							$accent,
							$background,
							$surface,
							$subheading_asset,
							$special_asset,
							$qa_asset
						);
					}
					continue;
			}

			$css .= $heading . '{box-sizing:border-box;max-width:100%;overflow-wrap:anywhere;color:' . $text . '!important}';
			$css .= 'h2' . $heading . '{padding:17px 20px!important;background:linear-gradient(135deg,' . $background . ',' . $surface . ')!important;border:2px solid ' . $primary . '!important;border-left:9px solid ' . $secondary . '!important;border-radius:' . $radius . 'px!important;box-shadow:0 6px 16px rgba(0,0,0,.10)!important}';
			$css .= 'h3' . $heading . '{padding:12px 15px!important;background:' . $surface . '!important;border:0!important;border-left:7px solid ' . $primary . '!important;border-bottom:2px solid ' . $accent . '!important;border-radius:' . max( 4, $radius - 5 ) . 'px!important}';
			$css .= 'h4' . $heading . '{padding:9px 11px!important;background:transparent!important;border:0!important;border-bottom:4px double ' . $secondary . '!important;border-radius:0!important}';
			$css .= 'h5' . $heading . '{padding:7px 10px!important;background:' . $background . '!important;border:0!important;border-left:4px solid ' . $accent . '!important;border-radius:' . max( 3, $radius - 8 ) . 'px!important}';
			$css .= 'h6' . $heading . '{padding:4px 0 4px 16px!important;background:transparent!important;border:0!important;border-radius:0!important}';
			$css .= 'h6' . $heading . '::before{content:"";display:inline-block;width:8px;height:8px;margin:0 8px 1px -16px;border-radius:50%;background:' . $primary . '}';

			$css .= 'ul' . $list . ',ol' . $list . '{list-style:none!important;padding-left:0!important;counter-reset:aichi-palette-list-counter}';
			$css .= 'ul' . $list . '>li,ol' . $list . '>li{position:relative;margin:.55em 0!important;padding:11px 12px 11px 45px!important;background:' . $surface . '!important;border:1px solid ' . $accent . '!important;border-radius:' . $radius . 'px!important;color:' . $text . '!important}';
			$css .= 'ul' . $list . '>li::before,ol' . $list . '>li::before{position:absolute;left:13px;top:10px;display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background:' . $secondary . ';color:#fff;font-size:12px;font-weight:900}';
			$css .= 'ul' . $list . '>li::before{content:"✓"}';
			$css .= 'ol' . $list . '>li{counter-increment:aichi-palette-list-counter}';
			$css .= 'ol' . $list . '>li::before{content:counter(aichi-palette-list-counter)}';

			$css .= $qa . '{padding:18px!important;background:linear-gradient(135deg,' . $background . ',' . $surface . ')!important;border:2px solid ' . $primary . '!important;border-radius:' . $radius . 'px!important;color:' . $text . '!important}';
			$css .= $qa . ' .aichi-palette-question{border-bottom:2px dotted ' . $accent . '!important}';
			$css .= $qa . ' .aichi-palette-question::before{background:' . $primary . '!important;color:#fff!important}';
			$css .= $qa . ' .aichi-palette-answer::before{background:' . $secondary . '!important;color:#fff!important;border-color:' . $secondary . '!important}';

			$css .= $box . '{padding:20px!important;background:linear-gradient(135deg,' . $background . ',' . $surface . ')!important;border:2px solid ' . $secondary . '!important;border-radius:' . $radius . 'px!important;color:' . $text . '!important;box-shadow:0 6px 16px rgba(0,0,0,.08)!important;overflow:visible!important}';
			$css .= $box . '::before{content:"POINT";background:' . $primary . '!important;color:#fff!important}';
			$css .= $box_point . '::before{content:"POINT";background:' . $primary . '!important;color:#fff!important}';
			$css .= $box_special . '{background:linear-gradient(135deg,#fffdf5,' . $background . ')!important;border:3px solid ' . $accent . '!important;box-shadow:0 7px 18px rgba(159,112,21,.15)!important}';
			$css .= $box_special . '::before{content:"購入者限定特典";background:' . $accent . '!important;color:#51380d!important}';

			$css .= $button . '>a,' . $button . ' .btn,' . $button . ' .wp-block-button__link{background:linear-gradient(135deg,' . $primary . ',' . $secondary . ')!important;border:2px solid ' . $accent . '!important;border-radius:' . max( 10, $radius ) . 'px!important;color:#fff!important;box-shadow:0 5px 12px rgba(0,0,0,.16)!important}';

			if ( $heading_asset ) {
				$css .= $heading . '{position:relative!important}';
				$css .= 'h2' . $heading . '{min-height:92px!important;padding-right:105px!important}';
				$css .= 'h2' . $heading . '::after{content:"";position:absolute;right:13px;top:50%;width:78px;height:78px;transform:translateY(-50%);background:url("' . $heading_asset . '") center/contain no-repeat;pointer-events:none}';
				$css .= $qa . '{position:relative!important;overflow:hidden!important}';
				$css .= $qa . '>*{position:relative;z-index:1}';
				$css .= $qa . '::after{content:"";position:absolute;right:9px;bottom:7px;width:48px;height:48px;background:url("' . $heading_asset . '") center/contain no-repeat;opacity:.16;pointer-events:none}';
			}

			if ( $accent_asset ) {
				$css .= $box . '{position:relative!important;min-height:128px!important;padding-right:132px!important;overflow:visible!important}';
				$css .= $box . '>*{position:relative;z-index:1}';
				$css .= $box . '::after{content:"";position:absolute;right:10px;bottom:4px;width:112px;height:92px;background:url("' . $accent_asset . '") right bottom/contain no-repeat;pointer-events:none}';
			}
			if ( $heading_asset ) {
				$css .= $box_special . '::after{content:"";position:absolute;right:16px;bottom:13px;width:82px;height:82px;background:url("' . $heading_asset . '") center/contain no-repeat;pointer-events:none}';
			}

			$css .= '@media(max-width:782px){h2' . $heading . '{padding:14px 13px!important}ul' . $list . '>li,ol' . $list . '>li{padding-right:10px!important}';
			if ( $heading_asset ) {
				$css .= 'h2' . $heading . '{min-height:76px!important;padding-right:73px!important}h2' . $heading . '::after{right:8px;width:56px;height:56px}';
			}
			if ( $accent_asset ) {
				$css .= $box . '{min-height:0!important;padding-right:18px!important;padding-bottom:88px!important}' . $box . '::after{right:8px;bottom:5px;width:92px;height:74px}';
			}
			if ( $heading_asset ) {
				$css .= $box_special . '::after{right:10px;bottom:8px;width:70px;height:70px}';
			}
			$css .= '}';
		}

		return $css;
	}

	/**
	 * 完成済みH2へは触れず、クリスマスコーデのH3仕上げと残りのパーツだけを生成します。
	 */
	private static function generate_complete_christmas_css( $heading, $list, $qa, $box, $box_point, $box_special, $button, $primary, $secondary, $accent, $background, $surface, $subheading_asset, $special_asset, $qa_asset ) {
		$css = '';

		$css .= 'h2' . $heading . '{z-index:0!important;-webkit-text-stroke:2.5px #fff!important;paint-order:stroke fill!important;text-shadow:-1px -1px 0 #fff,1px -1px 0 #fff,-1px 1px 0 #fff,1px 1px 0 #fff!important}h2' . $heading . '::before,h2' . $heading . '::after{z-index:-1!important}';
		$css .= '@media(max-width:782px){h2' . $heading . '{-webkit-text-stroke:2px #fff!important;text-shadow:-.8px -.8px 0 #fff,.8px -.8px 0 #fff,-.8px .8px 0 #fff,.8px .8px 0 #fff!important}}';
		$css .= 'h3' . $heading . '{padding-left:102px!important}h3' . $heading . '::after{left:8px;width:48px;height:54px}';
		if ( $subheading_asset ) {
			$css .= 'h3' . $heading . '::before{left:14px;width:70px;height:60px}';
		}

		$css .= 'h4' . $heading . '{box-sizing:border-box!important;position:relative!important;max-width:100%!important;margin:1.65em 0 .72em!important;padding:8px 12px 13px 38px!important;overflow-wrap:anywhere!important;word-break:auto-phrase!important;color:' . $secondary . '!important;background:transparent!important;border:0!important;border-radius:0!important;box-shadow:none!important;font-family:"AICHI Christmas Pop",sans-serif!important;font-size:clamp(1.24rem,1.9vw,1.52rem)!important;font-weight:400!important;line-height:1.48!important;letter-spacing:-.04em!important}';
		$css .= 'h4' . $heading . '::before{content:"";position:absolute;left:8px;top:12px;width:19px;height:19px;border:3px solid ' . $accent . ';border-radius:48% 52% 46% 54%;background:radial-gradient(circle at 36% 32%,#fff8f0 0 2px,transparent 2.6px),' . $primary . ';box-shadow:0 -8px 0 -6px ' . $accent . ';transform:rotate(-6deg)}';
		$css .= 'h4' . $heading . '::after{content:"";position:absolute;left:37px;right:5px;bottom:2px;height:7px;background:linear-gradient(178deg,transparent 0 35%,' . $primary . ' 36% 52%,transparent 53%),linear-gradient(182deg,transparent 0 64%,rgba(201,71,61,.42) 65% 76%,transparent 77%);border-radius:48% 52% 44% 56%;transform:rotate(-.18deg);pointer-events:none}';
		$css .= 'h5' . $heading . '{box-sizing:border-box!important;position:relative!important;max-width:100%!important;margin:1.45em 0 .62em!important;padding:5px 10px 9px 28px!important;overflow-wrap:anywhere!important;word-break:auto-phrase!important;color:' . $primary . '!important;background:transparent!important;border:0!important;border-radius:0!important;box-shadow:none!important;font-family:"AICHI Christmas Pop",sans-serif!important;font-size:clamp(1.08rem,1.48vw,1.27rem)!important;font-weight:400!important;line-height:1.5!important;letter-spacing:-.035em!important}';
		$css .= 'h5' . $heading . '::before{content:"★";position:absolute;left:5px;top:5px;color:' . $accent . ';font-family:serif;font-size:16px;line-height:1;transform:rotate(-8deg)}h5' . $heading . '::after{content:"";position:absolute;left:28px;bottom:1px;width:92px;height:5px;background:linear-gradient(178deg,transparent 0 35%,' . $secondary . ' 36% 58%,transparent 59%);border-radius:50%;transform:rotate(-.35deg)}';
		$css .= 'h6' . $heading . '{box-sizing:border-box!important;position:relative!important;max-width:100%!important;margin:1.25em 0 .52em!important;padding:3px 8px 4px 24px!important;overflow-wrap:anywhere!important;word-break:auto-phrase!important;color:' . $secondary . '!important;background:transparent!important;border:0!important;border-radius:0!important;box-shadow:none!important;font-family:"AICHI Christmas Pop",sans-serif!important;font-size:clamp(.98rem,1.18vw,1.1rem)!important;font-weight:400!important;line-height:1.55!important;letter-spacing:-.025em!important}';
		$css .= 'h6' . $heading . '::before{content:"✦";position:absolute;left:5px;top:4px;color:' . $primary . ';font-family:serif;font-size:14px;line-height:1;transform:rotate(8deg)}h6' . $heading . '::after{content:"";display:inline-block;width:34px;height:2px;margin:0 0 3px 9px;background:' . $accent . ';border-radius:50%;transform:rotate(-1deg)}';

		$css .= 'ul' . $list . ',ol' . $list . '{list-style:none!important;max-width:100%!important;margin:1.15em 0!important;padding-left:0!important;counter-reset:aichi-palette-list-counter}';
		$css .= 'ul' . $list . '>li,ol' . $list . '>li{box-sizing:border-box!important;position:relative!important;margin:.48em 0!important;padding:10px 13px 10px 43px!important;overflow-wrap:anywhere!important;color:#233d32!important;background:linear-gradient(102deg,rgba(255,248,240,.94),rgba(255,253,247,.72))!important;border:0!important;border-left:3px solid rgba(46,107,78,.34)!important;border-radius:5px 12px 7px 10px!important;line-height:1.72!important}';
		$css .= 'ul' . $list . '>li::before,ol' . $list . '>li::before{position:absolute!important;left:11px!important;top:12px!important;display:flex!important;align-items:center!important;justify-content:center!important;width:23px!important;height:23px!important;border:2px solid ' . $accent . '!important;border-radius:48% 52% 45% 55%!important;background:' . $primary . '!important;color:#fff8f0!important;font-size:11px!important;font-weight:900!important;line-height:1!important;transform:rotate(-5deg)!important;box-sizing:border-box!important}';
		$css .= 'ul' . $list . '>li::before{content:"★"!important;color:#f4d884!important}ol' . $list . '>li{counter-increment:aichi-palette-list-counter}ol' . $list . '>li::before{content:counter(aichi-palette-list-counter)!important}';

		$css .= $qa . '{box-sizing:border-box!important;position:relative!important;max-width:100%!important;margin:1.55em 0!important;padding:18px 74px 18px 18px!important;overflow:hidden!important;color:#233d32!important;background:radial-gradient(circle at 88% 76%,rgba(212,178,106,.16) 0 2px,transparent 2.8px),linear-gradient(142deg,' . $background . ',' . $surface . ')!important;border:2px solid rgba(212,178,106,.72)!important;border-radius:18px 11px 20px 13px!important;box-shadow:0 6px 15px rgba(75,53,31,.08)!important}';
		$css .= $qa . '>*{position:relative!important;z-index:1}' . $qa . ' .aichi-palette-question,' . $qa . ' .aichi-palette-answer{box-sizing:border-box!important;margin:0!important;padding:13px 8px 13px 56px!important;min-height:50px!important;color:#233d32!important;line-height:1.75!important}';
		$css .= $qa . ' .aichi-palette-question{border-bottom:2px dotted rgba(212,178,106,.72)!important}' . $qa . ' .aichi-palette-question::before,' . $qa . ' .aichi-palette-answer::before{left:5px!important;top:11px!important;width:37px!important;height:37px!important;border-radius:45% 55% 48% 52%!important;color:#fff8f0!important;font-family:"AICHI Christmas Pop",sans-serif!important;font-size:16px!important;font-weight:400!important;box-shadow:0 3px 7px rgba(58,42,27,.12)!important;transform:rotate(-4deg)!important}';
		$css .= $qa . ' .aichi-palette-question::before{content:"Q"!important;background:' . $primary . '!important;border:2px solid #e48a7f!important}' . $qa . ' .aichi-palette-answer::before{content:"A"!important;background:' . $secondary . '!important;border:2px solid #7ea68f!important;transform:rotate(3deg)!important}';
		if ( $qa_asset ) {
			$css .= $qa . '::after{content:"";position:absolute;z-index:0;right:9px;top:8px;width:55px;height:55px;background:url("' . $qa_asset . '") center/contain no-repeat;pointer-events:none}';
		}

		$css .= $box . '{box-sizing:border-box!important;position:relative!important;max-width:100%!important;margin:1.75em 0!important;padding:27px 20px 19px!important;overflow:visible!important;color:#233d32!important;background:linear-gradient(142deg,' . $background . ',' . $surface . ')!important;border:2px solid ' . $secondary . '!important;border-radius:15px 9px 17px 11px!important;box-shadow:0 6px 15px rgba(45,84,62,.09)!important;line-height:1.75!important}';
		$css .= $box . '>*{position:relative!important;z-index:1}' . $box . '::before{content:"POINT"!important;position:absolute!important;z-index:3!important;top:-17px!important;left:57px!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;min-width:78px!important;min-height:31px!important;padding:5px 15px!important;overflow:visible!important;white-space:nowrap!important;color:#fff8f0!important;background:' . $primary . '!important;border:2px solid #fff8f0!important;border-radius:13px 8px 13px 9px!important;box-shadow:0 3px 7px rgba(128,49,43,.18)!important;font-family:"AICHI Christmas Pop",sans-serif!important;font-size:13px!important;font-weight:400!important;line-height:1.35!important;letter-spacing:.015em!important;box-sizing:border-box!important}';
		if ( $subheading_asset ) {
			$css .= $box . '::after{content:"";position:absolute;z-index:4;left:13px;top:-24px;width:48px;height:39px;background:url("' . $subheading_asset . '") center/contain no-repeat;pointer-events:none}';
		}
		$css .= $box_point . '{background:linear-gradient(142deg,' . $background . ',' . $surface . ')!important;border-color:' . $secondary . '!important}' . $box_point . '::before{content:"POINT"!important}';
		$css .= $box_special . '{min-height:126px!important;padding:31px 128px 23px 22px!important;background:radial-gradient(circle at 82% 25%,rgba(212,178,106,.18) 0 2px,transparent 2.8px),linear-gradient(138deg,#fffdf5,' . $background . ')!important;border:3px solid ' . $accent . '!important;border-radius:18px 10px 20px 12px!important;box-shadow:0 8px 18px rgba(159,112,21,.14)!important}';
		$css .= $box_special . '::before{content:"購入者限定特典"!important;left:18px!important;min-width:0!important;min-height:32px!important;padding:5px 16px!important;color:#51380d!important;background:#f0cd68!important;border:2px solid #fff8f0!important;border-radius:13px 8px 13px 9px!important;white-space:nowrap!important}';
		if ( $special_asset ) {
			$css .= $box_special . '::after{content:"";position:absolute;z-index:0;left:auto;right:14px;top:auto;bottom:8px;width:102px;height:91px;background:url("' . $special_asset . '") right bottom/contain no-repeat;pointer-events:none}';
		}

		$css .= $button . '>a,' . $button . ' .btn,' . $button . ' .wp-block-button__link{position:relative!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;max-width:100%!important;min-height:48px!important;padding:13px 27px!important;overflow-wrap:anywhere!important;color:#fff8f0!important;background:linear-gradient(175deg,#dc5a50 0%,' . $primary . ' 58%,#b83f37 100%)!important;border:2px solid #f0d58a!important;border-radius:17px 11px 18px 12px!important;box-shadow:0 5px 0 #96342f,0 9px 16px rgba(112,43,37,.18)!important;font-family:"AICHI Christmas Pop",sans-serif!important;font-size:clamp(1rem,1.35vw,1.15rem)!important;font-weight:400!important;line-height:1.5!important;letter-spacing:-.02em!important;text-align:center!important;text-decoration:none!important;transition:transform .18s ease,box-shadow .18s ease,filter .18s ease!important;box-sizing:border-box!important}';
		$css .= $button . '>a::before,' . $button . ' .btn::before,' . $button . ' .wp-block-button__link::before{content:"★";flex:0 0 auto;margin-right:9px;color:#f4d884;font-family:serif;font-size:.9em;line-height:1;transform:rotate(-8deg)}';
		$css .= $button . '>a:hover,' . $button . ' .btn:hover,' . $button . ' .wp-block-button__link:hover{transform:translateY(2px)!important;filter:saturate(1.05)!important;box-shadow:0 3px 0 #96342f,0 6px 11px rgba(112,43,37,.18)!important}';

		$css .= '@media(max-width:782px){h3' . $heading . '{padding-left:77px!important}h3' . $heading . '::after{left:5px;width:42px;height:48px}';
		if ( $subheading_asset ) {
			$css .= 'h3' . $heading . '::before{left:9px;width:59px;height:51px}';
		}
		$css .= 'h4' . $heading . '{padding:7px 8px 12px 34px!important;font-size:clamp(1.16rem,5vw,1.35rem)!important}h4' . $heading . '::before{left:6px;top:10px;width:17px;height:17px}h4' . $heading . '::after{left:33px;right:3px}h5' . $heading . '{padding-left:26px!important;font-size:clamp(1.05rem,4.5vw,1.2rem)!important}h6' . $heading . '{font-size:clamp(.98rem,4.1vw,1.08rem)!important}';
		$css .= $qa . '{padding:15px 54px 15px 13px!important;border-radius:15px 9px 16px 11px!important}' . $qa . ' .aichi-palette-question,' . $qa . ' .aichi-palette-answer{padding:12px 4px 12px 48px!important;line-height:1.72!important}' . $qa . ' .aichi-palette-question::before,' . $qa . ' .aichi-palette-answer::before{left:2px!important;width:34px!important;height:34px!important}';
		if ( $qa_asset ) {
			$css .= $qa . '::after{right:6px;top:7px;width:43px;height:43px}';
		}
		$css .= $box . '{padding:26px 14px 17px!important;border-radius:13px 8px 14px 9px!important}' . $box . '::before{left:51px!important;min-height:30px!important;padding:5px 12px!important;font-size:12px!important}';
		if ( $subheading_asset ) {
			$css .= $box . '::after{left:10px;top:-21px;width:43px;height:35px}';
		}
		$css .= $box_special . '{min-height:0!important;padding:29px 14px 91px!important}' . $box_special . '::before{left:13px!important;padding:5px 13px!important}';
		if ( $special_asset ) {
			$css .= $box_special . '::after{left:auto;right:9px;top:auto;bottom:5px;width:88px;height:76px}';
		}
		$css .= $button . '>a,' . $button . ' .btn,' . $button . ' .wp-block-button__link{width:100%!important;min-height:48px!important;padding:13px 16px!important;font-size:1rem!important}}';
		$css .= '@media(max-width:380px){h3' . $heading . '{padding-left:70px!important}';
		if ( $subheading_asset ) {
			$css .= 'h3' . $heading . '::before{left:7px;width:55px;height:48px}';
		}
		$css .= $qa . '{padding-right:45px!important}' . $box_special . '::before{font-size:11px!important;padding-left:11px!important;padding-right:11px!important}}';

		return $css;
	}

	/**
	 * 検証済みの画像だけをCSS用data URIへ変換します。
	 */
	private static function asset_data_uri( $coordinate, $slot ) {
		if ( empty( $coordinate['assets'][ $slot ]['mime'] ) || empty( $coordinate['assets'][ $slot ]['data'] ) ) {
			return '';
		}
		$mime = $coordinate['assets'][ $slot ]['mime'];
		$data = $coordinate['assets'][ $slot ]['data'];
		if ( ! in_array( $mime, array( 'image/png', 'image/webp', 'image/jpeg' ), true ) || ! preg_match( '/^[A-Za-z0-9+\/=]+$/', $data ) ) {
			return '';
		}
		return 'data:' . $mime . ';base64,' . $data;
	}

	/**
	 * 検証・保存済みのuploads相対パスだけを背景画像URLへ変換します。
	 */
	private static function stored_asset_url( $coordinate, $slot ) {
		if ( empty( $coordinate['assets'][ $slot ]['mime'] ) || empty( $coordinate['assets'][ $slot ]['file'] ) ) {
			return '';
		}
		$mime = $coordinate['assets'][ $slot ]['mime'];
		$file = $coordinate['assets'][ $slot ]['file'];
		if ( ! in_array( $mime, array( 'image/png', 'image/webp', 'image/jpeg' ), true ) ||
			! is_string( $file ) ||
			! preg_match( '#^' . preg_quote( self::UPLOAD_SUBDIR, '#' ) . '/chiichan-[a-z0-9][a-z0-9-]{1,54}/[a-z0-9-]+\.(?:png|webp|jpg)$#', $file ) ) {
			return '';
		}
		$upload = wp_upload_dir();
		if ( ! empty( $upload['error'] ) || empty( $upload['baseurl'] ) ) {
			return '';
		}
		return esc_url_raw( trailingslashit( $upload['baseurl'] ) . $file );
	}

	private static function generate_page_background_css( $coordinate ) {
		if ( empty( $coordinate['pageBackground'] ) || empty( $coordinate['id'] ) ) {
			return '';
		}
		$background = $coordinate['pageBackground'];
		$desktop    = self::stored_asset_url( $coordinate, 'backgroundDesktop' );
		$mobile     = self::stored_asset_url( $coordinate, 'backgroundMobile' );
		if ( ! $desktop ) {
			return '';
		}
		if ( ! $mobile ) {
			$mobile = $desktop;
		}
		$id             = $coordinate['id'];
		$page           = '.aichi-ai-page-set-' . $id;
		$body           = 'body' . $page;
		$editor         = '.editor-styles-wrapper' . $page;
		/*
		 * 公開画面ではテーマ側のbody_classフィルター順に依存せず、
		 * 記事本文内へ保存されたページコーデ印から紙面を特定する。
		 * Cocoonの .article / .entry-content は、いずれか片方だけが白い背景を持つ場合があるため両方を対象にする。
		 */
		/*
		 * ページ印がWordPressの保存処理で省かれる環境でも、着せ替え済みH2の
		 * 固有クラスは公開HTMLに残る。背景の判定にはこちらを主として使用する。
		 */
		$front_marker   = '.aichi-palette-heading-custom-' . $id;
		$front_paper    = 'body .main:has(' . $front_marker . '),' .
			'body .site-main:has(' . $front_marker . '),' .
			'body #main:has(' . $front_marker . '),' .
			'body .article:has(' . $front_marker . '),' .
			'body .entry-content:has(' . $front_marker . '),' .
			'body .wp-block-post-content:has(' . $front_marker . '),' .
			'body .main:has(' . $page . '),' .
			'body .article:has(' . $page . ')';
		$base_color     = $background['baseColor'];
		$content_rgba   = self::hex_to_rgba( $background['contentColor'], $background['contentOpacity'] / 100 );
		$desktop_pos    = str_replace( '-', ' ', $background['desktopPosition'] );
		$mobile_pos     = str_replace( '-', ' ', $background['mobilePosition'] );

		/*
		 * ページ外側ではなく、記事本文の紙面へ半調イラストを重ねる。
		 * 透明背景の素材だけが本文カードの端に現れるため、長文の可読性を保つ。
		 */
		$paper = $body . ' .main,' . $body . ' .site-main,' . $body . ' main.wp-block-group,' . $body . ' #main.article,' . $body . ' #main .article,' . $body . ' .main .article,' . $body . ' article.article,' . $front_paper . ',' . $editor . ' .is-root-container';
		$css  = $body . ',' . $editor . '{background-color:' . $base_color . '!important;background-image:none!important}';
		$css .= $body . ' #container,' . $body . ' .site,' . $body . ' .site-content,' . $body . ' .content-in{background-color:transparent!important}';
		/* Cocoonでは .article が .main の白い面を覆うため、公開側の本文カードも明示的に対象にする。 */
		$css .= $paper . '{box-sizing:border-box!important;background-color:' . $content_rgba . '!important;background-image:url("' . $desktop . '")!important;background-repeat:' . $background['desktopRepeat'] . '!important;background-position:' . $desktop_pos . '!important;background-size:' . $background['desktopSize'] . '!important;background-attachment:scroll!important;border-radius:24px!important;box-shadow:0 12px 34px rgba(72,52,35,.12)!important}';
		$css .= $body . ' .article .entry-content,' . $body . ' .article .article-content,' . $body . ' .wp-block-post-content{box-sizing:border-box!important;max-width:100%!important;background-color:transparent!important}';
		$css .= '@media(max-width:782px){' . $paper . '{background-image:url("' . $mobile . '")!important;background-repeat:' . $background['mobileRepeat'] . '!important;background-position:' . $mobile_pos . '!important;background-size:' . $background['mobileSize'] . '!important;border-radius:16px!important;box-shadow:0 7px 22px rgba(72,52,35,.1)!important}}';
		return $css;
	}

	private static function hex_to_rgba( $hex, $alpha ) {
		$hex = ltrim( (string) $hex, '#' );
		if ( ! preg_match( '/^[0-9a-f]{6}$/i', $hex ) ) {
			return 'rgba(255,255,255,0.96)';
		}
		$alpha = max( 0, min( 1, (float) $alpha ) );
		return sprintf(
			'rgba(%d,%d,%d,%.2f)',
			hexdec( substr( $hex, 0, 2 ) ),
			hexdec( substr( $hex, 2, 2 ) ),
			hexdec( substr( $hex, 4, 2 ) ),
			$alpha
		);
	}

	private static function error( $code, $message, $status ) {
		return new WP_Error( $code, $message, array( 'status' => $status ) );
	}
}
