<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <style>
    * {
      --top-bg-color: #0c70efaa;
      --top-inner-bg-color: #fffa;
      --top-title-color: #fff;
      --top-sub-color: #888;

      --arrow-time-color: #333;
      --split-line-color: #bbb;
      --departure-station-color: #fa8a12;
      --terminal-station-color: #53b13d;

      --bottom-bg-color: #fffa;
      --bottom-inner-bg-color: #fffa;
      --bottom-main-color: #000;
      --bottom-sub-color: #888;

      --station-table-head-bg-color: #e5eef8aa;
      --station-table-odd-bg-color: #fffc;
      --station-table-even-bg-color: #f5f5f5aa;
      --station-table-head-color: #002d7a;
      --station-table-line-color: #aaa;
      --station-table-arrived-line-color: #1b82ed;

      --box-shadow: 3px 3px 8px #0003;

      font-family: sans-serif;
    }

    .bg-wrapper {
      width: max-content;
      padding: 20px;
      background-position: center;
      background-repeat: no-repeat;
      background-size: cover;
    }

    .main {
      display: flex;
      flex-direction: column;
      width: 400px;
    }

    .card-top,
    .card-bottom,
    .card-end {
      display: flex;
      flex-direction: column;
      padding: 20px;
      backdrop-filter: blur(3px);
      border-radius: 10px;
      box-shadow: var(--box-shadow);
    }

    .card-top {
      background-color: var(--top-bg-color);
      margin-bottom: 20px;
    }

    .card-title {
      font-size: 24px;
      font-weight: bold;
      text-align: center;
      color: var(--top-title-color);
      margin-bottom: 10px;
    }

    .station {
      width: 100%;
      border-radius: 10px;
      display: flex;
      flex-direction: column;
      overflow: hidden;
      background-color: var(--top-inner-bg-color);
      box-shadow: var(--box-shadow);
    }

    .station-top {
      display: flex;
      flex-direction: row;
      justify-content: space-between;
      margin: 20px;
      /* margin-bottom: 0px; */
    }

    .departure-station,
    .terminal-station {
      display: flex;
      flex-direction: column;
    }

    .departure-station {
      align-items: flex-start;
    }

    .terminal-station {
      align-items: flex-end;
    }

    .departure-mark,
    .terminal-mark {
      height: 14px;
      width: 14px;
      border-radius: 4px;
      color: var(--top-inner-bg-color);
      font-size: 10px;
      text-align: center;
    }

    .departure-mark {
      margin-right: 2px;
      background-color: var(--departure-station-color);
    }

    .terminal-mark {
      margin-left: 2px;
      background-color: var(--terminal-station-color);
    }

    .arrow-box {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }

    .arrow-icon {
      width: 100%;
      display: flex;
      flex-direction: row;
      align-items: center;
      margin: 2px;
    }

    .arrow-icon .line {
      width: 100%;
      height: 3px;
      background-color: var(--split-line-color);
    }

    .arrow-icon .triangle {
      width: 0;
      height: 0;
      border-top: 5px solid transparent;
      border-bottom: 5px solid transparent;
      border-left: 10px solid var(--split-line-color);
    }

    .place {
      font-size: 18px;
      font-weight: bold;
      color: var(--bottom-main-color);
      margin-bottom: 4px;
    }

    .time,
    .duration {
      font-size: 12px;
      color: var(--top-sub-color);
    }

    .date {
      font-size: 12px;
      color: var(--arrow-time-color);
    }

    .time {
      display: flex;
      flex-direction: row;
      align-items: center;
    }

    .station-bottom {
      border-top: 1px solid var(--split-line-color);
      margin: 10px 20px;
      padding-top: 10px;
      display: flex;
      justify-content: space-between;
    }

    .admin,
    .emu_no {
        font-size: 12px;
        color: var(--top-sub-color);
        text-align: left;
    }

    .card-bottom {
      background-color: var(--bottom-bg-color);
    }

    .bottom-title {
      font-size: 18px;
      font-weight: bold;
      color: var(--bottom-main-color);
      margin-bottom: 10px;
    }

    .station-table {
      width: 100%;
      font-size: 14px;
      border-collapse: collapse;
      border-radius: 10px;
      overflow: hidden;
      box-shadow: var(--box-shadow);
    }

    /* .station-table thead {
        border-bottom: 1px solid var(--station-table-line-color);
      } */

    .station-table thead tr {
      background-color: var(--station-table-head-bg-color);
    }

    .station-table tbody tr:nth-child(odd) {
      background-color: var(--station-table-odd-bg-color);
    }

    .station-table tbody tr:nth-child(even) {
      background-color: var(--station-table-even-bg-color);
    }

    .station-table th,
    .station-table td {
      padding: 10px 5px;
    }

    .station-table tr th:first-child,
    .station-table tr td:first-child {
      padding-left: 20px;
    }

    .station-table th {
      font-weight: bold;
      color: var(--station-table-head-color);
      text-align: left;
    }

    .station-table td {
      color: var(--bottom-main-color);
    }

    .station-point {
      width: 8px;
      height: 8px;
      border-radius: 50%;
      box-shadow: var(--box-shadow);
      background-color: var(--station-table-line-color);

      /* for ::after */
      --line-height: 31px;
      text-align: center;
    }

    .station-point::after {
      content: '';
      width: 0px;
      height: var(--line-height);
      position: absolute;
      margin-left: -1px;
      margin-top: calc(var(--line-height) * -1);
      box-shadow: var(--box-shadow);
      border-left: 2px dashed var(--station-table-line-color);
    }

    .station-point.arrived {
      background-color: var(--station-table-arrived-line-color);
    }

    .station-point.arrived::after {
      border-left: 2px solid var(--station-table-arrived-line-color);
    }

    .station-table tbody tr:first-child td .station-point::after {
      height: 0px;
    }
  </style>
</head>

<body>
  <div class="bg-wrapper">
    <div class="main">
      <div class="card-top">
        <div class="card-title">
          {{ " / ".join(info.station_train_codes) }}
        </div>
        <div class="station">
          <div class="station-top">
            <div class="departure-station">
              <div class="place">{{ info.summary.from_station }}</div>
              <div class="time">
                <div class="departure-mark">始</div>
                {{ info.stations[0].start_time }}
              </div>
            </div>
            <div class="arrow-box">
              {% set total_time = info.total_time -%}
              {%- if info.arrive_next_day -%}
              <div class="duration">历时 {{ total_time[0] }} 时 {{ total_time[1] }} 分</div>
              <div class="arrow-icon">
                <div class="line"></div>
                <div class="triangle"></div>
              </div>
              {%- else -%}
              <div class="date">{{ info.date_summary }}</div>
              <div class="arrow-icon">
                <div class="line"></div>
                <div class="triangle"></div>
              </div>
              <div class="duration">历时 {{ total_time[0] }} 时 {{ total_time[1] }} 分</div>
              {%- endif %}
            </div>
            <div class="terminal-station">
              <div class="place">{{ info.summary.to_station }}</div>
              <div class="time">
                {{ info.stations[-1].arrive_time }}
                <div class="terminal-mark">终</div>
              </div>
            </div>
          </div>
          <div class="station-bottom">
            <div class="admin">担当路局：{{ info.maintancer }}</div>
            {%- if info.emu_no -%}
            <div class="emu_no">车组号：{{ info.emu_no }}</div>
            {%- endif %}
          </div>
        </div>
      </div>

      <div class="card-bottom">
        <div class="bottom-title">经停站</div>
        <div class="station-table-wrapper">
          <table class="station-table">
            <thead>
              <tr>
                <th></th>
                <th>停靠站</th>
                <th>开点</th>
                <th>到点</th>
                <th>停留</th>
                <th>车次</th>
              </tr>
            </thead>
            <tbody>
              {% for station in info.stations -%}
              <tr>
                <td>
                  <div class="station-point{% if info.arrived(loop.index0) %} arrived{% endif %}"></div>
                </td>
                <td>{{ station.station_name }}</td>
                <td>
                  {%- if station.start_time.startswith("-") %}{{ station.arrive_time }}
                  {%- else %}{{ station.start_time }}{% endif -%}
                </td>
                <td>
                  {%- if station.arrive_time.startswith("-") %}{{ station.start_time }}
                  {%- else %}{{ station.arrive_time }}{% endif -%}
                </td>
                <td>{% if loop.first %}始发站{% elif loop.last %}终点站{% else %}{{ station.stay_minutes }} 分{% endif %}</td>
                <td>{{ station.station_train_code }}</td>
              </tr>
              {% endfor -%}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
</body>

<script>
  // #region  https://github.com/Qix-/color-convert/blob/master/conversions.js
  /**
   * @param {[number, number, number]} rgb
   * @returns {[number, number, number]}
   */
  function rgb2Hsl(rgb) {
    const r = rgb[0] / 255;
    const g = rgb[1] / 255;
    const b = rgb[2] / 255;
    const min = Math.min(r, g, b);
    const max = Math.max(r, g, b);
    const delta = max - min;
    let h;
    let s;
    if (max === min) h = 0;
    else if (r === max) h = (g - b) / delta;
    else if (g === max) h = 2 + (b - r) / delta;
    else if (b === max) h = 4 + (r - g) / delta;
    h = Math.min(h * 60, 360);
    if (h < 0) h += 360;
    const l = (min + max) / 2;
    if (max === min) s = 0;
    else if (l <= 0.5) s = delta / (max + min);
    else s = delta / (2 - max - min);
    return [h, s * 100, l * 100].map((v) => Math.round(v));
  }

  /**
   * @param {[number, number, number]} hsl
   * @returns {[number, number, number]}
   */
  function hsl2Rgb(hsl) {
    const h = hsl[0] / 360;
    const s = hsl[1] / 100;
    const l = hsl[2] / 100;
    let t2;
    let t3;
    let val;
    if (s === 0) {
      val = l * 255;
      return [val, val, val];
    }
    if (l < 0.5) t2 = l * (1 + s);
    else t2 = l + s - l * s;
    const t1 = 2 * l - t2;
    const rgb = [0, 0, 0];
    for (let i = 0; i < 3; i++) {
      t3 = h + (1 / 3) * -(i - 1);
      if (t3 < 0) t3++;
      if (t3 > 1) t3--;
      if (6 * t3 < 1) val = t1 + (t2 - t1) * 6 * t3;
      else if (2 * t3 < 1) val = t2;
      else if (3 * t3 < 2) val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
      else val = t1;
      rgb[i] = val * 255;
    }
    return rgb.map((v) => Math.round(v));
  }
  // #endregion

  // #region https://kuangyx.cn/docs/文章/前端/提取图片主题色.html
  /**
   * @param {ImageBitmap} img
   * @param {number} offset color filter offset, pass a <= 0 val to disable
   * @returns {string[]} array of `rNum, gNum, bNum` str
   */
  function getImageThemeColor(img, offset = 16) {
    const htmlWidth = document.querySelector('.bg-wrapper').scrollWidth;
    const width = htmlWidth;
    const height = (htmlWidth * img.height) / img.width;
    const canvasElem = document.createElement('canvas');
    canvasElem.width = width;
    canvasElem.height = height;
    const ctx = canvasElem.getContext('2d');
    ctx.drawImage(img, 0, 0, width, height);
    const pxArr = Array.from(ctx.getImageData(0, 0, width, height).data);

    /** @type {Record<string, number>} */
    const colorList = {};
    const needFilter = offset > 0;
    let i = 0;
    while (i < pxArr.length) {
      const r = pxArr[i];
      const g = pxArr[i + 1];
      const b = pxArr[i + 2];
      // const a = pxArr[i + 3];
      i = i + 4; // 最后 +4 比每次 i++ 快 10ms 左右性能

      // filter colors near to black or white
      if (
        needFilter &&
        ((r + offset >= 255 && g + offset >= 255 && b + offset >= 255) ||
          (r - offset <= 0 && g - offset <= 0 && b - offset <= 0))
      )
        continue;

      const key = `${r}, ${g}, ${b}`; // , ${a}`
      if (key in colorList) ++colorList[key];
      else colorList[key] = 1;
    }

    return Object.entries(colorList)
      .sort(([_, a], [__, b]) => b - a)
      .map((v) => v[0]);
  }
  // #endregion

  /**
   * @param  {...[number, number, number]} colors
   * @returns {[number, number, number]}
   */
  function getAverageColor(...colors) {
    return colors
      .reduce((acc, cur) => acc.map((val, i) => val + cur[i]), [0, 0, 0])
      .map((val) => Math.round(val / colors.length));
  }

  /**
   * @param {[number, number, number]} color
   * @returns {string}
   */
  function rgbReverse(color) {
    return color.map((v) => 255 - v);
  }

  /**
   * @param {[number, number, number]} rgb
   * @param {number} alpha
   * @returns {string}
   */
  function rgb2Str(rgb, alpha = 255) {
    const [r, g, b] = rgb;
    return `rgb(${r},${g},${b},${alpha / 255})`;
  }

  /**
   * @param {[number, number, number]} hsl
   * @param {number} alpha
   * @returns {string}
   */
  function hsl2Str(hsl, alpha = 255) {
    const [h, s, l] = hsl;
    return `hsl(${h},${s}%,${l}%,${alpha / 255})`;
  }

  /**
   * @param {[number, number, number]} color
   * @param {number} offsetS
   * @param {number} offsetL
   * @returns {[number, number, number]} hsl
   */
  function modifyRgb2Hsl(color, offsetH, offsetS, offsetL) {
    const [h, s, l] = rgb2Hsl(color);
    const newH = (h + offsetH) % 361;
    const newS = Math.max(0, Math.min(100, s + offsetS));
    const newL = Math.max(0, Math.min(100, l + offsetL));
    return [newH, newS, newL];
  }

  function adjustLineHeight() {
    /** @type {NodeListOf<HTMLTableRowElement>} */
    const trElements = document.querySelectorAll('.station-table tbody tr');
    for (const trElem of trElements) {
      const lineHeight = trElem.offsetHeight;
      const pointElem = trElem.querySelector('.station-point');
      pointElem.style.setProperty('--line-height', `${lineHeight - 8}px`);
    }
  }

  async function changeThemeColor() {
    const bgUrl = 'https://cnrail.nonebot/image';

    const resp = await fetch(bgUrl, { redirect: 'follow' });
    const blob = await resp.blob();
    const dataUrl = URL.createObjectURL(blob);

    const bitmap = await createImageBitmap(blob);
    let colors = getImageThemeColor(bitmap);
    if (colors.length < 3) colors = getImageThemeColor(bitmap, 0);
    const themeRGB = getAverageColor(
      ...colors
        .slice(0, 3)
        .map((str) => str.split(',').map((v) => parseInt(v.trim())))
    );

    const headHsl = modifyRgb2Hsl(themeRGB, 0, +10, -25);
    const headBgHsl = modifyRgb2Hsl(themeRGB, 0, -10, +10);
    const cssWillAppend =
      `.bg-wrapper { background-image: url('${dataUrl}'); }\n` +
      `* {` +
      ` --top-bg-color: ${rgb2Str(themeRGB, 0xaa)};` +
      ` --top-title-color: ${rgb2Str(rgbReverse(themeRGB))};` +
      ` --station-table-head-bg-color: ${hsl2Str(headBgHsl, 0xaa)};` +
      ` --station-table-arrived-line-color: ${hsl2Str(headHsl)};` +
      ` --station-table-head-color: ${hsl2Str(headHsl)};` +
      ` }`;

    const styleElem = document.createElement('style');
    styleElem.innerHTML = cssWillAppend;
    document.head.appendChild(styleElem);
  }

  async function main() {
    try {
      adjustLineHeight();
      await changeThemeColor();
    } catch (e) {
      console.error(e);
    }

    // notify playwright that the page is done
    const doneDivElem = document.createElement('div');
    doneDivElem.id = 'done';
    document.body.appendChild(doneDivElem);
  }

  main();
</script>

</html>
