<?php
if (!defined('APP_ROOT')) exit;

/*
 * The Blackout preset adapts palette relationships from Blackout Theme by
 * obscurefreeman (晦涩弗里曼), licensed MIT:
 * https://theme.typoraio.cn/theme/Blackout/
 * https://github.com/obscurefreeman/typora_theme_blackout
 * Only fixed forum theme values are mapped; no upstream CSS, assets, or fonts
 * are bundled or loaded.
 */

/*
 * Typora Docsify preset research references:
 * https://theme.typoraio.cn/theme/Typora-Docsify/
 * https://github.com/Erimus-Koo/theme_typora_docsify
 * Original theme author: Erimus-Koo. The upstream repository declares no
 * license. This plugin independently maps public palette facts to fixed forum
 * fields and does not copy or redistribute upstream CSS, LESS, selectors,
 * screenshots, release archives, or other theme source.
 */

/*
 * Mist Blue preset research references:
 * https://theme.typoraio.cn/theme/Mist-Blue/
 * https://github.com/itworksig/typora-mist-blue
 * Research commit: e640dbc91362f906be3f1ad7d5aeeb02f767f123
 * Original theme author: itworksig. The upstream theme is licensed MIT and
 * provides one light stylesheet with no official dark variant or bundled font
 * binaries. This plugin maps palette relationships to fixed forum fields; its
 * dark palette is an independent forum adaptation. No upstream CSS, selectors,
 * screenshots, archives, or font files are bundled.
 *
 * Embedded offline font comes from Fontsource 5.3.0:
 * https://fontsource.org/fonts/poppins
 * Poppins 400/600 Latin WOFF2:
 * Copyright 2020 The Poppins Project Authors
 * (https://github.com/itfoundry/Poppins)
 * The code (monospace) font is no longer embedded; it falls back to the
 * user's system monospace fonts or an account-uploaded custom font.
 * The embedded Poppins font remains licensed under SIL Open Font License 1.1.
 * Complete shared license text:
 *
 * This Font Software is licensed under the SIL Open Font License, Version 1.1.
 * This license is copied below, and is also available with a FAQ at:
 * http://scripts.sil.org/OFL
 *
 *
 * -----------------------------------------------------------
 * SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
 * -----------------------------------------------------------
 *
 * PREAMBLE
 * The goals of the Open Font License (OFL) are to stimulate worldwide
 * development of collaborative font projects, to support the font creation
 * efforts of academic and linguistic communities, and to provide a free and
 * open framework in which fonts may be shared and improved in partnership
 * with others.
 *
 * The OFL allows the licensed fonts to be used, studied, modified and
 * redistributed freely as long as they are not sold by themselves. The
 * fonts, including any derivative works, can be bundled, embedded,
 * redistributed and/or sold with any software provided that any reserved
 * names are not used by derivative works. The fonts and derivatives,
 * however, cannot be released under any other type of license. The
 * requirement for fonts to remain under this license does not apply
 * to any document created using the fonts or their derivatives.
 *
 * DEFINITIONS
 * "Font Software" refers to the set of files released by the Copyright
 * Holder(s) under this license and clearly marked as such. This may
 * include source files, build scripts and documentation.
 *
 * "Reserved Font Name" refers to any names specified as such after the
 * copyright statement(s).
 *
 * "Original Version" refers to the collection of Font Software components as
 * distributed by the Copyright Holder(s).
 *
 * "Modified Version" refers to any derivative made by adding to, deleting,
 * or substituting -- in part or in whole -- any of the components of the
 * Original Version, by changing formats or by porting the Font Software to a
 * new environment.
 *
 * "Author" refers to any designer, engineer, programmer, technical
 * writer or other person who contributed to the Font Software.
 *
 * PERMISSION & CONDITIONS
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of the Font Software, to use, study, copy, merge, embed, modify,
 * redistribute, and sell modified and unmodified copies of the Font
 * Software, subject to the following conditions:
 *
 * 1) Neither the Font Software nor any of its individual components,
 * in Original or Modified Versions, may be sold by itself.
 *
 * 2) Original or Modified Versions of the Font Software may be bundled,
 * redistributed and/or sold with any software, provided that each copy
 * contains the above copyright notice and this license. These can be
 * included either as stand-alone text files, human-readable headers or
 * in the appropriate machine-readable metadata fields within text or
 * binary files as long as those fields can be easily viewed by the user.
 *
 * 3) No Modified Version of the Font Software may use the Reserved Font
 * Name(s) unless explicit written permission is granted by the corresponding
 * Copyright Holder. This restriction only applies to the primary font name as
 * presented to the users.
 *
 * 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
 * Software shall not be used to promote, endorse or advertise any
 * Modified Version, except to acknowledge the contribution(s) of the
 * Copyright Holder(s) and the Author(s) or with their explicit written
 * permission.
 *
 * 5) The Font Software, modified or unmodified, in part or in whole,
 * must be distributed entirely under this license, and must not be
 * distributed under any other license. The requirement for fonts to
 * remain under this license does not apply to any document created
 * using the Font Software.
 *
 * TERMINATION
 * This license becomes null and void if any of the above conditions are
 * not met.
 *
 * DISCLAIMER
 * THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
 * OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
 * DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
 * OTHER DEALINGS IN THE FONT SOFTWARE.
 */

const APPEARANCE_THEME_SCHEMA_VERSION = 2;
const APPEARANCE_THEME_MAX_SCHEMES = 20;
const APPEARANCE_THEME_MAX_PAYLOAD_BYTES = 196608;
const APPEARANCE_THEME_MAX_FONT_BYTES = 2097152;
const APPEARANCE_THEME_MAX_SFNT_BYTES = 33554432;
const APPEARANCE_THEME_CUSTOM_FONT_FAMILY = 'AppearanceThemeCustomUI';

function appearance_theme_invalid(string $message): never {throw new InvalidArgumentException($message);}

const APPEARANCE_THEME_POPPINS_400_WOFF2 =
  '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';

const APPEARANCE_THEME_POPPINS_600_WOFF2 =
  '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';

function appearance_theme_embedded_font_css(): string
{
  $range = 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD';
  return '@font-face{font-family:"AppearanceThemePoppins";font-style:normal;' .
      'font-display:swap;font-weight:400;src:url("data:font/woff2;base64,' .
      APPEARANCE_THEME_POPPINS_400_WOFF2 . '") format("woff2");unicode-range:' .
      $range . '}' .
      '@font-face{font-family:"AppearanceThemePoppins";font-style:normal;' .
      'font-display:swap;font-weight:600;src:url("data:font/woff2;base64,' .
      APPEARANCE_THEME_POPPINS_600_WOFF2 . '") format("woff2");unicode-range:' .
      $range . '}';
}

function appearance_theme_uint32(string $data, int $offset): int
{
  $length = strlen($data);
  if ($offset < 0 || $offset > $length - 4) appearance_theme_invalid('32 位字段不完整。');
  $value = unpack('Nvalue', $data, $offset)['value'] ?? null;
  if (!is_int($value) || $value < 0) appearance_theme_invalid('32 位字段超出平台范围。');
  return $value;
}

function appearance_theme_woff2_uint_base128(string $data, int &$offset): int
{
  $length = strlen($data);
  if ($offset < 0) appearance_theme_invalid('字体目录偏移无效。');
  $value = 0;
  for ($i = 0; $i < 5; $i++) {
      if ($offset >= $length) appearance_theme_invalid('字体目录不完整。');
      $byte = ord($data[$offset++]);
      if ($i === 0 && $byte === 0x80) appearance_theme_invalid('字体目录编码非规范。');
      $chunk = $byte & 0x7f;
      if ($value > 0x01ffffff || $value > intdiv(PHP_INT_MAX - $chunk, 128)) {
          appearance_theme_invalid('字体目录数值过大。');
      }
      $value = ($value << 7) | $chunk;
      if (($byte & 0x80) === 0) return $value;
  }
  appearance_theme_invalid('字体目录编码无效。');
}

function appearance_theme_validate_decoder(string $font): void
{
  if (!function_exists('proc_open')) throw new RuntimeException('服务器未启用字体解码器。');
  $dir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'appearance-font-' . bin2hex(random_bytes(12));
  if (!mkdir($dir, 0700)) throw new RuntimeException('字体验证临时文件创建失败。');
  $in = $dir . '/font.woff2'; $out = $dir . '/font.ttf';
  try {
      if (file_put_contents($in, $font, LOCK_EX) !== strlen($font)) throw new RuntimeException('字体验证临时文件创建失败。');
      @chmod($in, 0600); $null = PHP_OS_FAMILY === 'Windows' ? 'NUL' : '/dev/null';
      $tools = [getenv('APPEARANCE_THEME_WOFF2_DECODER'),'/usr/bin/woff2_decompress','/usr/local/bin/woff2_decompress','/usr/bin/fonttools','/usr/local/bin/fonttools'];
      foreach ($tools as $tool) {
          if (!is_string($tool) || !preg_match('~^(?:/|[A-Za-z]:[\\\\/])~', $tool) || !is_executable($tool)) continue;
          $command = stripos(basename($tool), 'fonttools') === 0 ? [$tool,'ttLib.woff2','decompress','-q','-o',$out,$in] : [$tool,$in];
          @unlink($out); $p = @proc_open($command, [0=>['file',$null,'r'],1=>['file',$null,'a'],2=>['file',$null,'a']], $pipes);
          if (!is_resource($p)) continue;
          $end = microtime(true) + 8;
          do {
              $status = proc_get_status($p); clearstatcache(true, $out);
              $size = is_file($out) ? filesize($out) : false;
              if (!$status['running'] || (is_int($size) && $size > APPEARANCE_THEME_MAX_SFNT_BYTES)) break;
              usleep(10000);
          } while (microtime(true) < $end);
          if ($status['running']) proc_terminate($p, 9);
          proc_close($p); clearstatcache(true, $out); $size = is_file($out) ? filesize($out) : false;
          if (!$status['running'] && $status['exitcode'] === 0 && is_int($size) && $size >= 12 && $size <= APPEARANCE_THEME_MAX_SFNT_BYTES && file_get_contents($out, false, null, 0, 4) === "\0\1\0\0") return;
      }
  } finally { @unlink($in); @unlink($out); @rmdir($dir); }
  appearance_theme_invalid('WOFF2 字体无法安全重建。');
}

function appearance_theme_validate_woff2(string $font, bool $decode = false): array
{
  $length = strlen($font);
  if ($length < 48 || $length > APPEARANCE_THEME_MAX_FONT_BYTES) {
      appearance_theme_invalid('WOFF2 字体大小无效。');
  }
  if (substr($font, 0, 4) !== 'wOF2' || substr($font, 4, 4) !== "\x00\x01\x00\x00") {
      appearance_theme_invalid('仅支持 TrueType 轮廓的 WOFF2 字体。');
  }
  if (appearance_theme_uint32($font, 8) !== $length || substr($font, 14, 2) !== "\0\0") {
      appearance_theme_invalid('WOFF2 文件头无效。');
  }
  $table_count = unpack('nvalue', substr($font, 12, 2))['value'] ?? 0;
  $total_sfnt = appearance_theme_uint32($font, 16);
  $compressed_length = appearance_theme_uint32($font, 20);
  $meta_offset = appearance_theme_uint32($font, 28);
  $meta_length = appearance_theme_uint32($font, 32);
  $meta_original = appearance_theme_uint32($font, 36);
  $private_offset = appearance_theme_uint32($font, 40);
  $private_length = appearance_theme_uint32($font, 44);
  if ($table_count < 9 || $table_count > 64 || $total_sfnt < 12 + 16 * $table_count ||
      $total_sfnt > APPEARANCE_THEME_MAX_SFNT_BYTES || $compressed_length < 1 ||
      $meta_offset !== 0 || $meta_length !== 0 || $meta_original !== 0 ||
      $private_offset !== 0 || $private_length !== 0) {
      appearance_theme_invalid('WOFF2 结构或大小限制无效。');
  }

  $known = ['cmap','head','hhea','hmtx','maxp','name','OS/2','post','cvt ','fpgm','glyf','loca','prep','CFF ','VORG','EBDT','EBLC','gasp','hdmx','kern','LTSH','PCLT','VDMX','vhea','vmtx','BASE','GDEF','GPOS','GSUB','EBSC','JSTF','MATH','CBDT','CBLC','COLR','CPAL','SVG ','sbix','acnt','avar','bdat','bloc','bsln','cvar','fdsc','feat','fmtx','fvar','gvar','hsty','just','lcar','mort','morx','opbd','prop','trak','Zapf','Silf','Glat','Gloc','Feat','Sill'];
  $required = array_fill_keys(['cmap','head','hhea','hmtx','maxp','name','OS/2','post','glyf','loca'], true);
  $allowed = array_fill_keys(['cmap','head','hhea','hmtx','maxp','name','OS/2','post','glyf','loca','cvt ','fpgm','prep','gasp','hdmx','kern','LTSH','PCLT','VDMX','vhea','vmtx','BASE','GDEF','GPOS','GSUB','JSTF','MATH','STAT','DSIG'], true);
  $offset = 48;
  $tags = [];
  $transforms = [];
  $indices = [];
  $orig_total = 0;
  $padded_total = 0;
  $transformed_total = 0;
  for ($i = 0; $i < $table_count; $i++) {
      if ($offset >= $length) appearance_theme_invalid('WOFF2 表目录不完整。');
      $flags = ord($font[$offset++]);
      $tag_index = $flags & 0x3f;
      $transform = $flags >> 6;
      if ($tag_index === 63) {
          if ($offset + 4 > $length) appearance_theme_invalid('WOFF2 自定义表标签不完整。');
          $tag = substr($font, $offset, 4);
          $offset += 4;
      } else {
          $tag = $known[$tag_index] ?? '';
      }
      if ($tag === '' || isset($tags[$tag]) || !isset($allowed[$tag])) {
          appearance_theme_invalid('WOFF2 包含未知、重复或不允许的字体表。');
      }
      $tags[$tag] = true;
      $indices[$tag] = $i;
      $orig_length = appearance_theme_woff2_uint_base128($font, $offset);
      if ($orig_length < 1 || $orig_length > APPEARANCE_THEME_MAX_SFNT_BYTES ||
          $orig_total > APPEARANCE_THEME_MAX_SFNT_BYTES - $orig_length) {
          appearance_theme_invalid('WOFF2 字体表大小无效。');
      }
      $orig_total += $orig_length;
      $padded_total += ($orig_length + 3) & ~3;
      if ($padded_total > APPEARANCE_THEME_MAX_SFNT_BYTES) appearance_theme_invalid('WOFF2 字体表总大小无效。');
      $transformed = false;
      if ($tag === 'glyf' || $tag === 'loca') {
          if (!in_array($transform, [0, 3], true)) appearance_theme_invalid('WOFF2 TrueType 表变换无效。');
          $transformed = $transform === 0;
      } elseif ($tag === 'hmtx') {
          if (!in_array($transform, [0, 1], true)) appearance_theme_invalid('WOFF2 水平度量表变换无效。');
          $transformed = $transform === 1;
      } elseif ($transform !== 0) {
          appearance_theme_invalid('WOFF2 包含不支持的表变换。');
      }
      $transform_length = $orig_length;
      if ($transformed) {
          $transform_length = appearance_theme_woff2_uint_base128($font, $offset);
          if (($tag === 'loca' && $transform_length !== 0) ||
              ($tag === 'glyf' && $transform_length < 36) ||
              ($tag === 'hmtx' && $transform_length < 1)) {
              appearance_theme_invalid('WOFF2 字体表变换长度无效。');
          }
      }
      $transforms[$tag] = $transform;
      $transformed_total += $transform_length;
      if ($transformed_total > APPEARANCE_THEME_MAX_SFNT_BYTES) appearance_theme_invalid('WOFF2 变换数据总大小无效。');
  }
  if (array_diff_key($required, $tags)) appearance_theme_invalid('WOFF2 缺少必要的 TrueType 表。');
  if ($transforms['glyf'] !== $transforms['loca'] || $indices['glyf'] >= $indices['loca']) {
      appearance_theme_invalid('WOFF2 TrueType 表变换不一致。');
  }
  $expected_sfnt = 12 + 16 * $table_count + $padded_total;
  if ($total_sfnt !== $expected_sfnt) appearance_theme_invalid('WOFF2 解压字体长度无效。');
  $compressed_end = $offset + $compressed_length;
  $padding = $length - $compressed_end;
  if ($compressed_end > $length || $padding > 3 || ($length & 3) !== 0 ||
      ($padding > 0 && substr($font, $compressed_end) !== str_repeat("\0", $padding))) {
      appearance_theme_invalid('WOFF2 含元数据、私有数据或尾随内容。');
  }
  if ($decode) appearance_theme_validate_decoder($font);
  return ['bytes' => $length, 'sha256' => hash('sha256', $font), 'stream_length' => $transformed_total, 'tables' => array_keys($tags)];
}

function appearance_theme_font_label(string $filename): string
{
  $raw = str_replace('\\', '/', $filename);
  $slash = strrpos($raw, '/');
  $name = $slash === false ? $raw : substr($raw, $slash + 1);
  $name = preg_replace('/\.woff2\z/i', '', $name) ?? '';
  if ($name === '' || preg_match('//u', $name) !== 1) return '自定义字体';
  $name = preg_replace('/[\x00-\x1F\x7F-\x9F\x{061C}\x{200B}\x{200E}\x{200F}\x{202A}-\x{202E}\x{2060}\x{2066}-\x{2069}\x{FEFF}]+/u', ' ', $name) ?? '';
  $name = trim(preg_replace('/\s+/u', ' ', $name) ?? '');
  if ($name === '') return '自定义字体';
  $characters = preg_split('//u', $name, -1, PREG_SPLIT_NO_EMPTY);
  if (is_array($characters) && count($characters) > 60) $name = implode('', array_slice($characters, 0, 60));
  return $name;
}

function appearance_theme_font_record(int $user_id): ?array
{
  if ($user_id < 1) return null;
  $row = one('SELECT display_name,data_base64,byte_length,sha256,stream_length FROM plugin_appearance_theme_fonts WHERE user_id=?', [$user_id]);
  if (!$row || !is_string($row['display_name'] ?? null) || !is_string($row['data_base64'] ?? null) ||
      !is_string($row['sha256'] ?? null) || !preg_match('/^[a-f0-9]{64}$/', $row['sha256'])) return null;
  $stored_bytes = $row['byte_length'] ?? null;
  if (!(is_int($stored_bytes) || (is_string($stored_bytes) && ctype_digit($stored_bytes)))) return null;
  $bytes = (int)$stored_bytes;
  $encoded = $row['data_base64'];
  $encoded_length = 4 * intdiv($bytes + 2, 3);
  if ($bytes < 1 || $bytes > APPEARANCE_THEME_MAX_FONT_BYTES || strlen($encoded) !== $encoded_length ||
      preg_match('/\A(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?\z/', $encoded) !== 1) return null;
  $font = base64_decode($encoded, true);
  if (!is_string($font) || strlen($font) !== $bytes ||
      !hash_equals($row['sha256'], hash('sha256', $font))) return null;
  try {
      $validated = appearance_theme_validate_woff2($font);
  } catch (Throwable $e) {
      return null;
  }
  $stream_length = $row['stream_length'] ?? null;
  if (!(is_int($stream_length) || (is_string($stream_length) && ctype_digit($stream_length))) ||
      (int)$stream_length !== $validated['stream_length']) return null;
  $display_name = appearance_theme_font_label($row['display_name'] . '.woff2');
  return ['name' => $display_name, 'bytes' => $bytes, 'sha256' => $row['sha256'], 'data' => $font];
}

function appearance_theme_font_client_state(int $user_id): ?array
{
  $font = appearance_theme_font_record($user_id);
  if (!$font) return null;
  return [
      'name' => $font['name'],
      'bytes' => $font['bytes'],
      'hash' => $font['sha256'],
      'url' => route_url('appearance_theme_font', ['v' => $font['sha256']]),
  ];
}

function appearance_theme_font_upload(array $plugin): void
{
  need_login();
  require_post();
  try {
      $file = $_FILES['appearance_theme_font'] ?? null;
      if (!is_array($file)) appearance_theme_invalid('请选择 WOFF2 字体文件。');
      $error = $file['error'] ?? UPLOAD_ERR_NO_FILE;
      if (!is_int($error)) $error = (int)$error;
      if ($error !== UPLOAD_ERR_OK) {
          $message = in_array($error, [UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE], true)
              ? '字体文件不能超过 2 MiB。'
              : '字体文件上传失败。';
          appearance_theme_invalid($message);
      }
      $tmp = $file['tmp_name'] ?? '';
      $name = $file['name'] ?? '';
      $declared_size = $file['size'] ?? 0;
      if (!is_string($tmp) || !is_string($name) ||
          !(is_int($declared_size) || (is_string($declared_size) && ctype_digit($declared_size))) ||
          !preg_match('/\.woff2$/i', $name) || !is_uploaded_file($tmp)) {
          appearance_theme_invalid('仅支持直接上传 WOFF2 字体文件。');
      }
      if ((int)$declared_size < 48 || (int)$declared_size > APPEARANCE_THEME_MAX_FONT_BYTES) {
          appearance_theme_invalid('字体文件不能超过 2 MiB。');
      }
      $font = file_get_contents($tmp, false, null, 0, APPEARANCE_THEME_MAX_FONT_BYTES + 1);
      if (!is_string($font) || strlen($font) !== (int)$declared_size) {
          appearance_theme_invalid('字体文件读取失败。');
      }
      if (class_exists('finfo')) {
          $finfo = new finfo(FILEINFO_MIME_TYPE);
          $mime = strtolower((string)$finfo->buffer(substr($font, 0, 4096)));
          if (in_array($mime, ['text/html','text/plain','application/zip','application/x-php','image/svg+xml'], true)) {
              appearance_theme_invalid('上传内容不是 WOFF2 字体。');
          }
      }
      $validated = appearance_theme_validate_woff2($font, true);
      $user_id = uid();
      $now = time();
      tx(function () use ($user_id, $name, $font, $validated, $now): void {
          $old = one('SELECT created_at FROM plugin_appearance_theme_fonts WHERE user_id=?', [$user_id]);
          app_db_upsert('plugin_appearance_theme_fonts', [
              'user_id' => $user_id,
              'display_name' => appearance_theme_font_label($name),
              'sha256' => $validated['sha256'],
              'byte_length' => $validated['bytes'],
              'stream_length' => $validated['stream_length'],
              'data_base64' => base64_encode($font),
              'created_at' => max(1, (int)($old['created_at'] ?? $now)),
              'updated_at' => $now,
          ], ['user_id']);
      });
      json_response(['ok' => 1, 'font' => appearance_theme_font_client_state($user_id)]);
  } catch (Throwable $e) {
      err($e instanceof InvalidArgumentException ? $e->getMessage() : '字体上传失败。', 422);
  }
}

function appearance_theme_lock_font(int $user_id): bool
{
  $sql = 'SELECT user_id FROM plugin_appearance_theme_fonts WHERE user_id=?';
  if (db_driver() !== 'sqlite') $sql .= ' FOR UPDATE';
  return one($sql, [$user_id]) !== null;
}

function appearance_theme_delete_font_data(int $user_id): void
{
  tx(function () use ($user_id): void {
      appearance_theme_lock_font($user_id);
      q('UPDATE plugin_appearance_theme_users SET light_custom_font=0,dark_custom_font=0 WHERE user_id=?', [$user_id]);
      q('UPDATE plugin_appearance_theme_schemes SET light_custom_font=0,dark_custom_font=0 WHERE user_id=?', [$user_id]);
      q('DELETE FROM plugin_appearance_theme_fonts WHERE user_id=?', [$user_id]);
  });
}

function appearance_theme_font_delete(array $plugin): void
{
  need_login();
  require_post();
  try {
      appearance_theme_delete_font_data(uid());
      json_response(['ok' => 1]);
  } catch (Throwable $e) {
      err('移除字体失败。', 422);
  }
}

function appearance_theme_font(array $plugin): void
{
  if (uid() < 1) err('你访问的页面不存在', 404);
  $font = appearance_theme_font_record(uid());
  $version = $_GET['v'] ?? '';
  if (!$font || !is_string($version) || !hash_equals($font['sha256'], $version)) {
      err('你访问的页面不存在', 404);
  }
  $etag = '"' . $font['sha256'] . '"';
  header('Content-Type: font/woff2');
  header('X-Content-Type-Options: nosniff');
  header('Cross-Origin-Resource-Policy: same-origin');
  header('Cache-Control: private, max-age=31536000, immutable');
  header('Vary: Cookie');
  header('ETag: ' . $etag);
  $if_none_match = (string)($_SERVER['HTTP_IF_NONE_MATCH'] ?? '');
  foreach (preg_split('/\s*,\s*/', $if_none_match, -1, PREG_SPLIT_NO_EMPTY) ?: [] as $candidate) {
      if (preg_replace('/^W\//', '', trim($candidate)) === $etag) {
          http_response_code(304);
          exit;
      }
  }
  header('Content-Length: ' . $font['bytes']);
  echo $font['data'];
  exit;
}

function appearance_theme_has_user_cascade(string $table): bool
{
  $driver = db_driver();
  if ($driver === 'sqlite') {
      $rows = q('PRAGMA foreign_key_list(' . app_db_identifier($driver, $table) . ')')->fetchAll();
      if (!$rows) return false;
      $row = $rows[0]; $parts = count($rows); $child = $row['from'] ?? ''; $parent = $row['table'] ?? '';
      $referenced = $row['to'] ?? ''; $action = strtoupper((string)($row['on_delete'] ?? ''));
  } else {
      if ($driver === 'mysql') {
          $sql = "SELECT COUNT(*) parts,MIN(k.column_name) child,MIN(k.referenced_table_name) parent,MIN(k.referenced_column_name) referenced,MIN(r.delete_rule) action FROM information_schema.key_column_usage k JOIN information_schema.referential_constraints r ON r.constraint_schema=k.constraint_schema AND r.constraint_name=k.constraint_name AND r.table_name=k.table_name WHERE k.table_schema=DATABASE() AND k.table_name=? AND k.referenced_table_name IS NOT NULL GROUP BY k.constraint_name";
      } else {
          $sql = "SELECT cardinality(c.conkey) parts,(SELECT attname FROM pg_attribute WHERE attrelid=c.conrelid AND attnum=c.conkey[1]) child,p.relname parent,(SELECT attname FROM pg_attribute WHERE attrelid=c.confrelid AND attnum=c.confkey[1]) referenced,c.confdeltype action FROM pg_constraint c JOIN pg_class t ON t.oid=c.conrelid JOIN pg_namespace n ON n.oid=t.relnamespace JOIN pg_class p ON p.oid=c.confrelid JOIN pg_namespace pn ON pn.oid=p.relnamespace WHERE c.contype='f' AND n.nspname=current_schema() AND t.relname=? AND pn.nspname=current_schema()";
      }
      $rows = q($sql, [$table])->fetchAll();
      if (!$rows) return false;
      $row = $rows[0]; $parts = (int)$row['parts']; $child = $row['child']; $parent = $row['parent'];
      $referenced = $row['referenced']; $action = strtoupper((string)$row['action']);
  }
  if (count($rows) !== 1 || $parts !== 1 || $child !== 'user_id' || $parent !== 'app_users' || $referenced !== 'id' || $action !== ($driver === 'pgsql' ? 'C' : 'CASCADE')) throw new RuntimeException('外观主题数据库外键无法安全升级。');
  return true;
}

function appearance_theme_migrate_user_cascades(array $defs): void
{
  $driver = db_driver();
  $migrate = function () use ($driver, $defs): void {
      foreach ($defs as $table => [$definition, $columns, $constraint]) {
          if (appearance_theme_has_user_cascade($table)) continue;
          $quoted = app_db_identifier($driver, $table);
          if ($driver === 'mysql') q("DELETE c FROM $quoted c LEFT JOIN app_users u ON u.id=c.user_id WHERE u.id IS NULL");
          else q("DELETE FROM $quoted WHERE NOT EXISTS(SELECT 1 FROM app_users u WHERE u.id=$quoted.user_id)");
          if ($driver === 'sqlite') {
              $actual = array_keys(app_db_columns(db(), $driver, $table));
              if (array_diff($actual, $columns) || array_diff($columns, $actual)) throw new RuntimeException('外观主题数据库结构无法安全升级。');
              $temporary = $table . '__fk';
              app_db_drop_table($temporary);
              app_db_create_table($temporary, $definition);
              $list = implode(',', array_map(fn($column) => app_db_identifier($driver, $column), $columns));
              $temp = app_db_identifier($driver, $temporary);
              q("INSERT INTO $temp($list) SELECT $list FROM $quoted");
              app_db_drop_table($table);
              q("ALTER TABLE $temp RENAME TO $quoted");
          } else {
              q('ALTER TABLE ' . $quoted . ' ADD CONSTRAINT ' . app_db_identifier($driver, $constraint) . ' FOREIGN KEY(user_id) REFERENCES app_users(id) ON DELETE CASCADE');
          }
      }
  };
  if ($driver === 'mysql') $migrate(); else tx($migrate);
}

function appearance_theme_install(array $plugin): void
{
  $t = app_db_types(); $u = $t['uint']; $text = $t['text'];
  $defs = [
      'plugin_appearance_theme_users' => [[
          'user_id' => "$u PRIMARY KEY", 'mode' => 'VARCHAR(16) NOT NULL', 'selected_scheme_id' => 'VARCHAR(64) NULL',
          'light_json' => "$text NOT NULL", 'dark_json' => "$text NOT NULL", 'light_custom_font' => "$u NOT NULL DEFAULT 0",
          'dark_custom_font' => "$u NOT NULL DEFAULT 0", 'schema_version' => "$u NOT NULL DEFAULT 1",
          'created_at' => "$u NOT NULL DEFAULT 0", 'updated_at' => "$u NOT NULL",
      ], [
          'mode' => "VARCHAR(16) NOT NULL DEFAULT 'dark'", 'selected_scheme_id' => 'VARCHAR(64) NULL',
          'light_json' => "$text NULL", 'dark_json' => "$text NULL", 'light_custom_font' => "$u NOT NULL DEFAULT 0",
          'dark_custom_font' => "$u NOT NULL DEFAULT 0", 'schema_version' => "$u NOT NULL DEFAULT 1",
          'created_at' => "$u NOT NULL DEFAULT 0", 'updated_at' => "$u NOT NULL DEFAULT 0",
      ], '', 'fk_appearance_theme_users_user'],
      'plugin_appearance_theme_schemes' => [[
          'user_id' => "$u NOT NULL", 'scheme_id' => 'VARCHAR(64) NOT NULL', 'name' => 'VARCHAR(80) NOT NULL',
          'light_json' => "$text NOT NULL", 'dark_json' => "$text NOT NULL", 'light_custom_font' => "$u NOT NULL DEFAULT 0",
          'dark_custom_font' => "$u NOT NULL DEFAULT 0", 'schema_version' => "$u NOT NULL DEFAULT 1",
          'created_at' => "$u NOT NULL", 'updated_at' => "$u NOT NULL",
      ], [
          'user_id' => "$u NULL", 'scheme_id' => 'VARCHAR(64) NULL', 'name' => 'VARCHAR(80) NULL',
          'light_json' => "$text NULL", 'dark_json' => "$text NULL", 'light_custom_font' => "$u NOT NULL DEFAULT 0",
          'dark_custom_font' => "$u NOT NULL DEFAULT 0", 'schema_version' => "$u NOT NULL DEFAULT 1",
          'created_at' => "$u NOT NULL DEFAULT 0", 'updated_at' => "$u NOT NULL DEFAULT 0",
      ], 'PRIMARY KEY(user_id,scheme_id)', 'fk_appearance_theme_schemes_user'],
      'plugin_appearance_theme_fonts' => [[
          'user_id' => "$u PRIMARY KEY", 'display_name' => 'VARCHAR(255) NOT NULL', 'sha256' => 'VARCHAR(64) NOT NULL',
          'byte_length' => "$u NOT NULL", 'stream_length' => "$u NOT NULL", 'data_base64' => "$text NOT NULL",
          'created_at' => "$u NOT NULL", 'updated_at' => "$u NOT NULL",
      ], [
          'display_name' => "VARCHAR(255) NOT NULL DEFAULT '自定义字体'", 'sha256' => "VARCHAR(64) NOT NULL DEFAULT ''",
          'byte_length' => "$u NOT NULL DEFAULT 0", 'stream_length' => "$u NOT NULL DEFAULT 0", 'data_base64' => "$text NULL",
          'created_at' => "$u NOT NULL DEFAULT 0", 'updated_at' => "$u NOT NULL DEFAULT 0",
      ], '', 'fk_appearance_theme_fonts_user'],
  ];
  foreach ($defs as $table => $def) {
      $parts = [];
      foreach ($def[0] as $name => $type) $parts[] = "$name $type";
      $definition = implode(',', $parts) . ($def[2] === '' ? '' : ',' . $def[2]) . ',CONSTRAINT ' . $def[3] . ' FOREIGN KEY(user_id) REFERENCES app_users(id) ON DELETE CASCADE';
      app_db_create_table($table, $definition);
      app_db_ensure_columns($table, $def[1]);
      $defs[$table] = [$definition, array_keys($def[0]), $def[3]];
  }
  appearance_theme_migrate_user_cascades($defs);
  app_db_drop_index('idx_appearance_theme_schemes_user_updated', 'plugin_appearance_theme_schemes');
  app_db_create_index('idx_appearance_theme_schemes_user_created_scheme', 'plugin_appearance_theme_schemes(user_id,created_at,scheme_id)');
}

function appearance_theme_uninstall(array $plugin): void
{
  foreach (['fonts','schemes','users'] as $table) app_db_drop_table('plugin_appearance_theme_' . $table);
}

function appearance_theme_menu_links($links, array $ctx): array
{
  $links = is_array($links) ? $links : [];
  if (empty($ctx['self'])) return $links;

  $links[] = [
      'text' => '外观主题',
      'url' => route_url('appearance_theme'),
      'icon' => 'settings',
  ];
  return $links;
}

function appearance_theme_v1_default_config(string $mode): array
{
  $dark = $mode === 'dark';
  return [
      'brand' => $dark ? '#5b7fb5' : '#2ecc71',
      'fontUi' => '系统字体',
      'fontSize' => 14,
      'density' => '舒适',
      'bg' => $dark ? '#161a20' : '#f5f5f5',
      'panel' => $dark ? '#1d222a' : '#ffffff',
      'text' => $dark ? '#e6eaef' : '#222222',
      'textMuted' => $dark ? '#9aa4b0' : '#666666',
      'line' => $dark ? '#2b323c' : '#e8e8e8',
      'postHover' => $dark ? '#232a34' : '#f0f3f6',
      'fontMono' => '系统等宽',
      'fontSizeSm' => 13,
      'lineHeight' => $dark ? 175 : 170,
      'codeFontSize' => 12,
      'radius' => 6,
      'radiusSm' => 4,
      'shadow' => $dark ? 52 : 36,
      'sideTranslucent' => $dark,
      'codeBg' => $dark ? '#0b1017' : '#0f172a',
      'codeText' => $dark ? '#e8eef6' : '#ffffff',
      'codeBorder' => $dark ? '#1b2430' : '#1f2937',
      'inlineCodeBg' => $dark ? '#252c37' : '#eef1f4',
      'success' => $dark ? '#3fa06a' : '#27ae60',
      'danger' => $dark ? '#d05c5c' : '#d94b4b',
      'warning' => $dark ? '#c9782c' : '#c2410c',
      'pinnedBadge' => $dark ? '#d08a3c' : '#d58932',
      'transition' => '标准',
      'backdrop' => $dark ? 58 : 42,
  ];
}

function appearance_theme_v1_config_rules(): array
{
  static $rules;
  if ($rules !== null) return $rules;

  $rules = [
      'brand' => ['color'],
      'fontUi' => ['enum', ['系统字体', 'LXGW WenKai', 'Noto Sans SC', 'Inter']],
      'fontSize' => ['int', 12, 20],
      'density' => ['enum', ['紧凑', '舒适', '宽松']],
      'bg' => ['color'],
      'panel' => ['color'],
      'text' => ['color'],
      'textMuted' => ['color'],
      'line' => ['color'],
      'postHover' => ['color'],
      'fontMono' => ['enum', ['系统等宽', 'Fira Code', 'Sarasa Mono']],
      'fontSizeSm' => ['int', 10, 16],
      'lineHeight' => ['int', 130, 210],
      'codeFontSize' => ['int', 10, 18],
      'radius' => ['int', 0, 20],
      'radiusSm' => ['int', 0, 14],
      'shadow' => ['int', 0, 100],
      'sideTranslucent' => ['bool'],
      'codeBg' => ['color'],
      'codeText' => ['color'],
      'codeBorder' => ['color'],
      'inlineCodeBg' => ['color'],
      'success' => ['color'],
      'danger' => ['color'],
      'warning' => ['color'],
      'pinnedBadge' => ['color'],
      'transition' => ['enum', ['关闭', '快速', '标准', '平缓']],
      'backdrop' => ['int', 0, 90],
  ];
  return $rules;
}

function appearance_theme_default_config(string $mode): array
{
  $dark = $mode === 'dark';
  return [
      'brand' => $dark ? '#5b7fb5' : '#2ecc71',
      'fontUi' => '系统字体',
      'fontSize' => 14,
      'density' => '舒适',
      'bg' => $dark ? '#161a20' : '#f5f5f5',
      'panel' => $dark ? '#1d222a' : '#ffffff',
      'text' => $dark ? '#e6eaef' : '#222222',
      'textMuted' => $dark ? '#9aa4b0' : '#666666',
      'textSubtle' => $dark ? '#697078' : '#111827',
      'line' => $dark ? '#2b323c' : '#e8e8e8',
      'inputBg' => $dark ? '#191e25' : '#f8f8f8',
      'postHover' => $dark ? '#232a34' : '#f0f3f6',
      'divider' => $dark ? '#2d323a' : '#ededed',
      'pinned' => $dark ? '#d08a3c' : '#d58932',
      'danger' => $dark ? '#d05c5c' : '#d94b4b',
      'bodyLink' => $dark ? '#7996c2' : '#111827',
      'lineHeight' => $dark ? 175 : 170,
      'quoteBorder' => $dark ? '#41587b' : '#86e1ad',
      'inlineCodeBg' => $dark ? '#252c37' : '#eef1f4',
      'codeBg' => $dark ? '#0b1017' : '#0f172a',
      'codeText' => $dark ? '#e8eef6' : '#ffffff',
      'fontMono' => '系统等宽',
      'fontSizeSm' => 13,
      'codeFontSize' => 12,
      'radius' => 6,
      'radiusSm' => 4,
      'shadow' => $dark ? 52 : 36,
  ];
}

function appearance_theme_config_rules(): array
{
  static $rules;
  if ($rules !== null) return $rules;

  $rules = [
      'brand' => ['color'],
      'fontUi' => ['enum', ['系统字体', 'LXGW WenKai', 'Noto Sans SC', 'Inter', 'Poppins']],
      'fontSize' => ['int', 12, 20],
      'density' => ['enum', ['紧凑', '舒适', '宽松']],
      'bg' => ['color'],
      'panel' => ['color'],
      'text' => ['color'],
      'textMuted' => ['color'],
      'textSubtle' => ['color'],
      'line' => ['color'],
      'inputBg' => ['color'],
      'postHover' => ['color'],
      'divider' => ['color'],
      'pinned' => ['color'],
      'danger' => ['color'],
      'bodyLink' => ['color'],
      'lineHeight' => ['int', 130, 210],
      'quoteBorder' => ['color'],
      'inlineCodeBg' => ['color'],
      'codeBg' => ['color'],
      'codeText' => ['color'],
      'fontMono' => ['enum', ['系统等宽', 'Fira Code', 'Sarasa Mono']],
      'fontSizeSm' => ['int', 10, 16],
      'codeFontSize' => ['int', 10, 18],
      'radius' => ['int', 0, 20],
      'radiusSm' => ['int', 0, 14],
      'shadow' => ['int', 0, 100],
  ];
  return $rules;
}

function appearance_theme_text_length(string $value): int
{
  if (function_exists('mb_strlen')) return mb_strlen($value, 'UTF-8');
  $count = preg_match_all('/./us', $value, $matches);
  return $count === false ? strlen($value) : $count;
}

function appearance_theme_normalize_color(mixed $value, string $fallback, bool $strict): string
{
  if ($strict && !is_string($value)) appearance_theme_invalid('颜色值无效。');
  $value = is_string($value) ? strtolower(trim($value)) : '';
  if (preg_match('/^#[0-9a-f]{3}$/', $value)) {
      return '#' . $value[1] . $value[1] . $value[2] . $value[2] . $value[3] . $value[3];
  }
  if (preg_match('/^#[0-9a-f]{6}$/', $value)) return $value;
  if ($strict) appearance_theme_invalid('颜色值无效。');
  return $fallback;
}

function appearance_theme_normalize_config_with_rules(
  mixed $raw,
  string $mode,
  bool $strict,
  array $rules,
  array $defaults
): array {
  if (!is_array($raw) || array_is_list($raw)) {
      if ($strict) appearance_theme_invalid('配色配置无效。');
      return $defaults;
  }

  if ($strict) {
      foreach ($raw as $key => $_value) {
          if (!is_string($key) || !array_key_exists($key, $rules)) {
              appearance_theme_invalid('配色包含不支持的字段。');
          }
      }
  }

  $normalized = [];
  foreach ($rules as $key => $rule) {
      if (!array_key_exists($key, $raw)) {
          if ($strict) appearance_theme_invalid('配色字段不完整。');
          $normalized[$key] = $defaults[$key];
          continue;
      }

      $value = $raw[$key];
      if ($rule[0] === 'color') {
          $normalized[$key] = appearance_theme_normalize_color($value, $defaults[$key], $strict);
          continue;
      }
      if ($rule[0] === 'enum') {
          if ($strict && !is_string($value)) appearance_theme_invalid('配色选项无效。');
          $candidate = is_string($value) ? trim($value) : '';
          if (!in_array($candidate, $rule[1], true)) {
              if ($strict) appearance_theme_invalid('配色选项无效。');
              $candidate = $defaults[$key];
          }
          $normalized[$key] = $candidate;
          continue;
      }
      if ($rule[0] === 'int') {
          if ($strict && !is_int($value)) appearance_theme_invalid('配色数值无效。');
          $candidate = (int)$value;
          if ($strict && ($candidate < $rule[1] || $candidate > $rule[2])) {
              appearance_theme_invalid('配色数值超出范围。');
          }
          $normalized[$key] = min($rule[2], max($rule[1], $candidate));
          continue;
      }
      if ($rule[0] === 'bool') {
          if ($strict && !is_bool($value)) appearance_theme_invalid('配色开关无效。');
          $normalized[$key] = is_bool($value) ? $value : (int)$value === 1;
      }
  }
  return $normalized;
}

function appearance_theme_normalize_v1_config(mixed $raw, string $mode, bool $strict = false): array
{
  return appearance_theme_normalize_config_with_rules($raw, $mode, $strict, appearance_theme_v1_config_rules(), appearance_theme_v1_default_config($mode));
}

function appearance_theme_normalize_config(mixed $raw, string $mode, bool $strict = false): array
{
  return appearance_theme_normalize_config_with_rules($raw, $mode, $strict, appearance_theme_config_rules(), appearance_theme_default_config($mode));
}

function appearance_theme_convert_v1_config(mixed $raw, string $mode): array
{
  $old = appearance_theme_normalize_v1_config($raw, $mode, true);
  $dark = appearance_theme_luminance($old['panel']) < 0.4;
  $away = appearance_theme_luminance($old['bg']) < 0.4 ? '#000000' : '#ffffff';
  $text = appearance_theme_readable_color($old['text'], $old['panel']);
  $muted = appearance_theme_readable_color($old['textMuted'], $old['panel']);
  $new = array_intersect_key($old, appearance_theme_config_rules());
  $new += [
      'textSubtle' => appearance_theme_readable_color(appearance_theme_mix($muted, $away, 0.32), $old['panel']),
      'inputBg' => appearance_theme_mix($old['panel'], $old['bg'], $dark ? 0.55 : 0.75),
      'divider' => appearance_theme_mix($old['panel'], $text, 0.08),
      'pinned' => $old['pinnedBadge'],
      'bodyLink' => appearance_theme_readable_color(appearance_theme_mix($old['brand'], $dark ? '#ffffff' : '#000000', $dark ? 0.18 : 0.10), $old['panel']),
      'quoteBorder' => appearance_theme_mix($old['brand'], $old['panel'], 0.42),
  ];
  return appearance_theme_normalize_config($new, $mode, true);
}

function appearance_theme_json_encode(array $value): string
{
  return json_encode(
      $value,
      JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR
  );
}

function appearance_theme_decode_stored_config(string $json, string $mode, int $schema_version = APPEARANCE_THEME_SCHEMA_VERSION): ?array
{
  try {
      $raw = json_decode($json, true, 64, JSON_THROW_ON_ERROR);
      if (!is_array($raw) || array_is_list($raw)) return null;
      return match ($schema_version) {
          1 => appearance_theme_convert_v1_config($raw, $mode),
          APPEARANCE_THEME_SCHEMA_VERSION => appearance_theme_normalize_config($raw, $mode, true),
          default => null,
      };
  } catch (Throwable $e) {
      return null;
  }
}

function appearance_theme_default_state(): array
{
  return [
      'persisted' => false,
      'needs_migration' => false,
      'mode' => 'dark',
      'selected_scheme_id' => 'default',
      'light' => appearance_theme_default_config('light'),
      'dark' => appearance_theme_default_config('dark'),
      'light_custom_font' => false,
      'dark_custom_font' => false,
  ];
}

function appearance_theme_user_state(int $user_id): array
{
  static $cache = [];
  if (isset($cache[$user_id])) return $cache[$user_id];

  $state = appearance_theme_default_state();
  if ($user_id < 1) return $cache[$user_id] = $state;

  $row = one(
      'SELECT mode,selected_scheme_id,light_json,dark_json,light_custom_font,dark_custom_font,schema_version FROM plugin_appearance_theme_users WHERE user_id=?',
      [$user_id]
  );
  if (!$row) return $cache[$user_id] = $state;

  $stored_schema = $row['schema_version'] ?? null;
  if (!(is_int($stored_schema) || (is_string($stored_schema) && ctype_digit($stored_schema))) ||
      !in_array((int)$stored_schema, [1, APPEARANCE_THEME_SCHEMA_VERSION], true) ||
      !is_string($row['mode'] ?? null) ||
      !in_array($row['mode'], ['light', 'dark'], true) ||
      (!is_null($row['selected_scheme_id'] ?? null) && !is_string($row['selected_scheme_id']))) {
      return $cache[$user_id] = $state;
  }

  $schema_version = (int)$stored_schema;
  $light = appearance_theme_decode_stored_config((string)($row['light_json'] ?? ''), 'light', $schema_version);
  $dark = appearance_theme_decode_stored_config((string)($row['dark_json'] ?? ''), 'dark', $schema_version);
  if (!$light || !$dark) return $cache[$user_id] = $state;

  $mode = $row['mode'];
  $selected = is_string($row['selected_scheme_id']) ? trim($row['selected_scheme_id']) : '';
  if (strlen($selected) > 64) return $cache[$user_id] = $state;
  $light_custom_font = $schema_version === APPEARANCE_THEME_SCHEMA_VERSION && (int)($row['light_custom_font'] ?? 0) === 1;
  $dark_custom_font = $schema_version === APPEARANCE_THEME_SCHEMA_VERSION && (int)($row['dark_custom_font'] ?? 0) === 1;
  if (($light_custom_font || $dark_custom_font) && !appearance_theme_font_record($user_id)) {
      $light_custom_font = false;
      $dark_custom_font = false;
  }

  return $cache[$user_id] = [
      'persisted' => true,
      'needs_migration' => $schema_version !== APPEARANCE_THEME_SCHEMA_VERSION,
      'mode' => $mode,
      'selected_scheme_id' => $selected,
      'light' => $light,
      'dark' => $dark,
      'light_custom_font' => $light_custom_font,
      'dark_custom_font' => $dark_custom_font,
  ];
}

function appearance_theme_personal_schemes(int $user_id, ?bool &$needs_migration = null): array
{
  $needs_migration = false;
  if ($user_id < 1) return [];
  $rows = q(
      'SELECT scheme_id,name,light_json,dark_json,light_custom_font,dark_custom_font,schema_version,created_at,updated_at FROM plugin_appearance_theme_schemes WHERE user_id=? ORDER BY created_at,scheme_id',
      [$user_id]
  )->fetchAll();

  $schemes = [];
  $font_available = appearance_theme_font_record($user_id) !== null;
  foreach ($rows as $row) {
      $stored_schema = $row['schema_version'] ?? null;
      if (!(is_int($stored_schema) || (is_string($stored_schema) && ctype_digit($stored_schema))) ||
          !in_array((int)$stored_schema, [1, APPEARANCE_THEME_SCHEMA_VERSION], true) ||
          !is_string($row['scheme_id'] ?? null) ||
          !is_string($row['name'] ?? null)) {
          continue;
      }
      $schema_version = (int)$stored_schema;
      $id = trim($row['scheme_id']);
      $name = trim($row['name']);
      if (!preg_match('/^mine_[a-f0-9]{24}$/', $id)) continue;
      if ($name === '' || appearance_theme_text_length($name) > 20 || preg_match('/[\x00-\x1F\x7F]/u', $name)) continue;
      $light = appearance_theme_decode_stored_config((string)($row['light_json'] ?? ''), 'light', $schema_version);
      $dark = appearance_theme_decode_stored_config((string)($row['dark_json'] ?? ''), 'dark', $schema_version);
      if (!$light || !$dark) continue;
      if ($schema_version !== APPEARANCE_THEME_SCHEMA_VERSION) $needs_migration = true;
      $schemes[] = [
          'id' => $id,
          'name' => $name,
          'light' => $light,
          'dark' => $dark,
          'light_custom_font' => $schema_version === APPEARANCE_THEME_SCHEMA_VERSION && $font_available && (int)($row['light_custom_font'] ?? 0) === 1,
          'dark_custom_font' => $schema_version === APPEARANCE_THEME_SCHEMA_VERSION && $font_available && (int)($row['dark_custom_font'] ?? 0) === 1,
      ];
      if (count($schemes) >= APPEARANCE_THEME_MAX_SCHEMES) break;
  }
  return $schemes;
}

function appearance_theme_builtin_scheme_ids(): array
{
  return ['default', 'slate', 'paper', 'blackout', 'typora_docsify', 'mist_blue'];
}

function appearance_theme_validate_custom_font_flag(mixed $value, bool $font_available): bool
{
  if (!is_bool($value)) appearance_theme_invalid('自定义字体引用无效。');
  if ($value && !$font_available) appearance_theme_invalid('账号尚未上传可用字体。');
  return $value;
}

function appearance_theme_validate_payload(string $json, bool $font_available = false): array
{
  if ($json === '' || strlen($json) > APPEARANCE_THEME_MAX_PAYLOAD_BYTES) {
      appearance_theme_invalid('配色数据无效。');
  }

  $raw = json_decode($json, true, 64, JSON_THROW_ON_ERROR);
  if (!is_array($raw) || array_is_list($raw)) {
      appearance_theme_invalid('配色数据无效。');
  }

  $allowed = ['schema_version', 'mode', 'selected_scheme_id', 'light', 'dark', 'light_custom_font', 'dark_custom_font', 'schemes'];
  foreach ($raw as $key => $_value) {
      if (!is_string($key) || !in_array($key, $allowed, true)) {
          appearance_theme_invalid('配色数据包含不支持的字段。');
      }
  }
  foreach ($allowed as $key) {
      if (!array_key_exists($key, $raw)) appearance_theme_invalid('配色数据不完整。');
  }
  if (!is_int($raw['schema_version']) || $raw['schema_version'] !== APPEARANCE_THEME_SCHEMA_VERSION) {
      appearance_theme_invalid('配色版本不受支持。');
  }

  if (!is_string($raw['mode']) || !in_array($raw['mode'], ['light', 'dark'], true)) {
      appearance_theme_invalid('明暗模式无效。');
  }
  $mode = $raw['mode'];
  if (!is_string($raw['selected_scheme_id'])) appearance_theme_invalid('配色标识无效。');
  $selected = trim($raw['selected_scheme_id']);
  if (strlen($selected) > 64) appearance_theme_invalid('配色标识无效。');

  $light = appearance_theme_normalize_config($raw['light'], 'light', true);
  $dark = appearance_theme_normalize_config($raw['dark'], 'dark', true);
  $light_custom_font = appearance_theme_validate_custom_font_flag($raw['light_custom_font'], $font_available);
  $dark_custom_font = appearance_theme_validate_custom_font_flag($raw['dark_custom_font'], $font_available);

  if (!is_array($raw['schemes']) || !array_is_list($raw['schemes'])) {
      appearance_theme_invalid('我的配色无效。');
  }
  if (count($raw['schemes']) > APPEARANCE_THEME_MAX_SCHEMES) {
      appearance_theme_invalid('我的配色数量已达上限。');
  }

  $schemes = [];
  $scheme_ids = [];
  foreach ($raw['schemes'] as $scheme) {
      if (!is_array($scheme) || array_is_list($scheme)) {
          appearance_theme_invalid('我的配色无效。');
      }
      $scheme_allowed = ['id', 'name', 'light', 'dark', 'light_custom_font', 'dark_custom_font'];
      foreach ($scheme as $key => $_value) {
          if (!is_string($key) || !in_array($key, $scheme_allowed, true)) {
              appearance_theme_invalid('我的配色包含不支持的字段。');
          }
      }
      foreach ($scheme_allowed as $key) {
          if (!array_key_exists($key, $scheme)) appearance_theme_invalid('我的配色不完整。');
      }

      if (!is_string($scheme['id']) || !is_string($scheme['name'])) {
          appearance_theme_invalid('配色名称或标识无效。');
      }
      $id = trim($scheme['id']);
      $name = trim($scheme['name']);
      if (!preg_match('/^mine_[a-f0-9]{24}$/', $id) || isset($scheme_ids[$id])) {
          appearance_theme_invalid('我的配色标识无效。');
      }
      if ($name === '' || appearance_theme_text_length($name) > 20 || preg_match('/[\x00-\x1F\x7F]/u', $name)) {
          appearance_theme_invalid('配色名称无效。');
      }
      $scheme_ids[$id] = true;
      $schemes[] = [
          'id' => $id,
          'name' => $name,
          'light' => appearance_theme_normalize_config($scheme['light'], 'light', true),
          'dark' => appearance_theme_normalize_config($scheme['dark'], 'dark', true),
          'light_custom_font' => appearance_theme_validate_custom_font_flag($scheme['light_custom_font'], $font_available),
          'dark_custom_font' => appearance_theme_validate_custom_font_flag($scheme['dark_custom_font'], $font_available),
      ];
  }

  $valid_selected = $selected === '' ||
      in_array($selected, appearance_theme_builtin_scheme_ids(), true) ||
      isset($scheme_ids[$selected]);
  if (!$valid_selected) appearance_theme_invalid('当前配色不存在。');

  return [
      'mode' => $mode,
      'selected_scheme_id' => $selected,
      'light' => $light,
      'dark' => $dark,
      'light_custom_font' => $light_custom_font,
      'dark_custom_font' => $dark_custom_font,
      'schemes' => $schemes,
  ];
}

function appearance_theme_save_state(int $user_id, array $state): void
{
  $now = time();
  tx(function () use ($user_id, $state, $now): void {
      $font_available = appearance_theme_lock_font($user_id);
      if (!$font_available) {
          $state['light_custom_font'] = false;
          $state['dark_custom_font'] = false;
          foreach ($state['schemes'] as &$scheme) {
              $scheme['light_custom_font'] = false;
              $scheme['dark_custom_font'] = false;
          }
          unset($scheme);
      }
      $old = one(
          'SELECT created_at FROM plugin_appearance_theme_users WHERE user_id=?',
          [$user_id]
      );
      app_db_upsert('plugin_appearance_theme_users', [
          'user_id' => $user_id,
          'mode' => $state['mode'],
          'selected_scheme_id' => $state['selected_scheme_id'] !== '' ? $state['selected_scheme_id'] : null,
          'light_json' => appearance_theme_json_encode($state['light']),
          'dark_json' => appearance_theme_json_encode($state['dark']),
          'light_custom_font' => !empty($state['light_custom_font']) ? 1 : 0,
          'dark_custom_font' => !empty($state['dark_custom_font']) ? 1 : 0,
          'schema_version' => APPEARANCE_THEME_SCHEMA_VERSION,
          'created_at' => max(1, (int)($old['created_at'] ?? $now)),
          'updated_at' => $now,
      ], ['user_id']);

      $existing_rows = q(
          'SELECT scheme_id,created_at FROM plugin_appearance_theme_schemes WHERE user_id=?',
          [$user_id]
      )->fetchAll();
      $created_at = [];
      foreach ($existing_rows as $row) {
          $created_at[(string)$row['scheme_id']] = max(1, (int)$row['created_at']);
      }

      q('DELETE FROM plugin_appearance_theme_schemes WHERE user_id=?', [$user_id]);
      foreach ($state['schemes'] as $scheme) {
          app_db_upsert('plugin_appearance_theme_schemes', [
              'user_id' => $user_id,
              'scheme_id' => $scheme['id'],
              'name' => $scheme['name'],
              'light_json' => appearance_theme_json_encode($scheme['light']),
              'dark_json' => appearance_theme_json_encode($scheme['dark']),
              'light_custom_font' => !empty($scheme['light_custom_font']) ? 1 : 0,
              'dark_custom_font' => !empty($scheme['dark_custom_font']) ? 1 : 0,
              'schema_version' => APPEARANCE_THEME_SCHEMA_VERSION,
              'created_at' => $created_at[$scheme['id']] ?? $now,
              'updated_at' => $now,
          ], ['user_id', 'scheme_id']);
      }
  });
}

function appearance_theme_hex_rgb(string $hex): array
{
  $hex = ltrim($hex, '#');
  return [
      hexdec(substr($hex, 0, 2)),
      hexdec(substr($hex, 2, 2)),
      hexdec(substr($hex, 4, 2)),
  ];
}

function appearance_theme_mix(string $from, string $to, float $amount): string
{
  $a = appearance_theme_hex_rgb($from);
  $b = appearance_theme_hex_rgb($to);
  $rgb = [];
  for ($i = 0; $i < 3; $i++) {
      $rgb[] = min(255, max(0, (int)round($a[$i] + ($b[$i] - $a[$i]) * $amount)));
  }
  return sprintf('#%02x%02x%02x', $rgb[0], $rgb[1], $rgb[2]);
}

function appearance_theme_luminance(string $hex): float
{
  $rgb = appearance_theme_hex_rgb($hex);
  $channels = array_map(static function (int $value): float {
      $value /= 255;
      return $value <= 0.03928 ? $value / 12.92 : (($value + 0.055) / 1.055) ** 2.4;
  }, $rgb);
  return 0.2126 * $channels[0] + 0.7152 * $channels[1] + 0.0722 * $channels[2];
}

function appearance_theme_contrast_text(string $background): string
{
  $luminance = appearance_theme_luminance($background);
  $white_contrast = 1.05 / ($luminance + 0.05);
  $dark_contrast = ($luminance + 0.05) / 0.05;
  return $white_contrast >= $dark_contrast ? '#ffffff' : '#111827';
}

function appearance_theme_solid_text(string $background): string
{
  $luminance = appearance_theme_luminance($background);
  $white_contrast = 1.05 / ($luminance + 0.05);
  $black_contrast = ($luminance + 0.05) / 0.05;
  return $white_contrast >= $black_contrast ? '#ffffff' : '#000000';
}

function appearance_theme_readable_color(string $foreground, string $background, float $minimum = 3.0): string
{
  $foreground_luminance = appearance_theme_luminance($foreground);
  $background_luminance = appearance_theme_luminance($background);
  $contrast = (max($foreground_luminance, $background_luminance) + 0.05) / (min($foreground_luminance, $background_luminance) + 0.05);
  return $contrast >= $minimum ? $foreground : appearance_theme_contrast_text($background);
}

function appearance_theme_derive(array $config): array
{
  $dark_panel = appearance_theme_luminance($config['panel']) < 0.4;
  $config['brandHover'] = appearance_theme_mix($config['brand'], $dark_panel ? '#ffffff' : '#000000', 0.16);
  $config['brandText'] = appearance_theme_solid_text($config['brand']);
  $config['brandHoverText'] = appearance_theme_solid_text($config['brandHover']);
  $config['brandSoft'] = appearance_theme_mix($config['panel'], $config['brand'], $dark_panel ? 0.16 : 0.10);
  $config['focusRing'] = appearance_theme_mix($config['panel'], $config['brand'], $dark_panel ? 0.42 : 0.36);
  $config['inputCaret'] = appearance_theme_readable_color($config['brand'], $config['inputBg']);
  $config['text'] = appearance_theme_readable_color($config['text'], $config['panel']);
  $config['textMuted'] = appearance_theme_readable_color($config['textMuted'], $config['panel']);
  $config['textSubtle'] = appearance_theme_readable_color($config['textSubtle'], $config['panel']);
  $config['bodyLink'] = appearance_theme_readable_color($config['bodyLink'], $config['panel']);
  $config['bodyText'] = appearance_theme_mix($config['text'], $config['panel'], 0.06);
  $config['quoteText'] = $config['textMuted'];
  $config['inlineCodeText'] = appearance_theme_readable_color($config['text'], $config['inlineCodeBg']);
  $config['codeText'] = appearance_theme_readable_color($config['codeText'], $config['codeBg']);
  $config['codeBorder'] = appearance_theme_mix($config['codeBg'], $config['line'], $dark_panel ? 0.46 : 0.34);
  $config['pinnedSoft'] = appearance_theme_mix($config['panel'], $config['pinned'], $dark_panel ? 0.20 : 0.13);
  $config['pinnedText'] = appearance_theme_readable_color($config['pinned'], $config['pinnedSoft'], 4.5);
  $config['dangerSoft'] = appearance_theme_mix($config['panel'], $config['danger'], $dark_panel ? 0.18 : 0.12);
  $config['dangerText'] = appearance_theme_readable_color($config['danger'], $config['dangerSoft'], 4.5);
  $config['dangerSolidText'] = appearance_theme_solid_text($config['danger']);
  $config['success'] = $dark_panel ? '#4ba878' : '#2f855a';
  $config['successSoft'] = appearance_theme_mix($config['panel'], $config['success'], $dark_panel ? 0.18 : 0.12);
  return $config;
}

function appearance_theme_font_ui_css(string $font, bool $custom = false): string
{
  $fallback = match ($font) {
      'LXGW WenKai' => '"LXGW WenKai","霞鹜文楷",serif',
      'Noto Sans SC' => '"Noto Sans SC","PingFang SC","Microsoft YaHei",sans-serif',
      'Inter' => 'Inter,system-ui,-apple-system,"Segoe UI",sans-serif',
      'Poppins' => '"AppearanceThemePoppins","Poppins","Source Han Sans HC",' .
          '"PingFang SC","Noto Sans CJK SC","Microsoft YaHei",system-ui,sans-serif',
      default => 'system-ui,-apple-system,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif',
  };
  return $custom ? '"' . APPEARANCE_THEME_CUSTOM_FONT_FAMILY . '",' . $fallback : $fallback;
}

function appearance_theme_font_mono_css(string $font): string
{
  return match ($font) {
      'Fira Code' => '"Fira Code",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace',
      'Sarasa Mono' => '"Sarasa Mono SC","Sarasa Mono",ui-monospace,Menlo,Consolas,monospace',
      default => 'ui-monospace,SFMono-Regular,Menlo,Consolas,monospace',
  };
}

function appearance_theme_user_css(array $config, string $mode, bool $custom_font = false): string
{
  $config = appearance_theme_derive($config);
  $dark_panel = appearance_theme_luminance($config['panel']) < 0.4;
  $line_soft = appearance_theme_mix($config['panel'], $config['line'], 0.55);
  $brand_soft_text = appearance_theme_solid_text($config['brandSoft']);
  $line_soft_text = appearance_theme_solid_text($line_soft);
  $bg_text = appearance_theme_solid_text($config['bg']);
  $text_disabled = appearance_theme_mix($config['textMuted'], $config['bg'], 0.42);
  $inverse = $dark_panel ? '#f8fafc' : '#111827';
  $inverse_text = appearance_theme_contrast_text($inverse);
  $inverse_border = $dark_panel ? '#d5dbe4' : '#253247';
  $backdrop_alpha = $dark_panel ? 0.58 : 0.42;
  $density = match ($config['density']) {
      '紧凑' => '8px',
      '宽松' => '15px',
      default => '11px',
  };
  $info = appearance_theme_mix($config['brand'], $config['text'], $dark_panel ? 0.18 : 0.10);
  $info_soft = appearance_theme_mix($config['panel'], $info, $dark_panel ? 0.18 : 0.10);
  $warning_soft = appearance_theme_mix($config['panel'], $config['pinned'], $dark_panel ? 0.18 : 0.12);
  $dark_rgb = appearance_theme_hex_rgb('#111827');
  $shadow_alpha = $config['shadow'] === 0 ? 0.0 : 0.025 + $config['shadow'] * 0.0014;
  $shadow_medium_alpha = $config['shadow'] === 0 ? 0.0 : 0.05 + $config['shadow'] * 0.0020;

  $css = "html:root{" .
      "color-scheme:{$mode};" .
      "--bg:{$config['bg']};--panel:{$config['panel']};" .
      "--line:{$config['line']};--line-soft:{$line_soft};" .
      "--text:{$config['text']};--text-muted:{$config['textMuted']};" .
      "--text-subtle:{$config['textSubtle']};--text-disabled:{$text_disabled};" .
      "--brand:{$config['brand']};--brand-hover:{$config['brandHover']};" .
      "--appearance-theme-brand-text:{$config['brandText']};" .
      "--appearance-theme-brand-hover-text:{$config['brandHoverText']};" .
      "--appearance-theme-brand-soft-text:{$brand_soft_text};" .
      "--appearance-theme-line-soft-text:{$line_soft_text};" .
      "--appearance-theme-bg-text:{$bg_text};" .
      "--brand-soft:{$config['brandSoft']};--focus-ring:{$config['focusRing']};" .
      "--success:{$config['success']};--success-soft:{$config['successSoft']};" .
      "--danger:{$config['danger']};--danger-soft:{$config['dangerSoft']};" .
      "--appearance-theme-danger-text:{$config['dangerText']};" .
      "--appearance-theme-danger-solid-text:{$config['dangerSolidText']};" .
      "--warning:{$config['pinned']};--warning-soft:{$warning_soft};" .
      "--info:{$info};--info-soft:{$info_soft};" .
      "--inverse:{$inverse};--inverse-border:{$inverse_border};--inverse-text:{$inverse_text};" .
      "--color-dark-rgb:{$dark_rgb[0]},{$dark_rgb[1]},{$dark_rgb[2]};" .
      "--backdrop:rgba(17,24,39," . number_format($backdrop_alpha, 2, '.', '') . ");" .
      "--shadow-base:rgba(15,23,42," . number_format($shadow_alpha, 3, '.', '') . ");" .
      "--shadow-medium:rgba(15,23,42," . number_format($shadow_medium_alpha, 3, '.', '') . ");" .
      "--transition-normal:.18s;" .
      "--font-mono:" . appearance_theme_font_mono_css($config['fontMono']) . ";" .
      "--font-size-md:{$config['fontSize']}px;" .
      "--font-size-lg:" . min(24, $config['fontSize'] + 2) . "px;" .
      "--font-size-xl:" . min(30, $config['fontSize'] + 6) . "px;" .
      "--font-size-sm:{$config['fontSizeSm']}px;" .
      "--font-size-xs:" . max(10, $config['fontSizeSm'] - 1) . "px;" .
      "--radius:{$config['radius']}px;--radius-sm:{$config['radiusSm']}px;" .
      "--appearance-theme-font-ui:" . appearance_theme_font_ui_css($config['fontUi'], $custom_font) . ";" .
      "--appearance-theme-code-font-size:{$config['codeFontSize']}px;" .
      "--appearance-theme-line-height:" . number_format($config['lineHeight'] / 100, 2, '.', '') . ";" .
      "--appearance-theme-body-text:{$config['bodyText']};" .
      "--appearance-theme-body-link:{$config['bodyLink']};" .
      "--appearance-theme-quote-text:{$config['quoteText']};" .
      "--appearance-theme-quote-border:{$config['quoteBorder']};" .
      "--appearance-theme-input-bg:{$config['inputBg']};" .
      "--appearance-theme-input-caret:{$config['inputCaret']};" .
      "--appearance-theme-post-hover:{$config['postHover']};" .
      "--appearance-theme-post-divider:{$config['divider']};" .
      "--appearance-theme-code-bg:{$config['codeBg']};" .
      "--appearance-theme-code-text:{$config['codeText']};" .
      "--appearance-theme-code-border:{$config['codeBorder']};" .
      "--appearance-theme-inline-code-bg:{$config['inlineCodeBg']};" .
      "--appearance-theme-inline-code-text:{$config['inlineCodeText']};" .
      "--appearance-theme-pinned:{$config['pinnedText']};" .
      "--appearance-theme-pinned-soft:{$config['pinnedSoft']};" .
      "--appearance-theme-topic-padding:{$density};" .
      "--appearance-theme-sidebar-bg:{$config['panel']};" .
      "--appearance-theme-sidebar-backdrop:none;" .
      "}";

  return str_ireplace('</style', '<\/style', $css);
}

function appearance_theme_page_head($html, array $ctx): string
{
  try {
      $user_id = uid();
      if ($user_id < 1) return (string)$html;
      $state = appearance_theme_user_state($user_id);
      if (empty($state['persisted'])) return (string)$html;
      $mode = $state['mode'];
      $custom_font = !empty($state[$mode . '_custom_font']);
      $font_face = '';
      if ($custom_font) {
          $font = appearance_theme_font_client_state($user_id);
          if (!$font) $custom_font = false;
          else $font_face = '@font-face{font-family:"' . APPEARANCE_THEME_CUSTOM_FONT_FAMILY .
              '";font-style:normal;font-display:swap;font-weight:400;src:url("' .
              str_ireplace('</style', '<\/style', $font['url']) . '") format("woff2")}';
      }
      return (string)$html . '<style id="appearance-theme-user-style">' .
          $font_face . appearance_theme_user_css($state[$mode], $mode, $custom_font) . '</style>' .
          '<script id="appearance-theme-activate">document.documentElement.classList.add("appearance-theme-active");</script>';
  } catch (Throwable $e) {
      return (string)$html;
  }
}

function appearance_theme_page(array $plugin): void
{
  need_login();
  $user_id = uid();

  if (is_post_request()) {
      require_post();
      try {
          $font_available = appearance_theme_font_record($user_id) !== null;
          $state = appearance_theme_validate_payload((string)($_POST['appearance_theme_payload'] ?? ''), $font_available);
          appearance_theme_save_state($user_id, $state);
      } catch (Throwable $e) {
          err($e instanceof InvalidArgumentException ? $e->getMessage() : '保存配色失败。');
      }
      set_flash('外观主题已保存');
      go(route_url('appearance_theme'));
  }

  $state = appearance_theme_user_state($user_id);
  $scheme_needs_migration = false;
  $schemes = appearance_theme_personal_schemes($user_id, $scheme_needs_migration);
  $needs_migration = !empty($state['needs_migration']) || $scheme_needs_migration;
  $personal_ids = array_fill_keys(array_column($schemes, 'id'), true);
  $selected = (string)$state['selected_scheme_id'];
  if ($selected !== '' &&
      !in_array($selected, appearance_theme_builtin_scheme_ids(), true) &&
      !isset($personal_ids[$selected])) {
      $selected = '';
  }

  $payload = [
      'schema_version' => APPEARANCE_THEME_SCHEMA_VERSION, 'mode' => $state['mode'],
      'selected_scheme_id' => $selected, 'light' => $state['light'], 'dark' => $state['dark'],
      'light_custom_font' => (bool)$state['light_custom_font'], 'dark_custom_font' => (bool)$state['dark_custom_font'],
      'schemes' => $schemes,
  ];
  $client_state = $payload + [
      'persisted' => (bool)$state['persisted'], 'needs_migration' => $needs_migration,
      'font' => appearance_theme_font_client_state($user_id),
      'font_upload_url' => route_url('appearance_theme_font_upload'), 'font_delete_url' => route_url('appearance_theme_font_delete'),
      'max_font_bytes' => APPEARANCE_THEME_MAX_FONT_BYTES, 'max_schemes' => APPEARANCE_THEME_MAX_SCHEMES,
  ];
  $state_json = appearance_theme_json_encode($client_state);
  $action = h(route_url('appearance_theme'));

  $main = '<div class="appearance-theme-root" id="appearance-theme-root" data-appearance-theme-state="' . h($state_json) . '">' .
      '<h1 class="appearance-theme-title">外观主题</h1>' .
      '<form class="appearance-theme-form" id="appearance-theme-form" method="post" action="' . $action . '" data-no-ajax="1">' .
      form_token() .
      '<input type="hidden" name="appearance_theme_payload" id="appearance-theme-payload" value="' . h(appearance_theme_json_encode($payload)) . '">' .
      '<section class="appearance-theme-section appearance-theme-preview-section" aria-label="外观预览">' .
      '<div class="appearance-theme-preview" id="appearance-theme-preview">' .
      '<div class="appearance-theme-preview-nav"><span>LINUX SB</span><span>搜索</span></div>' .
      '<div class="appearance-theme-preview-body" id="appearance-theme-preview-body">' .
      '<div class="appearance-theme-preview-main"><div class="appearance-theme-preview-tabs"><span class="appearance-theme-preview-tab-active">新评论</span><span>新帖子</span></div>' .
      '<div class="appearance-theme-preview-post"><i class="appearance-theme-preview-avatar"></i><div><div class="appearance-theme-preview-row"><i class="appearance-theme-preview-title-line"></i><span class="appearance-theme-preview-badge appearance-theme-preview-pin">置顶</span></div><div class="appearance-theme-preview-meta"><i></i><i></i></div></div></div>' .
      '<div class="appearance-theme-preview-post appearance-theme-preview-hover"><i class="appearance-theme-preview-avatar"></i><div><div class="appearance-theme-preview-row"><i class="appearance-theme-preview-title-line"></i><i class="appearance-theme-preview-badge"></i></div><div class="appearance-theme-preview-meta"><i></i><i></i></div></div></div>' .
      '<div class="appearance-theme-preview-post"><i class="appearance-theme-preview-avatar"></i><div><div class="appearance-theme-preview-row"><i class="appearance-theme-preview-title-line"></i><i class="appearance-theme-preview-badge"></i></div><div class="appearance-theme-preview-meta"><i></i><i></i></div></div></div>' .
      '<div class="appearance-theme-preview-copy"><i class="appearance-theme-preview-copy-line"></i><i class="appearance-theme-preview-copy-link"></i><i class="appearance-theme-preview-inline-code"></i></div>' .
      '<div class="appearance-theme-preview-quote"><i></i><i></i></div>' .
      '<div class="appearance-theme-preview-code"><i></i><i></i></div></div>' .
      '<div class="appearance-theme-preview-side"><i class="appearance-theme-preview-user"></i><i class="appearance-theme-preview-side-line"></i><i class="appearance-theme-preview-side-line"></i><div class="appearance-theme-preview-side-input"></div><div class="appearance-theme-preview-side-button"></div><i class="appearance-theme-preview-side-line appearance-theme-preview-side-gap"></i><i class="appearance-theme-preview-side-line"></i><i class="appearance-theme-preview-side-line"></i></div>' .
      '</div></div></section>' .
      '<section class="appearance-theme-section"><div class="appearance-theme-mode-bar" id="appearance-theme-mode-bar" role="group" aria-label="明暗模式">' .
      '<button type="button" data-appearance-theme-mode="light">浅色</button><button type="button" data-appearance-theme-mode="dark">深色</button></div>' .
      '<div class="appearance-theme-schemes" id="appearance-theme-schemes"></div></section>' .
      '<section class="appearance-theme-section"><div class="appearance-theme-level-bar" id="appearance-theme-level-bar" role="tablist" aria-label="设置级别">' .
      '<button type="button" id="appearance-theme-tab-basic" role="tab" aria-controls="appearance-theme-basics" data-appearance-theme-level="basic">基础设置</button><button type="button" id="appearance-theme-tab-advanced" role="tab" aria-controls="appearance-theme-advanced" data-appearance-theme-level="advanced">高级设置</button></div>' .
      '<div class="appearance-theme-basics" id="appearance-theme-basics" role="tabpanel" aria-labelledby="appearance-theme-tab-basic"></div><div class="appearance-theme-advanced" id="appearance-theme-advanced" role="tabpanel" aria-labelledby="appearance-theme-tab-advanced" hidden></div></section>' .
      '<div class="appearance-theme-footer"><span class="appearance-theme-actions">' .
      '<button class="appearance-theme-button" type="button" data-appearance-theme-action="import">导入配色</button>' .
      '<button class="appearance-theme-button" type="button" data-appearance-theme-action="export">导出配色</button>' .
      '<button class="appearance-theme-button appearance-theme-button-ghost" type="button" data-appearance-theme-action="undo">放弃修改</button>' .
      '<button class="appearance-theme-button appearance-theme-button-primary" id="appearance-theme-save" type="submit"' . ($state['persisted'] && !$needs_migration ? ' disabled' : '') . '>保存</button>' .
      '</span></div><input class="appearance-theme-file" id="appearance-theme-file" type="file" accept="application/json,.json" hidden>' .
      '<input class="appearance-theme-file" id="appearance-theme-font-file" type="file" accept="font/woff2,.woff2" hidden>' .
      '</form><div class="appearance-theme-toast" id="appearance-theme-toast" role="status" aria-live="polite"></div></div>';

  page('外观主题', form_shell($main, me()));
}

function appearance_theme_css(): string
{
    return appearance_theme_embedded_font_css() . <<<'CSS'
html.appearance-theme-active body{font-family:var(--appearance-theme-font-ui)}
html.appearance-theme-active :where(.post-meta,.breadcrumb,.breadcrumbs,.topic-meta,.forum-meta,.post-floor,.floor,.topic-stat,.topic-stats,.forum-stat,.forum-stats,.stat,.stats){color:var(--text-subtle);font-size:var(--font-size-sm)}
html.appearance-theme-active :where(input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not([type=file]),select,textarea,.search-field,.search-input,.reply-form textarea,.reply-box textarea,.modal input,.modal select,.modal textarea):not(.appearance-theme-root *){background-color:var(--appearance-theme-input-bg);color:var(--text);border-color:var(--line);caret-color:var(--appearance-theme-input-caret)}
html.appearance-theme-active :where(input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not([type=file]),textarea,.search-field,.search-input,.reply-form textarea,.reply-box textarea,.modal input,.modal textarea):not(.appearance-theme-root *)::selection{background:var(--brand-soft);color:var(--text)}
html.appearance-theme-active .post-content{line-height:var(--appearance-theme-line-height);color:var(--appearance-theme-body-text)}
html.appearance-theme-active .post-content a{color:var(--appearance-theme-body-link)}
html.appearance-theme-active :where(.post-content blockquote,.notify-quote-card){color:var(--appearance-theme-quote-text);border-left-color:var(--appearance-theme-quote-border)}
html.appearance-theme-active .post-content code{font-family:var(--font-mono);font-size:var(--appearance-theme-code-font-size);background-color:var(--appearance-theme-inline-code-bg);color:var(--appearance-theme-inline-code-text)}
html.appearance-theme-active .post-content pre{font-family:var(--font-mono);font-size:var(--appearance-theme-code-font-size);background:var(--appearance-theme-code-bg)!important;color:var(--appearance-theme-code-text);border-color:var(--appearance-theme-code-border)}
html.appearance-theme-active .post-content pre code{background:transparent;color:inherit}
html.appearance-theme-active :where(.post-highlight,.post-content pre){border-radius:var(--radius)}
html.appearance-theme-active :where(.post-content code,.post-tag){border-radius:var(--radius-sm)}
html.appearance-theme-active :where(.topic-list,.notification-list,.notify-list) > :where(.topic-item,.notification-item,.notify-item),html.appearance-theme-active .post-list:not(.topic-post-list)>.post-item{padding-top:var(--appearance-theme-topic-padding);padding-bottom:var(--appearance-theme-topic-padding);border-bottom-color:var(--appearance-theme-post-divider);transition:background-color var(--transition-normal)}
html.appearance-theme-active :where(.topic-list,.notification-list,.notify-list) > :where(.topic-item,.notification-item,.notify-item):hover,html.appearance-theme-active .post-list:not(.topic-post-list)>.post-item:hover{background-color:var(--appearance-theme-post-hover)}
html.appearance-theme-active .topic-post-list>.post-item{border-bottom-color:var(--appearance-theme-post-divider)}
html.appearance-theme-active .topic-badge.pinned{color:var(--appearance-theme-pinned);background:var(--appearance-theme-pinned-soft)}
html.appearance-theme-active :where(.notification-unread,.notification-kind){color:var(--appearance-theme-danger-text);background:var(--danger-soft)}
html.appearance-theme-active .notify-badge{color:var(--appearance-theme-danger-solid-text)}
html.appearance-theme-active .like-coin-form .like-coin-action{opacity:1}
html.appearance-theme-active .like-coin-form .like-coin-action:hover{color:var(--appearance-theme-line-soft-text)}
html.appearance-theme-active .like-coin-form .like-coin-action.active:not(:hover){color:var(--text)}
html.appearance-theme-active .plugin-ops .plugin-enable,
html.appearance-theme-active .plugin-ops .plugin-manage-link,
html.appearance-theme-active .plugin-ops .plugin-enable:hover,
html.appearance-theme-active .plugin-ops .plugin-manage-link:hover{color:var(--appearance-theme-brand-soft-text)}
html.appearance-theme-active .plugin-market-approval-form .plugin-market-approval-action.active{color:var(--appearance-theme-line-soft-text)}
html.appearance-theme-active .plugin-market-approval-form .plugin-market-approval-action.active:hover{color:var(--appearance-theme-bg-text)}
html.appearance-theme-active .plugin-market-version-actions .plugin-market-version-action:hover,
html.appearance-theme-active .plugin-market-history-list .plugin-market-history-download:hover{color:var(--text)}
html.appearance-theme-active .reply-panel .markdown-textarea{display:flex;align-items:center;flex-wrap:wrap;gap:2px;padding:4px 6px;border:1px solid var(--line);border-bottom:0;border-radius:var(--radius-sm) var(--radius-sm) 0 0;background:var(--brand-soft)}
html.appearance-theme-active .reply-panel .markdown-textarea button{display:inline-flex;align-items:center;justify-content:center;justify-self:auto;flex:0 0 30px;width:30px;min-width:30px;height:28px;min-height:28px;margin:0;padding:0;border:1px solid transparent;border-radius:var(--radius-sm);background:transparent;color:var(--brand);font:inherit;font-weight:500;line-height:1;box-shadow:none}
html.appearance-theme-active .reply-panel .markdown-textarea button:hover{border-color:var(--line);background:var(--panel);color:var(--brand-hover)}
html.appearance-theme-active .reply-panel .markdown-textarea+textarea{border-top-left-radius:0;border-top-right-radius:0}
html.appearance-theme-active .sidebar .card{background-color:var(--appearance-theme-sidebar-bg);-webkit-backdrop-filter:var(--appearance-theme-sidebar-backdrop);backdrop-filter:var(--appearance-theme-sidebar-backdrop)}
html.appearance-theme-active :where(.btn:not(.alt):not(.danger),button:not(.danger):not(.forum-more-toggle):not(.search-btn):not(.mobile-menu-button):not(.mobile-menu-close):not(.modal-close):not(.profile-edit-action):not(.profile-exit-button):not(.topic-color-swatch):not(.topic-color-clear):not(.settings-opcache-title):not(.settings-tool-action),.tab.active,.topic-toolbar .tab-post,.side-auth a,.btn-post,.reply-login-box a,.side a.pill):not(.appearance-theme-root *){--inverse-text:var(--appearance-theme-brand-text)}
html.appearance-theme-active :where(.btn:not(.alt):not(.danger):hover,button:not(.danger):not(.forum-more-toggle):not(.search-btn):not(.mobile-menu-button):not(.mobile-menu-close):not(.modal-close):not(.profile-edit-action):not(.profile-exit-button):not(.topic-color-swatch):not(.topic-color-clear):not(.settings-opcache-title):not(.settings-tool-action):hover,.btn-post:hover,.reply-login-box a:hover):not(.appearance-theme-root *){--inverse-text:var(--appearance-theme-brand-hover-text)}
.main-panel:has(> .appearance-theme-root){overflow:visible}
@media(prefers-reduced-motion:reduce){html.appearance-theme-active:root{--transition-normal:0s}}
.appearance-theme-root{--appearance-theme-editor-radius:6px;position:relative;color:var(--text)}
.appearance-theme-root *{box-sizing:border-box}
.appearance-theme-title{margin:0 0 22px;font-size:var(--font-size-xl);font-weight:650;line-height:1.3}
.appearance-theme-section{margin-bottom:30px}
.appearance-theme-preview-section{position:sticky;top:12px;z-index:12;margin-bottom:20px;padding:0;border:0;border-radius:0;background:transparent;box-shadow:none}
.appearance-theme-preview{overflow:hidden;border:1px solid var(--line);border-radius:8px;background:var(--appearance-theme-preview-bg);font-family:var(--appearance-theme-preview-font-ui);font-size:12px;line-height:var(--appearance-theme-preview-line-height,1.55)}
.appearance-theme-preview-nav{display:flex;align-items:center;justify-content:space-between;height:30px;padding:0 11px;background:var(--appearance-theme-preview-nav-bg);border-bottom:1px solid var(--appearance-theme-preview-line);color:var(--appearance-theme-preview-text);font-size:10px;font-weight:700}
.appearance-theme-preview-nav span:last-child{color:var(--appearance-theme-preview-text-subtle);font-weight:400}
.appearance-theme-preview-body{display:grid;grid-template-columns:minmax(0,1fr) 116px;gap:9px;padding:var(--appearance-theme-preview-mobile-padding,var(--appearance-theme-preview-padding,10px))}
.appearance-theme-preview-main,.appearance-theme-preview-side{padding:9px;border:1px solid var(--appearance-theme-preview-line);border-radius:var(--appearance-theme-preview-radius);background:var(--appearance-theme-preview-panel)}
.appearance-theme-preview-side{opacity:1;-webkit-backdrop-filter:none;backdrop-filter:none}
.appearance-theme-preview-tabs{display:flex;gap:4px;margin-bottom:7px}
.appearance-theme-preview-tabs span{padding:2px 7px;border:1px solid var(--appearance-theme-preview-line);border-radius:var(--appearance-theme-preview-radius-sm);color:var(--appearance-theme-preview-text-muted);font-size:9px}
.appearance-theme-preview-tabs .appearance-theme-preview-tab-active{border-color:var(--appearance-theme-preview-brand);background:var(--appearance-theme-preview-brand);color:var(--appearance-theme-preview-brand-text)}
.appearance-theme-preview-post{display:grid;grid-template-columns:22px minmax(0,1fr);gap:8px;padding:6px 0;border-bottom:1px solid var(--appearance-theme-preview-divider)}
.appearance-theme-preview-post:last-of-type{border-bottom:0}
.appearance-theme-preview-post.appearance-theme-preview-hover{margin:0 -7px;padding:6px 7px;border-radius:var(--appearance-theme-preview-radius-sm);background:var(--appearance-theme-preview-post-hover)}
.appearance-theme-preview-avatar{width:22px;height:22px;border-radius:var(--appearance-theme-preview-radius-sm);background:var(--appearance-theme-preview-avatar)}
.appearance-theme-preview-row{display:flex;align-items:center;gap:7px}
.appearance-theme-preview-title-line{flex:1 1 auto;height:6px;border-radius:3px;background:var(--appearance-theme-preview-post-title)}
.appearance-theme-preview-badge{display:inline-flex;align-items:center;justify-content:center;flex:0 0 28px;height:11px;border-radius:var(--appearance-theme-preview-radius-sm);background:var(--appearance-theme-preview-forum-badge);font-size:6px;font-style:normal;font-weight:700;line-height:1}
.appearance-theme-preview-pin{border:0;color:var(--appearance-theme-preview-pinned);background:var(--appearance-theme-preview-pinned-soft)}
.appearance-theme-preview-meta{display:flex;gap:5px;margin-top:5px}
.appearance-theme-preview-meta i{width:22px;height:3px;border-radius:2px;background:var(--appearance-theme-preview-post-meta);opacity:.55}
.appearance-theme-preview-user{display:block;width:22px;height:22px;margin-bottom:7px;border-radius:50%;background:linear-gradient(135deg,#c98e7a,#697b9b)}
.appearance-theme-preview-side-line{display:block;height:3px;margin:5px 0;border-radius:2px;background:var(--appearance-theme-preview-post-meta);opacity:.45}
.appearance-theme-preview-side-input{height:14px;margin-top:8px;border:1px solid var(--appearance-theme-preview-line);border-radius:var(--appearance-theme-preview-radius-sm);background:var(--appearance-theme-preview-input-bg)}
.appearance-theme-preview-side-button{height:15px;margin-top:6px;border-radius:var(--appearance-theme-preview-radius-sm);background:var(--appearance-theme-preview-brand)}
.appearance-theme-preview-side-gap{margin-top:12px}
.appearance-theme-preview-copy{display:flex;align-items:center;gap:5px;margin-top:7px;padding:5px 7px;border-radius:var(--appearance-theme-preview-radius-sm);background:var(--appearance-theme-preview-input-bg)}
.appearance-theme-preview-copy i{display:block;height:3px;border-radius:2px}
.appearance-theme-preview-copy-line{width:34%;background:var(--appearance-theme-preview-body-text)}
.appearance-theme-preview-copy-link{width:18%;background:var(--appearance-theme-preview-body-link)}
.appearance-theme-preview-inline-code{width:20%;height:10px!important;border:1px solid var(--appearance-theme-preview-code-border);background:var(--appearance-theme-preview-inline-code-bg)}
.appearance-theme-preview-quote{margin-top:6px;padding:5px 7px;border-left:3px solid var(--appearance-theme-preview-quote-border);background:var(--appearance-theme-preview-input-bg)}
.appearance-theme-preview-quote i{display:block;width:72%;height:3px;margin:3px 0;border-radius:2px;background:var(--appearance-theme-preview-quote-text);opacity:.65}
.appearance-theme-preview-quote i:nth-child(2){width:48%}
.appearance-theme-preview-code{margin:6px 0 0;padding:8px;border:1px solid var(--appearance-theme-preview-code-border);border-radius:var(--appearance-theme-preview-radius-sm);background:var(--appearance-theme-preview-code-bg);font-family:var(--appearance-theme-preview-font-mono);font-size:var(--appearance-theme-preview-code-font-size,12px)}
.appearance-theme-preview-code i{display:block;width:68%;height:3px;margin:4px 0;border-radius:2px;background:var(--appearance-theme-preview-code-text);opacity:.8}
.appearance-theme-preview-code i:nth-child(2){width:48%}
.appearance-theme-mode-bar,.appearance-theme-level-bar{display:inline-flex;gap:2px;margin-bottom:14px;padding:3px;border:1px solid var(--line);border-radius:var(--appearance-theme-editor-radius);background:var(--line-soft)}
.appearance-theme-mode-bar button,.appearance-theme-level-bar button{min-height:30px;padding:0 15px;border:0;border-radius:4px;background:transparent;color:var(--text-muted);font:inherit;font-size:var(--font-size-sm);cursor:pointer}
.appearance-theme-mode-bar button:hover,.appearance-theme-level-bar button:hover{background:color-mix(in srgb,var(--panel) 58%,transparent);color:var(--text)}
.appearance-theme-mode-bar button.appearance-theme-is-active,.appearance-theme-level-bar button.appearance-theme-is-active,.appearance-theme-mode-bar button.appearance-theme-is-active:hover,.appearance-theme-level-bar button.appearance-theme-is-active:hover{background:var(--panel);color:var(--text);font-weight:600;box-shadow:0 1px 3px rgba(16,24,40,.1)}
.appearance-theme-scheme-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(152px,185px));gap:10px;align-items:start;justify-content:start}
.appearance-theme-preset-grid{grid-template-columns:repeat(6,minmax(0,1fr));width:100%}
.appearance-theme-scheme-heading{margin:18px 0 10px;font-size:var(--font-size-sm);font-weight:650;color:var(--text)}
.appearance-theme-scheme-cell{display:flex;flex-direction:column;min-width:0}
.appearance-theme-scheme{display:block;width:100%;min-width:0;min-height:0;overflow:hidden;padding:0;border:1.5px solid var(--line);border-radius:8px;background:var(--panel);color:var(--text);font:inherit;text-align:left;white-space:normal;cursor:pointer;transition:border-color .15s,box-shadow .15s,background-color .15s}
.appearance-theme-scheme:hover{border-color:color-mix(in srgb,var(--line) 45%,var(--text-muted));background:var(--panel);color:var(--text)}
.appearance-theme-scheme.appearance-theme-is-active,.appearance-theme-scheme.appearance-theme-is-active:hover{border-color:var(--brand);background:var(--panel);color:var(--text);box-shadow:0 0 0 3px var(--brand-soft)}
.appearance-theme-scheme-swatches{position:relative;display:flex;width:100%;height:52px;overflow:hidden;border-radius:6px 6px 0 0}
.appearance-theme-scheme-accent{flex:0 0 30%;border-radius:6px 0 0 0}
.appearance-theme-scheme-page{position:relative;flex:1 1 auto;padding:10px 10px 0}
.appearance-theme-scheme-page:after{content:"";position:absolute;right:9px;bottom:8px;width:22px;height:7px;border-radius:3px;background:var(--appearance-theme-scheme-status,var(--line))}
.appearance-theme-scheme-line{display:block;width:82%;height:6px;margin-bottom:5px;border-radius:3px}
.appearance-theme-scheme-line:last-child{width:56%}
.appearance-theme-scheme-name{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:8px 10px;border-top:1px solid var(--line-soft);font-size:var(--font-size-sm);font-weight:600}
.appearance-theme-scheme-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.appearance-theme-scheme-current{flex:0 0 auto;color:var(--brand);font-size:var(--font-size-xs);font-style:normal;font-weight:600}
.appearance-theme-scheme-tag{flex:0 0 auto;padding:1px 6px;border-radius:3px;background:var(--line-soft);color:var(--text-subtle);font-size:var(--font-size-xs);font-style:normal;font-weight:500}
.appearance-theme-scheme-tag-mine{background:var(--brand-soft);color:var(--brand-hover)}
.appearance-theme-scheme-editor{display:block;margin-top:7px}
.appearance-theme-scheme-editor .appearance-theme-scheme-name-input{display:block;width:100%;min-width:0}
.appearance-theme-scheme-editor .appearance-theme-scheme-editor-controls{margin-top:7px}
.appearance-theme-scheme-editor .appearance-theme-scheme-action:first-of-type{font-weight:600}
.appearance-theme-scheme-editor-card{display:block;width:100%;min-width:0;overflow:hidden;border:1.5px solid var(--line);border-radius:8px;background:var(--panel);color:var(--text)}
.appearance-theme-scheme-editor-card .appearance-theme-scheme-swatches{height:52px;border-radius:6px 6px 0 0}
.appearance-theme-scheme-editor-body{display:block;padding:8px 9px 9px;border-top:1px solid var(--line-soft)}
.appearance-theme-scheme-name-input{width:min(260px,100%);min-height:32px;padding:5px 9px;border:1px solid var(--line);border-radius:4px;background:var(--panel);color:var(--text);font:inherit;font-size:var(--font-size-sm)}
.appearance-theme-scheme-editor-card .appearance-theme-scheme-name-input{display:block;width:100%;min-width:0}
.appearance-theme-scheme-name-input:focus{outline:2px solid var(--focus-ring);outline-offset:1px;border-color:var(--brand)}
.appearance-theme-scheme-editor-controls{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-top:7px}
.appearance-theme-scheme-editor-controls .appearance-theme-scheme-action{display:flex;align-items:center;justify-content:center;width:100%;min-width:0;padding:0 7px;border:1px solid var(--line);background:var(--panel);color:var(--text-muted);font:inherit;font-size:var(--font-size-xs);font-weight:500;line-height:1;white-space:nowrap;box-shadow:none;appearance:none;-webkit-appearance:none}
.appearance-theme-scheme-editor-controls .appearance-theme-scheme-action:hover{border-color:var(--text-muted);background:color-mix(in srgb,var(--panel) 88%,var(--bg));color:var(--text)}
.appearance-theme-scheme-actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-top:7px}
.appearance-theme-scheme-actions .appearance-theme-scheme-action{display:flex;align-items:center;justify-content:center;width:100%;min-width:0;padding:0 7px;white-space:nowrap}
.appearance-theme-scheme-action{min-height:30px;padding:0 11px;border:1px solid var(--line);border-radius:4px;background:var(--panel);color:var(--text-muted);font:inherit;font-size:var(--font-size-sm);cursor:pointer}
.appearance-theme-scheme-action:hover{border-color:var(--text-muted);background:color-mix(in srgb,var(--panel) 88%,var(--bg));color:var(--text)}
.appearance-theme-scheme-action-delete{color:var(--danger)}
.appearance-theme-scheme-action-delete:hover{border-color:var(--danger);background:var(--danger-soft);color:var(--danger)}
.appearance-theme-scheme-add{border-style:dashed}
.appearance-theme-scheme-add:hover{background:color-mix(in srgb,var(--panel) 94%,var(--brand-soft));color:var(--text)}
.appearance-theme-scheme-add:disabled,.appearance-theme-scheme-add:disabled:hover{border-color:var(--line);background:var(--panel);color:var(--text-subtle);box-shadow:none;opacity:.55;cursor:not-allowed}
.appearance-theme-scheme-add .appearance-theme-scheme-swatches{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--panel) 88%,var(--bg));color:var(--text-subtle);font-size:var(--font-size-xl)}
.appearance-theme-scheme-add .appearance-theme-scheme-name{color:var(--text-muted);font-weight:500}
.appearance-theme-basics,.appearance-theme-advanced{overflow:hidden;border:1px solid var(--line);border-radius:var(--appearance-theme-editor-radius)}
.appearance-theme-field{display:grid;grid-template-columns:minmax(0,1fr) 232px;align-items:center;gap:16px;min-height:58px;padding:10px 14px;border-bottom:1px solid var(--line-soft)}
.appearance-theme-field:last-child{border-bottom:0}
.appearance-theme-field-label strong{display:block;font-size:var(--font-size-sm);font-weight:600}
.appearance-theme-field-label small{display:block;margin-top:1px;color:var(--text-subtle);font-size:var(--font-size-xs)}
.appearance-theme-control{position:relative;display:flex;align-items:center;gap:8px;min-width:0}
.appearance-theme-control input[type=color]{flex:0 0 32px;width:32px;height:30px;padding:0;border:1px solid var(--line);border-radius:4px;background:var(--panel);cursor:pointer}
.appearance-theme-control input[type=text],.appearance-theme-control input[type=number],.appearance-theme-control select{flex:1 1 0;min-width:0;min-height:30px;padding:0 8px;border:1px solid var(--line);border-radius:4px;background:var(--panel);color:var(--text);font:inherit;font-size:var(--font-size-sm)}
.appearance-theme-control input[type=text]{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:var(--font-size-xs);text-transform:uppercase}
.appearance-theme-control input[type=number]{padding-right:30px}
.appearance-theme-control input[type=range]{flex:1 1 0;min-width:0;accent-color:var(--brand)}
.appearance-theme-control-output{flex:0 0 32px;color:var(--text-muted);font-size:var(--font-size-xs);text-align:right;font-variant-numeric:tabular-nums}
.appearance-theme-control-unit{position:absolute;right:9px;top:50%;transform:translateY(-50%);pointer-events:none;color:var(--text-subtle);font-size:var(--font-size-xs)}
.appearance-theme-font-control{flex-wrap:wrap;row-gap:6px}
.appearance-theme-font-control select{flex:1 1 100px;min-width:100px}
.appearance-theme-font-action{flex:0 0 auto;min-height:30px;padding:0 10px;border:1px solid var(--line);border-radius:4px;background:var(--panel);color:var(--text-muted);font:inherit;font-size:var(--font-size-xs);white-space:nowrap;cursor:pointer}
.appearance-theme-font-action:hover{border-color:var(--text-muted);background:color-mix(in srgb,var(--panel) 88%,var(--bg));color:var(--text)}
.appearance-theme-font-remove{border-color:transparent;color:var(--danger)}
.appearance-theme-font-remove:hover{border-color:var(--danger);background:var(--danger-soft);color:var(--danger)}
.appearance-theme-font-action:disabled{opacity:.55;cursor:wait}
.appearance-theme-toggle-label{flex:1 1 auto;color:var(--text-muted);font-size:var(--font-size-xs)}
.appearance-theme-toggle{display:flex;align-items:center;justify-content:flex-start;flex:0 0 40px;width:40px;height:22px;min-height:22px;padding:2px;border:0;border-radius:999px;background:color-mix(in srgb,var(--line) 70%,var(--text-subtle));cursor:pointer;transition:background .18s}
.appearance-theme-toggle:hover{background:color-mix(in srgb,var(--line) 58%,var(--text-subtle))}
.appearance-theme-toggle.appearance-theme-is-active,.appearance-theme-toggle.appearance-theme-is-active:hover{background:var(--brand)}
.appearance-theme-toggle i{display:block;width:18px;height:18px;border-radius:50%;background:#fff;transition:transform .18s}
.appearance-theme-toggle.appearance-theme-is-active i{transform:translateX(18px)}
.appearance-theme-advanced-group{border-bottom:1px solid var(--line-soft)}
.appearance-theme-advanced-group:last-child{border-bottom:0}
.appearance-theme-advanced-title{display:flex;align-items:center;gap:10px;width:100%;min-height:50px;padding:0 14px;border:0;background:transparent;color:var(--text);font:inherit;font-size:var(--font-size-sm);font-weight:600;text-align:left;cursor:pointer}
.appearance-theme-advanced-title:hover,.appearance-theme-advanced-group.appearance-theme-is-open .appearance-theme-advanced-title{background:color-mix(in srgb,var(--panel) 90%,var(--bg));color:var(--text)}
.appearance-theme-advanced-count{color:var(--text-subtle);font-size:var(--font-size-xs);font-weight:400}
.appearance-theme-advanced-chevron{margin-left:auto;color:var(--text-subtle);font-size:var(--font-size-xs);transition:transform .18s}
.appearance-theme-advanced-group.appearance-theme-is-open .appearance-theme-advanced-chevron{transform:rotate(90deg)}
.appearance-theme-advanced-body{padding:2px 14px 10px}
.appearance-theme-advanced-body .appearance-theme-field{min-height:48px;padding:6px 0}
.appearance-theme-advanced-body .appearance-theme-field-label>span:first-child{display:block;font-size:var(--font-size-sm);font-weight:600}
.appearance-theme-footer{position:sticky;bottom:0;z-index:10;display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:10px 0 max(6px,env(safe-area-inset-bottom,0px));border-top:1px solid var(--line);background:var(--panel)}
.appearance-theme-actions{display:flex;align-items:center;gap:8px;margin-left:auto}
.appearance-theme-button{display:inline-flex;align-items:center;min-height:32px;padding:0 13px;border:1px solid var(--line);border-radius:4px;background:var(--panel);color:var(--text-muted);font:inherit;font-size:var(--font-size-sm);cursor:pointer}
.appearance-theme-button:hover{border-color:color-mix(in srgb,var(--line) 50%,var(--text-muted));background:color-mix(in srgb,var(--panel) 88%,var(--bg));color:var(--text)}
.appearance-theme-button-primary{border-color:var(--brand);background:var(--brand);color:var(--appearance-theme-brand-text);font-weight:600}
.appearance-theme-button-primary:hover{border-color:var(--brand-hover);background:var(--brand-hover);color:var(--appearance-theme-brand-hover-text)}
.appearance-theme-button-ghost:hover{border-color:var(--line);background:color-mix(in srgb,var(--panel) 88%,var(--bg));color:var(--text)}
.appearance-theme-button-primary:disabled,.appearance-theme-button-primary:disabled:hover{border-color:var(--brand);background:var(--brand);color:var(--appearance-theme-brand-text);opacity:.4;cursor:not-allowed}
.appearance-theme-button-ghost{border-color:transparent;background:transparent}
.appearance-theme-toast{position:fixed;left:50%;bottom:24px;z-index:99;padding:9px 16px;border-radius:var(--appearance-theme-editor-radius);background:#22303f;color:#fff;font-size:var(--font-size-sm);opacity:0;pointer-events:none;transform:translate(-50%,14px);transition:.22s}
.appearance-theme-toast.appearance-theme-is-visible{opacity:1;transform:translate(-50%,0)}
.appearance-theme-root button:active{opacity:1}
.appearance-theme-root :where(button,input,select):focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
@media (max-width:980px){.home-shell:has(.appearance-theme-root) .forum-layout{display:block}.home-shell:has(.appearance-theme-root) .sidebar{display:none}}
@media (max-height:760px){.appearance-theme-footer{position:static}.appearance-theme-preview-section{padding:0}.appearance-theme-preview-nav{height:28px;padding:0 9px}.appearance-theme-preview-body{--appearance-theme-preview-mobile-padding:7px}.appearance-theme-preview-main,.appearance-theme-preview-side{padding:7px}.appearance-theme-preview-post{padding:4px 0}.appearance-theme-preview-post:nth-of-type(4),.appearance-theme-preview-copy,.appearance-theme-preview-quote{display:none}.appearance-theme-preview-code{padding:6px}}
@media (max-width:680px){.appearance-theme-title{margin-bottom:16px}.appearance-theme-section{margin-bottom:24px}.appearance-theme-preview-section{top:6px;margin-bottom:16px}.appearance-theme-mode-bar,.appearance-theme-level-bar{margin-bottom:12px}.appearance-theme-scheme-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.appearance-theme-preset-grid>.appearance-theme-scheme:last-child:nth-child(odd){grid-column:1/-1;width:calc((100% - 10px)/2);justify-self:center}.appearance-theme-scheme-heading{margin:14px 0 8px}.appearance-theme-field{grid-template-columns:1fr;gap:8px}.appearance-theme-footer{position:static;align-items:stretch;padding:10px 0 max(8px,env(safe-area-inset-bottom,0px))}.appearance-theme-actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));width:100%;margin-left:0}.appearance-theme-button{width:100%;min-height:44px;justify-content:center}.appearance-theme-preview-body{grid-template-columns:minmax(0,1fr) 90px}.appearance-theme-scheme-name-input{flex:1 1 100%;width:100%}.appearance-theme-scheme-editor-controls .appearance-theme-scheme-action{min-height:44px;padding:0 5px}.appearance-theme-font-action{min-height:44px}}
@media (max-width:390px){body:has(.appearance-theme-root) .search-form{display:none}body:has(.appearance-theme-root) .brand{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;font-size:var(--font-size-md)}.appearance-theme-preview-section{padding:0}.appearance-theme-preview-nav{height:26px;padding:0 8px}.appearance-theme-preview-body{grid-template-columns:1fr;gap:0;--appearance-theme-preview-mobile-padding:7px}.appearance-theme-preview-side{display:none}.appearance-theme-preview-main{padding:7px}.appearance-theme-preview-post{grid-template-columns:18px minmax(0,1fr);gap:6px;padding:4px 0}.appearance-theme-preview-avatar{width:18px;height:18px}.appearance-theme-preview-post:nth-of-type(4){display:none}.appearance-theme-preview-copy,.appearance-theme-preview-quote{display:none}.appearance-theme-preview-code{padding:5px}}
@media (max-width:340px){.appearance-theme-preview-post:nth-of-type(3){display:none}.appearance-theme-preview-code{margin-top:5px}.appearance-theme-scheme-grid{grid-template-columns:1fr}.appearance-theme-preset-grid>.appearance-theme-scheme:last-child:nth-child(odd){grid-column:auto;width:100%}}
@media (prefers-reduced-motion:reduce){.appearance-theme-root *{scroll-behavior:auto;transition-duration:0s}}
CSS;
}

function appearance_theme_js(): string
{
  $schema_version = APPEARANCE_THEME_SCHEMA_VERSION;
    return str_replace('__APPEARANCE_THEME_SCHEMA_VERSION__', (string)$schema_version, <<<'JS'
(function appearance_theme_main(){
"use strict";
var userStyle=document.getElementById("appearance-theme-user-style");
document.documentElement.classList.toggle("appearance-theme-active",!!userStyle);
var root=document.getElementById("appearance-theme-root");
if(!root)return;
var parseState=function(){try{return JSON.parse(root.getAttribute("data-appearance-theme-state")||"");}catch(error){return null;}};
var initial=parseState();
if(!initial)return;
var clone=function(value){return JSON.parse(JSON.stringify(value));};
var clamp=function(value,min,max){return Math.min(max,Math.max(min,value));};
var hexRgb=function(hex){var value=String(hex||"").replace("#","");if(value.length===3)value=value[0]+value[0]+value[1]+value[1]+value[2]+value[2];if(!/^[0-9a-f]{6}$/i.test(value))return null;return[parseInt(value.slice(0,2),16),parseInt(value.slice(2,4),16),parseInt(value.slice(4,6),16)];};
var rgbHex=function(rgb){return"#"+rgb.map(function(value){return clamp(Math.round(value),0,255).toString(16).padStart(2,"0");}).join("");};
var mix=function(from,to,amount){var a=hexRgb(from),b=hexRgb(to);if(!a||!b)return from;return rgbHex([a[0]+(b[0]-a[0])*amount,a[1]+(b[1]-a[1])*amount,a[2]+(b[2]-a[2])*amount]);};
var luminance=function(hex){var rgb=hexRgb(hex);if(!rgb)return 0;var channels=rgb.map(function(value){value/=255;return value<=.03928?value/12.92:Math.pow((value+.055)/1.055,2.4);});return.2126*channels[0]+.7152*channels[1]+.0722*channels[2];};
var contrastText=function(background){var value=luminance(background),white=1.05/(value+.05),dark=(value+.05)/.05;return white>=dark?"#ffffff":"#111827";};
var solidText=function(background){var value=luminance(background),white=1.05/(value+.05),black=(value+.05)/.05;return white>=black?"#ffffff":"#000000";};
var readableColor=function(foreground,background){var first=luminance(foreground),second=luminance(background),lighter=Math.max(first,second),darker=Math.min(first,second);return(lighter+.05)/(darker+.05)>=3?foreground:contrastText(background);};
var readableSmallColor=function(foreground,background){var first=luminance(foreground),second=luminance(background),lighter=Math.max(first,second),darker=Math.min(first,second);return(lighter+.05)/(darker+.05)>=4.5?foreground:contrastText(background);};
var isDark=function(hex){return luminance(hex)<.4;};
var schemaVersion=__APPEARANCE_THEME_SCHEMA_VERSION__;
var v1UiFonts=["系统字体","LXGW WenKai","Noto Sans SC","Inter"];
var uiFonts=["系统字体","LXGW WenKai","Noto Sans SC","Inter","Poppins"];
var monoFonts=["系统等宽","Fira Code","Sarasa Mono"];
var basic=[
["brand","强调色","链接、按钮和选中状态","color"],
["fontUi","字体","界面文字","select",{options:uiFonts}],
["fontSize","字号","正文和控件大小","number",{min:12,max:20,unit:"px"}],
["density","列表密度","主题和通知列表的行距","select",{options:["紧凑","舒适","宽松"]}]
];
var groups=[
["forum","论坛界面",[["bg","论坛页面背景","","color"],["panel","内容面板背景","","color"],["text","主题标题与主要文字","","color"],["textMuted","次要文字","","color"],["textSubtle","作者、时间与楼层文字","","color"],["line","卡片与表单边框","","color"],["inputBg","输入框背景","","color"]]],
["topics","主题列表与提示",[["postHover","主题/通知列表悬停背景","","color"],["divider","主题列表分隔线","","color"],["pinned","置顶主题标签色","","color"],["danger","未读通知与危险操作色","","color"]]],
["reading","帖子正文",[["bodyLink","正文链接颜色","帖子中的链接","color"],["lineHeight","帖子正文行高","","slider",{min:130,max:210}],["quoteBorder","引用边线颜色","","color"],["inlineCodeBg","行内代码背景","","color"],["codeBg","多行代码背景","","color"],["codeText","多行代码文字","","color"]]],
["appearance","字体与外观",[["fontMono","帖子代码字体","","select",{options:monoFonts}],["fontSizeSm","作者与时间字号","","number",{min:10,max:16,unit:"px"}],["codeFontSize","帖子代码字号","","number",{min:10,max:18,unit:"px"}],["radius","面板圆角","","number",{min:0,max:20,unit:"px"}],["radiusSm","按钮与标签圆角","","number",{min:0,max:14,unit:"px"}],["shadow","面板阴影强度","","slider",{min:0,max:100}]]]
];
var fields={};
basic.forEach(function(field){fields[field[0]]={key:field[0],label:field[1],description:field[2],type:field[3],options:field[4]||{}};});
groups.forEach(function(group){group[2].forEach(function(field){fields[field[0]]={key:field[0],label:field[1],description:field[2],type:field[3],options:field[4]||{}};});});
var fieldKeys=Object.keys(fields);
var normalizeHex=function(value){if(typeof value!=="string")return null;var rgb=hexRgb(value);return rgb?rgbHex(rgb):null;};
var defaults={dark:{
brand:"#5b7fb5",fontUi:"系统字体",fontSize:14,density:"舒适",bg:"#161a20",panel:"#1d222a",text:"#e6eaef",textMuted:"#9aa4b0",textSubtle:"#697078",line:"#2b323c",inputBg:"#191e25",postHover:"#232a34",divider:"#2d323a",pinned:"#d08a3c",danger:"#d05c5c",bodyLink:"#7996c2",lineHeight:175,quoteBorder:"#41587b",inlineCodeBg:"#252c37",codeBg:"#0b1017",codeText:"#e8eef6",fontMono:"系统等宽",fontSizeSm:13,codeFontSize:12,radius:6,radiusSm:4,shadow:52
},light:{
brand:"#2ecc71",fontUi:"系统字体",fontSize:14,density:"舒适",bg:"#f5f5f5",panel:"#ffffff",text:"#222222",textMuted:"#666666",textSubtle:"#111827",line:"#e8e8e8",inputBg:"#f8f8f8",postHover:"#f0f3f6",divider:"#ededed",pinned:"#d58932",danger:"#d94b4b",bodyLink:"#111827",lineHeight:170,quoteBorder:"#86e1ad",inlineCodeBg:"#eef1f4",codeBg:"#0f172a",codeText:"#ffffff",fontMono:"系统等宽",fontSizeSm:13,codeFontSize:12,radius:6,radiusSm:4,shadow:36
}};
var normalizeConfig=function(raw,mode){var fallback=defaults[mode];
var result={};
fieldKeys.forEach(function(key){var field=fields[key],value=raw&&Object.prototype.hasOwnProperty.call(raw,key)?raw[key]:fallback[key];if(field.type==="color")value=normalizeHex(value)||fallback[key];else if(field.type==="number"||field.type==="slider")value=clamp(parseInt(value,10)||field.options.min,field.options.min,field.options.max);else if(field.type==="select")value=field.options.options.indexOf(value)>=0?value:fallback[key];else value=value===true;result[key]=value;});
return result;
};
var derive=function(config){var value=clone(config),dark=isDark(value.panel);value.brandHover=mix(value.brand,dark?"#ffffff":"#000000",.16);value.brandText=solidText(value.brand);value.brandHoverText=solidText(value.brandHover);value.brandSoft=mix(value.panel,value.brand,dark?.16:.10);value.focusRing=mix(value.panel,value.brand,dark?.42:.36);value.inputCaret=readableColor(value.brand,value.inputBg);value.text=readableColor(value.text,value.panel);value.textMuted=readableColor(value.textMuted,value.panel);value.textSubtle=readableColor(value.textSubtle,value.panel);value.bodyLink=readableColor(value.bodyLink,value.panel);value.bodyText=mix(value.text,value.panel,.06);value.quoteText=value.textMuted;value.postMeta=value.textSubtle;value.postTitle=mix(value.text,value.panel,.04);value.avatar=mix(value.panel,value.brand,dark?.18:.12);value.forumBadge=mix(value.panel,value.text,.07);value.navBg=value.panel;value.inlineCodeText=readableColor(value.text,value.inlineCodeBg);value.codeText=readableColor(value.codeText,value.codeBg);value.codeBorder=mix(value.codeBg,value.line,dark?.46:.34);value.pinnedSoft=mix(value.panel,value.pinned,dark?.20:.13);value.pinnedText=readableSmallColor(value.pinned,value.pinnedSoft);value.dangerSoft=mix(value.panel,value.danger,dark?.18:.12);value.dangerText=readableSmallColor(value.danger,value.dangerSoft);value.dangerSolidText=solidText(value.danger);return value;};
var presets=[
{id:"default",name:"论坛默认",light:normalizeConfig(defaults.light,"light"),dark:normalizeConfig(defaults.dark,"dark")},
{id:"slate",name:"石板灰",light:normalizeConfig({brand:"#516185",fontUi:"系统字体",fontSize:14,density:"舒适",bg:"#f4f5f7",panel:"#ffffff",text:"#1f262e",textMuted:"#68717d",textSubtle:"#111827",line:"#dfe3e8",inputBg:"#f7f8f9",postHover:"#eef1f5",divider:"#edeeee",pinned:"#b86b22",danger:"#c94f58",bodyLink:"#495778",lineHeight:170,quoteBorder:"#9aa3b8",inlineCodeBg:"#e9edf2",codeBg:"#17202b",codeText:"#edf2f7",fontMono:"系统等宽",fontSizeSm:13,codeFontSize:12,radius:6,radiusSm:4,shadow:38},"light"),dark:normalizeConfig({brand:"#7b90b8",fontUi:"系统字体",fontSize:14,density:"舒适",bg:"#14171c",panel:"#1c2027",text:"#e2e7ee",textMuted:"#9ba6b3",textSubtle:"#69717a",line:"#303741",inputBg:"#181b21",postHover:"#252b34",divider:"#2c3037",pinned:"#d08a43",danger:"#d66770",bodyLink:"#93a4c5",lineHeight:175,quoteBorder:"#53617b",inlineCodeBg:"#282f39",codeBg:"#0c1118",codeText:"#e6edf6",fontMono:"系统等宽",fontSizeSm:13,codeFontSize:12,radius:6,radiusSm:4,shadow:54},"dark")},
{id:"paper",name:"暖调纸张",light:normalizeConfig({brand:"#4a7c59",fontUi:"LXGW WenKai",fontSize:15,density:"宽松",bg:"#faf7ef",panel:"#fffdf8",text:"#2c3a32",textMuted:"#746f63",textSubtle:"#111827",line:"#e5dfd1",inputBg:"#fbf9f1",postHover:"#f4efe4",divider:"#eeede8",pinned:"#b36e32",danger:"#b9574f",bodyLink:"#437050",lineHeight:180,quoteBorder:"#96b29c",inlineCodeBg:"#eee9dd",codeBg:"#2d332d",codeText:"#f5f0e7",fontMono:"系统等宽",fontSizeSm:13,codeFontSize:12,radius:8,radiusSm:5,shadow:28},"light"),dark:normalizeConfig({brand:"#7ba98a",fontUi:"LXGW WenKai",fontSize:15,density:"宽松",bg:"#1b1a17",panel:"#232120",text:"#e8e2d6",textMuted:"#aaa294",textSubtle:"#746e65",line:"#3a3630",inputBg:"#1f1d1b",postHover:"#2d2a26",divider:"#33302f",pinned:"#ca8a50",danger:"#d07167",bodyLink:"#93b89f",lineHeight:180,quoteBorder:"#56705d",inlineCodeBg:"#302d28",codeBg:"#111411",codeText:"#eee8db",fontMono:"系统等宽",fontSizeSm:13,codeFontSize:12,radius:8,radiusSm:5,shadow:46},"dark")},
{id:"blackout",name:"Blackout",light:normalizeConfig(defaults.light,"light"),dark:normalizeConfig({brand:"#ff9100",fontUi:"系统字体",fontSize:15,density:"紧凑",bg:"#1e1e1e",panel:"#2d2d2d",text:"#c6c5b8",textMuted:"#b7b7b7",textSubtle:"#8c8e92",line:"#383838",inputBg:"#292929",postHover:"#404040",divider:"#383838",pinned:"#ff9100",danger:"#e81123",bodyLink:"#ff9100",lineHeight:170,quoteBorder:"#ff9100",inlineCodeBg:"#383838",codeBg:"#292929",codeText:"#c6c5b8",fontMono:"系统等宽",fontSizeSm:12,codeFontSize:15,radius:8,radiusSm:4,shadow:24},"dark")},
{id:"typora_docsify",name:"Typora Docsify",light:normalizeConfig({brand:"#2244dd",fontUi:"Poppins",fontSize:15,density:"舒适",bg:"#f5f5f5",panel:"#ffffff",text:"#333333",textMuted:"#666666",textSubtle:"#808080",line:"#d9d9df",inputBg:"#f7f7f7",postHover:"#f0f1ff",divider:"#e5e5e8",pinned:"#ff3366",danger:"#ff3300",bodyLink:"#9933ff",lineHeight:175,quoteBorder:"#2244dd",inlineCodeBg:"#eeeeee",codeBg:"#333333",codeText:"#ffffff",fontMono:"Fira Code",fontSizeSm:12,codeFontSize:13,radius:8,radiusSm:4,shadow:16},"light"),dark:normalizeConfig({brand:"#7384ff",fontUi:"Poppins",fontSize:15,density:"舒适",bg:"#15161c",panel:"#20222a",text:"#e6e7eb",textMuted:"#b5b7c0",textSubtle:"#858894",line:"#373a45",inputBg:"#191b22",postHover:"#292c36",divider:"#30333d",pinned:"#ff6699",danger:"#ff664d",bodyLink:"#c08cff",lineHeight:175,quoteBorder:"#7384ff",inlineCodeBg:"#2c2f3a",codeBg:"#0f1015",codeText:"#f5f5f7",fontMono:"Fira Code",fontSizeSm:12,codeFontSize:13,radius:8,radiusSm:4,shadow:28},"dark")},
{id:"mist_blue",name:"Mist Blue",light:normalizeConfig({brand:"#4c6f91",fontUi:"LXGW WenKai",fontSize:16,density:"舒适",bg:"#d9dee8",panel:"#f1f4f8",text:"#263241",textMuted:"#647084",textSubtle:"#8793a7",line:"#b7becb",inputBg:"#eef2f8",postHover:"#c9d5e2",divider:"#c5ccd7",pinned:"#4c6f91",danger:"#9a5a36",bodyLink:"#2f5e88",lineHeight:178,quoteBorder:"#4c6f91",inlineCodeBg:"#e2e8f1",codeBg:"#e0e6ef",codeText:"#253244",fontMono:"Fira Code",fontSizeSm:12,codeFontSize:14,radius:8,radiusSm:5,shadow:18},"light"),dark:normalizeConfig({brand:"#8fb5d9",fontUi:"LXGW WenKai",fontSize:16,density:"舒适",bg:"#18222d",panel:"#222f3c",text:"#dce5ee",textMuted:"#aab8c7",textSubtle:"#7f90a3",line:"#3b4b5c",inputBg:"#1d2935",postHover:"#2b3d4d",divider:"#334454",pinned:"#8fb5d9",danger:"#d99a73",bodyLink:"#a9cbed",lineHeight:178,quoteBorder:"#739bbf",inlineCodeBg:"#2d3c4b",codeBg:"#151e27",codeText:"#d9e4ef",fontMono:"Fira Code",fontSizeSm:12,codeFontSize:14,radius:8,radiusSm:5,shadow:28},"dark")}
];
var state={schema_version:schemaVersion,persisted:initial.persisted===true,needs_migration:initial.needs_migration===true,mode:initial.mode==="light"?"light":"dark",selected_scheme_id:String(initial.selected_scheme_id||""),light:normalizeConfig(initial.light,"light"),dark:normalizeConfig(initial.dark,"dark"),light_custom_font:initial.light_custom_font===true,dark_custom_font:initial.dark_custom_font===true,schemes:Array.isArray(initial.schemes)?initial.schemes.map(function(scheme){return{id:String(scheme.id||""),name:String(scheme.name||""),light:normalizeConfig(scheme.light,"light"),dark:normalizeConfig(scheme.dark,"dark"),light_custom_font:scheme.light_custom_font===true,dark_custom_font:scheme.dark_custom_font===true};}):[],font:initial.font&&typeof initial.font==="object"?initial.font:null,font_upload_url:String(initial.font_upload_url||""),font_delete_url:String(initial.font_delete_url||""),max_font_bytes:parseInt(initial.max_font_bytes,10)||2097152,max_schemes:parseInt(initial.max_schemes,10)||20,level:"basic",open:{},scheme_editor:null,submitting:false,font_busy:false,imported_name:""};
var serializeConfig=function(config){var output={};fieldKeys.forEach(function(key){output[key]=config[key];});return output;};
var serializable=function(){return{schema_version:schemaVersion,mode:state.mode,selected_scheme_id:state.selected_scheme_id,light:serializeConfig(state.light),dark:serializeConfig(state.dark),light_custom_font:state.light_custom_font,dark_custom_font:state.dark_custom_font,schemes:state.schemes.map(function(scheme){return{id:scheme.id,name:scheme.name,light:serializeConfig(scheme.light),dark:serializeConfig(scheme.dark),light_custom_font:scheme.light_custom_font,dark_custom_font:scheme.dark_custom_font};})};};
var saved=JSON.stringify(serializable()),savedPersisted=state.persisted,savedNeedsMigration=state.needs_migration;
var byId=function(id){return document.getElementById(id);};
var element=function(tag,className,text){var node=document.createElement(tag);if(className)node.className=className;if(text!==undefined)node.textContent=text;return node;};
var toast=function(message){var node=byId("appearance-theme-toast");node.textContent=message;node.classList.add("appearance-theme-is-visible");clearTimeout(toast.timer);toast.timer=setTimeout(function(){node.classList.remove("appearance-theme-is-visible");},1900);};
var customFontFace=null;
var fontFace=function(url){if(!url||typeof FontFace!=="function")return Promise.reject(new Error("当前浏览器不支持字体预览"));return new FontFace("AppearanceThemeCustomUI","url("+JSON.stringify(url)+") format(\"woff2\")").load();};
var loadCustomFont=function(url){return fontFace(url).then(function(face){if(customFontFace)document.fonts.delete(customFontFace);document.fonts.add(face);customFontFace=face;return face;});};
var formData=function(){var data=new FormData(),token=byId("appearance-theme-form").querySelector('input[name="_csrf"]');if(token)data.append("_csrf",token.value);return data;};
var requestJson=function(url,data){return fetch(url,{method:"POST",body:data,credentials:"same-origin",headers:{"X-Requested-With":"XMLHttpRequest"}}).then(function(response){return response.json().catch(function(){throw new Error("服务器响应无效");}).then(function(payload){if(!response.ok||!payload||payload.ok!==1)throw new Error(payload&&payload.message?payload.message:"操作失败");return payload;});});};
var current=function(){return state[state.mode];};
var customFontKey=function(mode){return mode+"_custom_font";};
var currentUsesCustomFont=function(){return state[customFontKey(state.mode)]===true&&!!state.font;};
var findScheme=function(id){var match=presets.find(function(scheme){return scheme.id===id;});return match||state.schemes.find(function(scheme){return scheme.id===id;})||null;};
var schemeIsMine=function(id){return state.schemes.some(function(scheme){return scheme.id===id;});};
var renderModes=function(){root.querySelectorAll("[data-appearance-theme-mode]").forEach(function(button){var active=button.getAttribute("data-appearance-theme-mode")===state.mode;button.classList.toggle("appearance-theme-is-active",active);button.setAttribute("aria-pressed",String(active));});};
var schemeSwatches=function(palette){var swatches=element("span","appearance-theme-scheme-swatches"),accent=element("span","appearance-theme-scheme-accent"),page=element("span","appearance-theme-scheme-page"),line1=element("span","appearance-theme-scheme-line"),line2=element("span","appearance-theme-scheme-line");swatches.setAttribute("aria-hidden","true");swatches.style.setProperty("--appearance-theme-scheme-status",palette.pinned);accent.style.background=palette.brand;page.style.background=palette.bg;page.style.boxShadow="inset 0 0 0 7px "+palette.panel;page.style.borderRadius="0 6px 0 0";line1.style.background=mix(palette.panel,palette.text,.30);line2.style.background=mix(palette.panel,palette.text,.16);page.append(line1,line2);swatches.append(accent,page);return swatches;};
var schemeCard=function(scheme,mine){var active=state.selected_scheme_id===scheme.id,button=element("button","appearance-theme-scheme"+(active?" appearance-theme-is-active":""));button.type="button";button.setAttribute("data-appearance-theme-scheme",scheme.id);button.setAttribute("aria-label",(mine?"我的配色":"内置配色")+"："+scheme.name+(active?"，当前选中":""));button.setAttribute("aria-pressed",String(active));var name=element("span","appearance-theme-scheme-name"),title=element("span","appearance-theme-scheme-title",scheme.name);name.appendChild(title);if(active)name.appendChild(element("em","appearance-theme-scheme-current","使用中"));else name.appendChild(element("i","appearance-theme-scheme-tag"+(mine?" appearance-theme-scheme-tag-mine":""),mine?"我的":"预设"));button.append(schemeSwatches(scheme[state.mode]),name);return button;};
var schemeEditorInput=function(type){var input=element("input","appearance-theme-scheme-name-input");input.type="text";input.maxLength=20;input.value=state.scheme_editor.name;input.autocomplete="off";input.setAttribute("data-appearance-theme-scheme-name","");input.setAttribute("aria-label",type==="new"?"新配色名称":"配色新名称");return input;};
var schemeEditorControls=function(type){var controls=element("span","appearance-theme-scheme-editor-controls"),confirm=element("button","appearance-theme-scheme-action",type==="new"?"加入草稿":"确认重命名"),cancel=element("button","appearance-theme-scheme-action","取消");confirm.type="button";cancel.type="button";confirm.setAttribute("data-appearance-theme-action","confirm-scheme-editor");cancel.setAttribute("data-appearance-theme-action","cancel-scheme-editor");confirm.setAttribute("aria-label",type==="new"?"将当前浅色和深色设置加入草稿；同步到账号仍需点击页面底部保存":"确认重命名配色；同步到账号仍需点击页面底部保存");cancel.setAttribute("aria-label",type==="new"?"取消另存为新配色":"取消重命名配色");controls.append(confirm,cancel);return controls;};
var newSchemeEditorCard=function(){var editor=element("div","appearance-theme-scheme-editor-card"),body=element("span","appearance-theme-scheme-editor-body");editor.setAttribute("role","group");editor.setAttribute("aria-label","另存为新配色草稿");editor.setAttribute("data-appearance-theme-scheme-editor","new");body.append(schemeEditorInput("new"),schemeEditorControls("new"));editor.append(schemeSwatches(current()),body);return editor;};
var renameSchemeEditor=function(){var editor=element("div","appearance-theme-scheme-editor");editor.setAttribute("role","group");editor.setAttribute("aria-label","重命名当前配色");editor.setAttribute("data-appearance-theme-scheme-editor","rename");editor.append(schemeEditorInput("rename"),schemeEditorControls("rename"));return editor;};
var inlineSchemeActions=function(scheme){var actions=element("div","appearance-theme-scheme-actions"),rename=element("button","appearance-theme-scheme-action","重命名"),remove=element("button","appearance-theme-scheme-action appearance-theme-scheme-action-delete","删除");rename.type="button";remove.type="button";rename.setAttribute("data-appearance-theme-action","rename-scheme");remove.setAttribute("data-appearance-theme-action","delete-scheme");rename.setAttribute("aria-label","重命名配色 "+scheme.name);remove.setAttribute("aria-label","删除配色 "+scheme.name);actions.append(rename,remove);return actions;};
var renderSchemes=function(){var container=byId("appearance-theme-schemes");container.replaceChildren();var presetGrid=element("div","appearance-theme-scheme-grid appearance-theme-preset-grid");presetGrid.setAttribute("role","group");presetGrid.setAttribute("aria-label","内置配色");presets.forEach(function(scheme){presetGrid.appendChild(schemeCard(scheme,false));});container.appendChild(presetGrid);container.appendChild(element("div","appearance-theme-scheme-heading","我的配色"));var mineGrid=element("div","appearance-theme-scheme-grid");mineGrid.setAttribute("role","group");mineGrid.setAttribute("aria-label","我的配色");var editor=state.scheme_editor;state.schemes.forEach(function(scheme){var cell=element("div","appearance-theme-scheme-cell");cell.appendChild(schemeCard(scheme,true));if(editor&&editor.type==="rename"&&editor.scheme_id===scheme.id)cell.appendChild(renameSchemeEditor());else if(!editor&&scheme.id===state.selected_scheme_id)cell.appendChild(inlineSchemeActions(scheme));mineGrid.appendChild(cell);});if(editor&&editor.type==="new")mineGrid.appendChild(newSchemeEditorCard());else if(!editor){var add=element("button","appearance-theme-scheme appearance-theme-scheme-add");add.type="button";add.setAttribute("data-appearance-theme-scheme","__save");add.setAttribute("aria-label",state.schemes.length>=state.max_schemes?"我的配色已满":"将当前设置另存为新配色");var swatches=element("span","appearance-theme-scheme-swatches","＋"),name=element("span","appearance-theme-scheme-name"),title=element("span","appearance-theme-scheme-title",state.schemes.length>=state.max_schemes?"我的配色已满":"另存为新配色");swatches.setAttribute("aria-hidden","true");name.appendChild(title);add.append(swatches,name);if(state.schemes.length>=state.max_schemes)add.disabled=true;mineGrid.appendChild(add);}container.appendChild(mineGrid);if(state.scheme_editor&&state.scheme_editor.focus_pending){state.scheme_editor.focus_pending=false;setTimeout(function(){var input=root.querySelector("[data-appearance-theme-scheme-name]");if(input){input.focus();input.select();}},0);}};
var control=function(field,value,labelId){var wrapper=element("span","appearance-theme-control"),baseId="appearance-theme-"+state.mode+"-"+field.key;if(field.type==="color"){var color=element("input"),hex=element("input");color.type="color";color.id=baseId+"-color";color.value=value;color.setAttribute("aria-labelledby",labelId);color.setAttribute("aria-label",field.label+"颜色选择器");color.setAttribute("data-appearance-theme-control","color");hex.type="text";hex.id=baseId+"-hex";hex.value=value;hex.maxLength=7;hex.spellcheck=false;hex.setAttribute("aria-labelledby",labelId);hex.setAttribute("aria-label",field.label+"十六进制颜色");hex.setAttribute("data-appearance-theme-control","hex");wrapper.append(color,hex);}else if(field.type==="select"){var select=element("select");select.id=baseId;select.setAttribute("aria-labelledby",labelId);select.setAttribute("data-appearance-theme-control","select");field.options.options.forEach(function(option){var node=element("option","",option);node.value=option;node.selected=option===value;select.appendChild(node);});if(field.key==="fontUi"){wrapper.classList.add("appearance-theme-font-control");if(state.font){var mine=element("option","","我的字体 · "+state.font.name);mine.value="__custom__";mine.selected=currentUsesCustomFont();select.appendChild(mine);}var upload=element("button","appearance-theme-font-action",state.font?"更换字体":"上传字体");upload.type="button";upload.disabled=state.font_busy;upload.setAttribute("data-appearance-theme-action","font-upload");wrapper.append(select,upload);if(state.font){var remove=element("button","appearance-theme-font-action appearance-theme-font-remove","移除");remove.type="button";remove.disabled=state.font_busy;remove.setAttribute("data-appearance-theme-action","font-delete");wrapper.appendChild(remove);}}else wrapper.appendChild(select);}else if(field.type==="number"){var number=element("input"),unit=element("span","appearance-theme-control-unit",field.options.unit||"");number.type="number";number.id=baseId;number.value=String(value);number.min=String(field.options.min);number.max=String(field.options.max);number.setAttribute("aria-labelledby",labelId);number.setAttribute("data-appearance-theme-control","number");wrapper.append(number,unit);}else if(field.type==="slider"){var range=element("input"),output=element("span","appearance-theme-control-output",String(value));range.type="range";range.id=baseId;range.min=String(field.options.min);range.max=String(field.options.max);range.value=String(value);range.setAttribute("aria-labelledby",labelId);range.setAttribute("data-appearance-theme-control","slider");wrapper.append(range,output);}else{var label=element("span","appearance-theme-toggle-label",value?"开启":"关闭"),toggle=element("button","appearance-theme-toggle"+(value?" appearance-theme-is-active":""));toggle.type="button";toggle.id=baseId;toggle.setAttribute("data-appearance-theme-control","toggle");toggle.setAttribute("aria-labelledby",labelId);toggle.setAttribute("aria-pressed",String(value));toggle.appendChild(element("i"));wrapper.append(label,toggle);}return wrapper;};
var fieldRow=function(field,advanced){var row=element("div","appearance-theme-field"),labelId="appearance-theme-label-"+state.mode+"-"+field.key;row.setAttribute("data-appearance-theme-key",field.key);var label=element("span","appearance-theme-field-label"),main=element(advanced?"span":"strong","",field.label);main.id=labelId;label.appendChild(main);if(field.description)label.appendChild(element("small","",field.description));row.append(label,control(field,current()[field.key],labelId));return row;};
var renderBasics=function(){var container=byId("appearance-theme-basics");container.replaceChildren();basic.forEach(function(item){container.appendChild(fieldRow(fields[item[0]],false));});};
var renderAdvanced=function(){var container=byId("appearance-theme-advanced");container.replaceChildren();groups.forEach(function(group){var open=state.open[group[0]]===true,wrapper=element("div","appearance-theme-advanced-group"+(open?" appearance-theme-is-open":"")),titleId="appearance-theme-group-title-"+group[0],bodyId="appearance-theme-group-body-"+group[0];wrapper.setAttribute("data-appearance-theme-group",group[0]);var title=element("button","appearance-theme-advanced-title");title.type="button";title.id=titleId;title.setAttribute("data-appearance-theme-action","group");title.setAttribute("aria-expanded",String(open));title.setAttribute("aria-controls",bodyId);title.append(document.createTextNode(group[1]),element("span","appearance-theme-advanced-count",group[2].length+" 项"),element("span","appearance-theme-advanced-chevron","▶"));var body=element("div","appearance-theme-advanced-body");body.id=bodyId;body.hidden=!open;body.setAttribute("aria-labelledby",titleId);group[2].forEach(function(item){body.appendChild(fieldRow(fields[item[0]],true));});wrapper.append(title,body);container.appendChild(wrapper);});};
var renderLevel=function(){root.querySelectorAll("[data-appearance-theme-level]").forEach(function(button){var active=button.getAttribute("data-appearance-theme-level")===state.level;button.classList.toggle("appearance-theme-is-active",active);button.setAttribute("aria-selected",String(active));button.tabIndex=active?0:-1;});var advanced=state.level==="advanced";byId("appearance-theme-basics").hidden=advanced;byId("appearance-theme-advanced").hidden=!advanced;};
var previewFont=function(font,mono){if(mono){if(font==="系统等宽")return"ui-monospace,monospace";return"\""+font+"\",monospace";}var fallback=font==="系统字体"?"system-ui,sans-serif":font==="Poppins"?"\"AppearanceThemePoppins\",\"Poppins\",\"Source Han Sans HC\",\"PingFang SC\",\"Noto Sans CJK SC\",\"Microsoft YaHei\",system-ui,sans-serif":"\""+font+"\",system-ui,sans-serif";return currentUsesCustomFont()?"\"AppearanceThemeCustomUI\","+fallback:fallback;};
var renderPreview=function(){var config=derive(current()),preview=byId("appearance-theme-preview"),keys=["brand","brandText","bg","panel","text","textMuted","textSubtle","bodyText","bodyLink","quoteText","quoteBorder","inputBg","inlineCodeBg","inlineCodeText","line","navBg","postHover","divider","postTitle","postMeta","avatar","forumBadge","pinnedText","pinnedSoft","codeBg","codeText","codeBorder"];keys.forEach(function(key){var name=key==="pinnedText"?"pinned":key;preview.style.setProperty("--appearance-theme-preview-"+name.replace(/[A-Z]/g,function(letter){return"-"+letter.toLowerCase();}),config[key]);});preview.style.setProperty("--appearance-theme-preview-radius",config.radius+"px");preview.style.setProperty("--appearance-theme-preview-radius-sm",config.radiusSm+"px");preview.style.setProperty("--appearance-theme-preview-font-ui",previewFont(config.fontUi,false));preview.style.setProperty("--appearance-theme-preview-font-mono",previewFont(config.fontMono,true));preview.style.setProperty("--appearance-theme-preview-line-height",(config.lineHeight/100).toFixed(2));preview.style.setProperty("--appearance-theme-preview-code-font-size",config.codeFontSize+"px");preview.style.boxShadow=config.shadow===0?"none":"0 "+Math.round(3+config.shadow*.1)+"px "+Math.round(8+config.shadow*.2)+"px rgba(16,24,40,"+(0.025+config.shadow*.0014).toFixed(3)+")";byId("appearance-theme-preview-body").style.setProperty("--appearance-theme-preview-padding",config.density==="紧凑"?"7px":config.density==="宽松"?"13px":"10px");};
var isDirty=function(){return!state.persisted||state.needs_migration||JSON.stringify(serializable())!==saved;};
var updateFooter=function(){byId("appearance-theme-save").disabled=!isDirty();};
var renderAll=function(){renderModes();renderSchemes();renderBasics();renderAdvanced();renderLevel();renderPreview();updateFooter();};
var manualChange=function(key,value){state[state.mode][key]=value;if(state.selected_scheme_id!==""){state.selected_scheme_id="";renderSchemes();}renderPreview();updateFooter();};
var applyPreset=function(scheme){state.light=clone(scheme.light);state.dark=clone(scheme.dark);state.light_custom_font=false;state.dark_custom_font=false;};
var applyScheme=function(scheme){if(schemeIsMine(scheme.id)){state.light=clone(scheme.light);state.dark=clone(scheme.dark);state.light_custom_font=scheme.light_custom_font===true&&!!state.font;state.dark_custom_font=scheme.dark_custom_font===true&&!!state.font;}else applyPreset(scheme);state.selected_scheme_id=scheme.id;renderAll();toast("已切换到「"+scheme.name+"」");};
var randomSchemeId=function(){var bytes=new Uint8Array(12);if(window.crypto&&window.crypto.getRandomValues)window.crypto.getRandomValues(bytes);else for(var i=0;i<bytes.length;i++)bytes[i]=Math.floor(Math.random()*256);return"mine_"+Array.from(bytes).map(function(value){return value.toString(16).padStart(2,"0");}).join("");};
var newSchemeId=function(){var id;do{id=randomSchemeId();}while(findScheme(id));return id;};
var validSchemeName=function(raw){if(typeof raw!=="string")return null;var name=raw.trim();if(!name||Array.from(name).length>20||/[\u0000-\u001f\u007f]/.test(name))return null;return name;};
var suggestedSchemeName=function(){var names=state.schemes.map(function(scheme){return String(scheme.name).trim();}),number=1;while(names.indexOf("我的配色 "+number)>=0)number++;return"我的配色 "+number;};
var focusSchemeEditor=function(){var input=root.querySelector("[data-appearance-theme-scheme-name]");if(input)input.focus();};
var focusSchemeCard=function(id){setTimeout(function(){var card=Array.from(root.querySelectorAll("[data-appearance-theme-scheme]")).find(function(item){return item.getAttribute("data-appearance-theme-scheme")===id;});if(card)card.focus();},0);};
var blockSchemeEditor=function(){toast("请先确认或取消当前配色名称");setTimeout(focusSchemeEditor,0);};
var openSchemeEditor=function(type){if(state.scheme_editor){blockSchemeEditor();return;}if(type==="new"){if(state.schemes.length>=state.max_schemes){toast("我的配色已满");return;}state.scheme_editor={type:"new",name:suggestedSchemeName(),original_name:"",focus_pending:true};}else{var scheme=state.schemes.find(function(item){return item.id===state.selected_scheme_id;});if(!scheme)return;state.scheme_editor={type:"rename",scheme_id:scheme.id,name:scheme.name,original_name:scheme.name,focus_pending:true};}renderSchemes();};
var confirmSchemeEditor=function(){if(!state.scheme_editor)return;var editor=state.scheme_editor,name=validSchemeName(editor.name);if(!name){toast("名称需为 1 至 20 个字符，且不能含控制字符");focusSchemeEditor();return;}if(editor.type==="new"){if(state.schemes.length>=state.max_schemes){toast("我的配色已满，请取消当前编辑");focusSchemeEditor();return;}var scheme={id:newSchemeId(),name:name,light:clone(state.light),dark:clone(state.dark),light_custom_font:state.light_custom_font,dark_custom_font:state.dark_custom_font};state.schemes.push(scheme);state.selected_scheme_id=scheme.id;state.scheme_editor=null;renderSchemes();updateFooter();toast("已加入草稿「"+name+"」，仍需点击页面底部“保存”同步到账号");focusSchemeCard(scheme.id);return;}var scheme=state.schemes.find(function(item){return item.id===editor.scheme_id;});if(!scheme){state.scheme_editor=null;renderSchemes();focusSchemeCard(editor.scheme_id);return;}if(name===validSchemeName(editor.original_name)){state.scheme_editor=null;renderSchemes();focusSchemeCard(scheme.id);return;}scheme.name=name;state.scheme_editor=null;renderSchemes();updateFooter();toast("已重命名，仍需点击页面底部“保存”同步到账号");focusSchemeCard(scheme.id);};
var cancelSchemeEditor=function(){if(!state.scheme_editor)return;var editor=state.scheme_editor;state.scheme_editor=null;renderSchemes();focusSchemeCard(editor.type==="new"?"__save":editor.scheme_id);};
var deleteSelectedScheme=function(){var index=state.schemes.findIndex(function(item){return item.id===state.selected_scheme_id;});if(index<0)return;var scheme=state.schemes[index];if(!window.confirm("删除配色「"+scheme.name+"」？\n删除将在点保存后生效。"))return;state.schemes.splice(index,1);state.selected_scheme_id="";renderSchemes();updateFooter();toast("已暂存删除，记得点保存");};
var clearCustomFontRefs=function(){state.light_custom_font=false;state.dark_custom_font=false;state.schemes.forEach(function(scheme){scheme.light_custom_font=false;scheme.dark_custom_font=false;});};
var clearSavedCustomFontRefs=function(){var baseline=JSON.parse(saved);baseline.light_custom_font=false;baseline.dark_custom_font=false;baseline.schemes.forEach(function(scheme){scheme.light_custom_font=false;scheme.dark_custom_font=false;});saved=JSON.stringify(baseline);};
var uploadFont=function(file){if(!file)return;if(!/\.woff2$/i.test(file.name||"")||file.size<48||file.size>state.max_font_bytes){toast("仅支持不超过 2 MiB 的 WOFF2 字体");return;}var localUrl=URL.createObjectURL(file);state.font_busy=true;renderBasics();fontFace(localUrl).catch(function(){throw new Error("字体文件无法解析");}).then(function(){URL.revokeObjectURL(localUrl);var data=formData();data.append("appearance_theme_font",file,file.name);return requestJson(state.font_upload_url,data);}).then(function(payload){state.font=payload.font;state[customFontKey(state.mode)]=true;state.selected_scheme_id="";return loadCustomFont(state.font.url);}).then(function(){renderAll();toast("字体已上传并用于当前模式，点击保存同步启用状态");}).catch(function(error){URL.revokeObjectURL(localUrl);toast(error&&error.message?error.message:"字体上传失败");}).finally(function(){state.font_busy=false;renderBasics();});};
var deleteFont=function(){if(!state.font||state.font_busy)return;if(!window.confirm("移除账号字体？\n已保存配色中的自定义字体引用也会立即清除。"))return;state.font_busy=true;renderBasics();requestJson(state.font_delete_url,formData()).then(function(){if(customFontFace)document.fonts.delete(customFontFace);customFontFace=null;state.font=null;clearCustomFontRefs();clearSavedCustomFontRefs();renderAll();toast("已移除自定义字体");}).catch(function(error){toast(error&&error.message?error.message:"移除字体失败");}).finally(function(){state.font_busy=false;renderBasics();});};
var exportTheme=function(){var selected=findScheme(state.selected_scheme_id),payload={schema_version:schemaVersion,name:selected?selected.name:(state.imported_name||"我的配色"),light:serializeConfig(state.light),dark:serializeConfig(state.dark)},blob=new Blob([JSON.stringify(payload,null,2)+"\n"],{type:"application/json;charset=utf-8"}),url=URL.createObjectURL(blob),link=document.createElement("a");link.href=url;link.download="appearance-theme.json";document.body.appendChild(link);link.click();link.remove();setTimeout(function(){URL.revokeObjectURL(url);},0);toast("已导出 appearance-theme.json");};
var strictImportedConfig=function(raw,mode){if(!raw||Array.isArray(raw)||typeof raw!=="object")throw new Error("配色内容无效");var keys=Object.keys(raw);if(keys.length!==fieldKeys.length||keys.some(function(key){return!Object.prototype.hasOwnProperty.call(fields,key);})||fieldKeys.some(function(key){return!Object.prototype.hasOwnProperty.call(raw,key);}))throw new Error("配色字段不完整");fieldKeys.forEach(function(key){var field=fields[key],value=raw[key];if(field.type==="color"&&(typeof value!=="string"||!normalizeHex(value)))throw new Error("颜色值无效");if((field.type==="number"||field.type==="slider")&&(!Number.isInteger(value)||value<field.options.min||value>field.options.max))throw new Error("配色数值无效");if(field.type==="select"&&(typeof value!=="string"||field.options.options.indexOf(value)<0))throw new Error("配色选项无效");});return normalizeConfig(raw,mode);};
var v1Fields={brand:["color"],fontUi:["select",v1UiFonts],fontSize:["number",12,20],density:["select",["紧凑","舒适","宽松"]],bg:["color"],panel:["color"],text:["color"],textMuted:["color"],line:["color"],postHover:["color"],fontMono:["select",monoFonts],fontSizeSm:["number",10,16],lineHeight:["number",130,210],codeFontSize:["number",10,18],radius:["number",0,20],radiusSm:["number",0,14],shadow:["number",0,100],sideTranslucent:["boolean"],codeBg:["color"],codeText:["color"],codeBorder:["color"],inlineCodeBg:["color"],success:["color"],danger:["color"],warning:["color"],pinnedBadge:["color"],transition:["select",["关闭","快速","标准","平缓"]],backdrop:["number",0,90]};
var strictV1ImportedConfig=function(raw,mode){if(!raw||Array.isArray(raw)||typeof raw!=="object")throw new Error("配色内容无效");var oldKeys=Object.keys(v1Fields),keys=Object.keys(raw);if(keys.length!==oldKeys.length||keys.some(function(key){return!Object.prototype.hasOwnProperty.call(v1Fields,key);})||oldKeys.some(function(key){return!Object.prototype.hasOwnProperty.call(raw,key);}))throw new Error("配色字段不完整");oldKeys.forEach(function(key){var rule=v1Fields[key],value=raw[key];if(rule[0]==="color"&&(typeof value!=="string"||!normalizeHex(value)))throw new Error("颜色值无效");if(rule[0]==="number"&&(!Number.isInteger(value)||value<rule[1]||value>rule[2]))throw new Error("配色数值无效");if(rule[0]==="select"&&(typeof value!=="string"||rule[1].indexOf(value)<0))throw new Error("配色选项无效");if(rule[0]==="boolean"&&typeof value!=="boolean")throw new Error("配色开关无效");});var dark=isDark(raw.panel),away=isDark(raw.bg)?"#000000":"#ffffff",text=readableColor(raw.text,raw.panel),muted=readableColor(raw.textMuted,raw.panel);return strictImportedConfig({brand:raw.brand,fontUi:raw.fontUi,fontSize:raw.fontSize,density:raw.density,bg:raw.bg,panel:raw.panel,text:raw.text,textMuted:raw.textMuted,textSubtle:readableColor(mix(muted,away,.32),raw.panel),line:raw.line,inputBg:mix(raw.panel,raw.bg,dark?.55:.75),postHover:raw.postHover,divider:mix(raw.panel,text,.08),pinned:raw.pinnedBadge,danger:raw.danger,bodyLink:readableColor(mix(raw.brand,dark?"#ffffff":"#000000",dark?.18:.10),raw.panel),lineHeight:raw.lineHeight,quoteBorder:mix(raw.brand,raw.panel,.42),inlineCodeBg:raw.inlineCodeBg,codeBg:raw.codeBg,codeText:raw.codeText,fontMono:raw.fontMono,fontSizeSm:raw.fontSizeSm,codeFontSize:raw.codeFontSize,radius:raw.radius,radiusSm:raw.radiusSm,shadow:raw.shadow},mode);};
var importTheme=function(file){if(!file)return;if(file.size>196608){toast("配色文件过大");return;}file.text().then(function(text){var payload=JSON.parse(text),allowed=["schema_version","name","light","dark"],keys=payload&&typeof payload==="object"&&!Array.isArray(payload)?Object.keys(payload):[];if(!payload||Array.isArray(payload)||typeof payload!=="object"||keys.length!==allowed.length||keys.some(function(key){return allowed.indexOf(key)<0;})||allowed.some(function(key){return!Object.prototype.hasOwnProperty.call(payload,key);}))throw new Error("配色文件无效");if(payload.schema_version!==1&&payload.schema_version!==schemaVersion)throw new Error("配色版本不受支持");if(typeof payload.name!=="string"||!payload.name.trim()||Array.from(payload.name.trim()).length>40||/[\u0000-\u001f\u007f]/.test(payload.name))throw new Error("配色名称无效");var adapter=payload.schema_version===1?strictV1ImportedConfig:strictImportedConfig,nextLight=adapter(payload.light,"light"),nextDark=adapter(payload.dark,"dark");state.light=nextLight;state.dark=nextDark;state.light_custom_font=false;state.dark_custom_font=false;state.selected_scheme_id="";state.imported_name=payload.name.trim();renderAll();toast("已导入，可预览后保存");}).catch(function(error){toast(error&&error.message?error.message:"配色文件无效");});};
root.addEventListener("click",function(event){var modeButton=event.target.closest("[data-appearance-theme-mode]");if(modeButton){state.mode=modeButton.getAttribute("data-appearance-theme-mode");renderAll();return;}var levelButton=event.target.closest("[data-appearance-theme-level]");if(levelButton){state.level=levelButton.getAttribute("data-appearance-theme-level");renderLevel();return;}var schemeButton=event.target.closest("[data-appearance-theme-scheme]");if(schemeButton){var id=schemeButton.getAttribute("data-appearance-theme-scheme");if(id==="__save")openSchemeEditor("new");else{var scheme=findScheme(id);if(scheme){if(state.scheme_editor){blockSchemeEditor();return;}applyScheme(scheme);}}return;}var actionButton=event.target.closest("[data-appearance-theme-action]");if(!actionButton)return;var action=actionButton.getAttribute("data-appearance-theme-action");if(action==="group"){var group=actionButton.closest("[data-appearance-theme-group]"),id=group.getAttribute("data-appearance-theme-group"),body=byId("appearance-theme-group-body-"+id);state.open[id]=!state.open[id];group.classList.toggle("appearance-theme-is-open",state.open[id]);actionButton.setAttribute("aria-expanded",String(state.open[id]));if(body)body.hidden=!state.open[id];}else if(action==="import")byId("appearance-theme-file").click();else if(action==="export")exportTheme();else if(action==="font-upload")byId("appearance-theme-font-file").click();else if(action==="font-delete")deleteFont();else if(action==="rename-scheme")openSchemeEditor("rename");else if(action==="confirm-scheme-editor")confirmSchemeEditor();else if(action==="cancel-scheme-editor")cancelSchemeEditor();else if(action==="delete-scheme")deleteSelectedScheme();else if(action==="undo"){var restored=JSON.parse(saved);state.persisted=savedPersisted;state.needs_migration=savedNeedsMigration;state.mode=restored.mode;state.selected_scheme_id=restored.selected_scheme_id;state.light=normalizeConfig(restored.light,"light");state.dark=normalizeConfig(restored.dark,"dark");state.light_custom_font=restored.light_custom_font===true&&!!state.font;state.dark_custom_font=restored.dark_custom_font===true&&!!state.font;state.schemes=restored.schemes.map(function(scheme){return{id:scheme.id,name:scheme.name,light:normalizeConfig(scheme.light,"light"),dark:normalizeConfig(scheme.dark,"dark"),light_custom_font:scheme.light_custom_font===true&&!!state.font,dark_custom_font:scheme.dark_custom_font===true&&!!state.font};});state.scheme_editor=null;state.imported_name="";renderAll();toast(state.persisted?"已放弃修改":"已恢复尚未保存的初始设置");}});
root.addEventListener("input",function(event){if(event.target.matches("[data-appearance-theme-scheme-name]")){if(state.scheme_editor)state.scheme_editor.name=event.target.value;return;}var row=event.target.closest("[data-appearance-theme-key]");if(!row)return;var key=row.getAttribute("data-appearance-theme-key"),role=event.target.getAttribute("data-appearance-theme-control"),field=fields[key];if(role==="color"){var value=event.target.value;row.querySelector('[data-appearance-theme-control="hex"]').value=value;manualChange(key,value);}else if(role==="hex"){var hex=normalizeHex(event.target.value.trim());if(hex){row.querySelector('[data-appearance-theme-control="color"]').value=hex;manualChange(key,hex);}}else if(role==="select"){if(key==="fontUi"&&event.target.value==="__custom__"){if(!state.font)return;state[customFontKey(state.mode)]=true;state.selected_scheme_id="";renderSchemes();renderPreview();updateFooter();}else{if(key==="fontUi")state[customFontKey(state.mode)]=false;manualChange(key,event.target.value);}}else if(role==="number")manualChange(key,clamp(parseInt(event.target.value,10)||field.options.min,field.options.min,field.options.max));else if(role==="slider"){var value=clamp(parseInt(event.target.value,10),field.options.min,field.options.max);row.querySelector(".appearance-theme-control-output").textContent=String(value);manualChange(key,value);}});
root.addEventListener("keydown",function(event){if(event.target.matches("[data-appearance-theme-scheme-name]")){if(event.key==="Enter"&&!event.isComposing){event.preventDefault();confirmSchemeEditor();}else if(event.key==="Escape"){event.preventDefault();cancelSchemeEditor();}return;}var tab=event.target.closest('[role="tab"][data-appearance-theme-level]');if(!tab||!["ArrowLeft","ArrowRight","Home","End"].includes(event.key))return;event.preventDefault();var tabs=Array.from(root.querySelectorAll('[role="tab"][data-appearance-theme-level]')),index=tabs.indexOf(tab),next=event.key==="Home"?0:event.key==="End"?tabs.length-1:(index+(event.key==="ArrowRight"?1:-1)+tabs.length)%tabs.length;tabs[next].focus();tabs[next].click();});
root.addEventListener("change",function(event){if(event.target.id==="appearance-theme-file"){importTheme(event.target.files&&event.target.files[0]);event.target.value="";}else if(event.target.id==="appearance-theme-font-file"){uploadFont(event.target.files&&event.target.files[0]);event.target.value="";}});
root.addEventListener("click",function(event){var toggle=event.target.closest('[data-appearance-theme-control="toggle"]');if(!toggle)return;var row=toggle.closest("[data-appearance-theme-key]"),value=!toggle.classList.contains("appearance-theme-is-active");toggle.classList.toggle("appearance-theme-is-active",value);toggle.setAttribute("aria-pressed",String(value));row.querySelector(".appearance-theme-toggle-label").textContent=value?"开启":"关闭";manualChange(row.getAttribute("data-appearance-theme-key"),value);});
byId("appearance-theme-form").addEventListener("submit",function(event){if(state.scheme_editor){event.preventDefault();blockSchemeEditor();return;}state.submitting=true;state.needs_migration=false;byId("appearance-theme-payload").value=JSON.stringify(serializable());});
window.addEventListener("beforeunload",function(event){if(state.submitting||(!state.scheme_editor&&!isDirty()))return;event.preventDefault();event.returnValue="";});
if(state.font&&(state.light_custom_font||state.dark_custom_font))loadCustomFont(state.font.url).then(renderPreview).catch(function(){});
renderAll();
})();
JS
    );
}

return [
  'id'=>'appearance_theme','name'=>'外观主题','version'=>'1.1.2',
  'description'=>'切换论坛主题外观：浅色/深色与内置与自定义配色，更原生的颜色。','author'=>'charcoal-fire-007',
  'assets'=>['css'=>'appearance_theme_css','js'=>'appearance_theme_js'],
  'hooks'=>['page.head'=>'appearance_theme_page_head','user.menu_links'=>'appearance_theme_menu_links'],
  'routes'=>[
      'appearance_theme'=>'appearance_theme_page','appearance_theme_font_upload'=>'appearance_theme_font_upload',
      'appearance_theme_font_delete'=>'appearance_theme_font_delete','appearance_theme_font'=>'appearance_theme_font',
  ],
  'install'=>'appearance_theme_install','uninstall'=>'appearance_theme_uninstall',
];
