<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
  <title>正来の小站</title>
  <style>
    @font-face {
      font-family: "CorpSrcWinSong";
      src: url("resources/fonts/CorpSrcWinSong-slim.woff2") format("woff2");
      font-weight: normal;
      font-style: normal;
      font-display: swap;
    }

    @font-face {
      font-family: "Noto Serif SC";
      src: url("https://fonts.gstatic.com/s/notoserifsc/v22/H4c8BXePl9DZ0Xe7gG9cyOj7oqP9qmtf.otf") format("opentype");
      font-weight: 200 900;
      font-style: normal;
      font-display: swap;
    }

    :root {
      --bg:          #4a4640;
      --bg-deep:     #3e3b37;
      --bg-surface:  rgba(58,55,51,0.92);
      --bg-card:     rgba(255,247,230,0.04);
      --bg-hover:    rgba(200,188,150,0.07);
      --cream:       #c8bc96;
      --cream-bright:#d8ccaa;
      --cream-dim:   rgba(200,188,150,0.55);
      --cream-ghost: rgba(200,188,150,0.28);
      --cream-faint: rgba(200,188,150,0.12);
      --line:        rgba(200,188,150,0.18);
      --line-strong: rgba(200,188,150,0.35);
      --shadow:      0 8px 32px rgba(20,18,14,0.45);
      --font-serif:  "CorpSrcWinSong", "Noto Serif SC", "Source Han Serif SC", "SimSun", serif;
      --font-sans:   "Noto Serif SC", "PingFang SC", "Microsoft YaHei", system-ui, serif;
    }

    * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }

    html, body {
      width: 100%; height: 100%;
      font-family: var(--font-sans);
      background: var(--bg);
      color: var(--cream);
      overflow: hidden;
    }

    /* ── Custom Cursor ── */
    #cursor {
      position: fixed; pointer-events: none; z-index: 9999;
      width: 24px; height: 24px;
      transform: translate(-50%, -50%);
      transition: left 0.12s ease, top 0.12s ease,
                  width 0.28s cubic-bezier(0.25,1,0.5,1),
                  height 0.28s cubic-bezier(0.25,1,0.5,1);
    }
    #cursor.expanded {
      transition: left 0.18s ease, top 0.18s ease,
                  width 0.3s cubic-bezier(0.25,1,0.5,1),
                  height 0.3s cubic-bezier(0.25,1,0.5,1);
    }
    .cc { position: absolute; width: 10px; height: 10px; border-color: var(--cream); border-style: solid; border-width: 0; }
    .cc.tl { top:0; left:0;  border-top-width:1.5px; border-left-width:1.5px; }
    .cc.tr { top:0; right:0; border-top-width:1.5px; border-right-width:1.5px; }
    .cc.bl { bottom:0; left:0;  border-bottom-width:1.5px; border-left-width:1.5px; }
    .cc.br { bottom:0; right:0; border-bottom-width:1.5px; border-right-width:1.5px; }

    /* ── Mountain SVG Background ── */
    #mountains {
      position: fixed; inset: 0; z-index: 0;
      pointer-events: none;
    }

    /* ── Decorative corners ── */
    .deco-corner {
      position: fixed; z-index: 2; pointer-events: none;
    }
    .deco-corner.bl {
      bottom: 28px; left: 28px;
      width: 28px; height: 28px;
      border-left: 1px solid var(--cream-ghost);
      border-bottom: 1px solid var(--cream-ghost);
    }
    .deco-corner.tr-lines {
      top: 28px; right: 36px;
      display: flex; gap: 5px;
    }
    .deco-corner.tr-lines span {
      display: block; width: 1px; height: 52px;
      background: var(--cream-ghost);
    }
    .deco-corner.br-lines {
      bottom: 36px; right: 36px;
      display: flex; flex-direction: column; gap: 5px;
    }
    .deco-corner.br-lines span {
      display: block; height: 1px; width: 52px;
      background: var(--cream-ghost);
    }

    /* ── Layout ── */
    .layout {
      position: relative; z-index: 1;
      display: flex; height: 100vh;
      padding: 16px; gap: 0;
    }

    /* ── Sidebar ── */
    .sidebar {
      width: 220px; flex-shrink: 0;
      display: flex; flex-direction: column;
      padding: 24px 16px 24px 20px;
      gap: 0;
    }

    .sidebar-logo {
      display: flex; align-items: center;
      margin-bottom: 32px;
    }

    .logo-mark {
      width: 48px; height: 48px;
      position: relative; flex-shrink: 0;
    }

    .logo-mark svg {
      width: 100%; height: 100%;
      stroke: var(--cream-ghost); stroke-width: 1;
      fill: none;
    }

    .sidebar-name {
      font-family: var(--font-serif);
      font-size: 13px;
      color: var(--cream-dim);
      letter-spacing: 3px;
      line-height: 1.8;
      margin-left: 12px;
      writing-mode: vertical-rl;
      text-orientation: mixed;
    }

    .sidebar-rule {
      height: 1px;
      background: var(--line);
      margin: 0 4px 24px;
    }

    .sidebar-nav {
      display: flex; flex-direction: column; gap: 2px;
    }

    .nav-item {
      display: flex; align-items: center; gap: 12px;
      padding: 9px 12px;
      font-size: 14px; letter-spacing: 1.5px;
      color: var(--cream-dim);
      cursor: pointer; border: none; background: transparent;
      width: 100%; text-align: left;
      position: relative; border-radius: 2px;
      transition: color 0.22s ease;
      font-family: var(--font-serif);
    }

    .nav-item::before {
      content: '';
      position: absolute; left: 0; top: 50%;
      transform: translateY(-50%);
      width: 2px; height: 0;
      background: var(--cream);
      transition: height 0.22s cubic-bezier(0.25,1,0.5,1);
    }

    .nav-item:hover { color: var(--cream); }
    .nav-item:hover::before { height: 14px; }

    .nav-item.active {
      color: var(--cream-bright);
      font-weight: 600;
    }
    .nav-item.active::before { height: 20px; }

    .nav-item-num {
      font-size: 10px; color: var(--cream-ghost);
      letter-spacing: 0.5px; flex-shrink: 0;
      font-family: "Cascadia Code", "Fira Code", monospace;
    }

    .nav-icon {
      width: 15px; height: 15px;
      fill: none; stroke: currentColor;
      stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
      opacity: 0.6; flex-shrink: 0;
    }
    .nav-item.active .nav-icon,
    .nav-item:hover .nav-icon { opacity: 1; }

    .sidebar-footer {
      margin-top: auto;
      display: flex; flex-direction: column; gap: 4px;
    }

    .sidebar-footer .sidebar-rule { margin-bottom: 14px; }

    .tts-btn {
      display: flex; align-items: center; gap: 8px;
      padding: 8px 12px;
      font-size: 12px; letter-spacing: 1px;
      color: var(--cream-ghost);
      cursor: pointer; border: none; background: transparent;
      width: 100%; text-align: left; border-radius: 2px;
      transition: color 0.2s;
      font-family: var(--font-serif);
    }
    .tts-btn:hover { color: var(--cream-dim); }
    .tts-btn.active { color: var(--cream); }
    .tts-btn svg { width: 14px; height: 14px; flex-shrink: 0; }

    .sidebar-ver {
      padding: 4px 12px;
      font-size: 10px; color: var(--cream-ghost);
      letter-spacing: 1px;
      font-family: "Cascadia Code", monospace;
    }

    /* ── Content Pane ── */
    .content {
      flex: 1;
      display: flex; flex-direction: column;
      margin-left: 8px;
      border-left: 1px solid var(--line);
      padding: 32px 44px;
      overflow-y: auto;
    }

    .content::-webkit-scrollbar { width: 4px; }
    .content::-webkit-scrollbar-track { background: transparent; }
    .content::-webkit-scrollbar-thumb {
      background: var(--cream-faint);
      border-radius: 999px;
    }

    /* ── Sections ── */
    .section { display: none; opacity: 0; transform: translateY(12px); }
    .section.active {
      display: block;
      animation: fadeUp 0.38s cubic-bezier(0.16,1,0.3,1) forwards;
    }
    #section-home { display: none; }
    #section-home.active {
      display: flex; align-items: center;
      min-height: 100%;
    }

    @keyframes fadeUp {
      to { opacity: 1; transform: translateY(0); }
    }

    /* ── Page Header ── */
    .page-head {
      display: flex; align-items: baseline; gap: 16px;
      margin-bottom: 32px;
      padding-bottom: 16px;
      border-bottom: 1px solid var(--line);
    }

    .page-title {
      font-family: var(--font-serif);
      font-size: 24px; font-weight: 400;
      color: var(--cream-bright);
      letter-spacing: 4px;
    }

    .page-sub {
      font-size: 11px; color: var(--cream-ghost);
      letter-spacing: 2px;
      font-family: "Cascadia Code", monospace;
    }

    /* ── Hero ── */
    .hero {
      display: flex; gap: 72px;
      align-items: center;
      width: 100%;
      padding: 24px 0;
    }

    .hero-left { flex: 1; display: flex; flex-direction: column; gap: 24px; }

    .hero-eyebrow {
      font-size: 11px; letter-spacing: 3px; color: var(--cream-ghost);
      font-family: "Cascadia Code", monospace;
    }

    .hero-name {
      font-family: var(--font-serif);
      font-size: 56px; font-weight: 400;
      color: var(--cream-bright);
      letter-spacing: 12px;
      line-height: 1.1;
      position: relative;
    }

    .hero-name::after {
      content: '';
      display: block;
      width: 32px; height: 1px;
      background: var(--cream-ghost);
      margin-top: 20px;
    }

    .hero-bio {
      font-size: 14px; color: var(--cream-dim);
      line-height: 2.1; max-width: 480px;
      letter-spacing: 0.3px;
    }

    .hero-links {
      display: flex; gap: 20px; flex-wrap: wrap;
      margin-top: 4px;
    }

    .hero-link {
      font-size: 12px; letter-spacing: 2px;
      color: var(--cream-ghost);
      text-decoration: none;
      border-bottom: 1px solid transparent;
      transition: color 0.2s, border-color 0.2s;
      padding-bottom: 2px;
    }
    .hero-link:hover {
      color: var(--cream);
      border-bottom-color: var(--cream-ghost);
    }

    /* ── Terminal panel ── */
    .hero-panel {
      width: 320px; flex-shrink: 0;
      border: 1px solid var(--line);
      padding: 28px 28px;
      display: flex; flex-direction: column; gap: 20px;
      position: relative;
    }

    .hero-panel::before,
    .hero-panel::after {
      content: '';
      position: absolute;
      width: 8px; height: 8px;
    }
    .hero-panel::before { top: -1px; left: -1px; border-top: 1px solid var(--cream-ghost); border-left: 1px solid var(--cream-ghost); }
    .hero-panel::after  { bottom: -1px; right: -1px; border-bottom: 1px solid var(--cream-ghost); border-right: 1px solid var(--cream-ghost); }

    .term-line { display: flex; flex-direction: column; gap: 5px; }

    .term-cmd {
      font-size: 12px; color: var(--cream-ghost);
      font-family: "Cascadia Code", "Fira Code", monospace;
    }
    .term-cmd .prompt { color: var(--cream-dim); margin-right: 6px; }

    .term-out {
      font-size: 13px; color: var(--cream-dim);
      line-height: 1.7; padding-left: 18px;
      font-family: var(--font-serif);
    }
    .term-out .hl { color: var(--cream-bright); }
    .term-out a {
      color: var(--cream-dim); text-decoration: none;
      border-bottom: 1px solid var(--line);
      transition: color 0.2s, border-color 0.2s;
    }
    .term-out a:hover { color: var(--cream); border-bottom-color: var(--cream-dim); }

    /* ── Card Group ── */
    .card-group {
      border: 1px solid var(--line);
      margin-bottom: 20px;
    }

    .card-group-head {
      padding: 10px 18px;
      font-size: 10px; letter-spacing: 2.5px;
      color: var(--cream-ghost);
      border-bottom: 1px solid var(--line);
      font-family: "Cascadia Code", monospace;
    }

    .card-item {
      padding: 16px 18px;
      display: flex; align-items: center; gap: 14px;
      border-bottom: 1px solid var(--line);
      transition: background 0.15s;
    }
    .card-item:last-child { border-bottom: none; }
    .card-item:hover { background: var(--bg-hover); }
    .card-item-clickable { cursor: pointer; }

    .card-item-icon {
      width: 38px; height: 38px;
      display: flex; align-items: center; justify-content: center;
      font-size: 16px; flex-shrink: 0;
      border: 1px solid var(--line);
    }

    .card-item-info {
      flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0;
    }

    .card-item-label {
      font-size: 14px; color: var(--cream-bright);
      font-family: var(--font-serif); letter-spacing: 0.5px;
    }

    .card-item-desc {
      font-size: 12px; color: var(--cream-dim); line-height: 1.5;
    }

    .card-item-extra {
      font-size: 12px; color: var(--cream-ghost); flex-shrink: 0; text-align: right;
    }

    .card-item-tag {
      display: inline-block; padding: 1px 8px;
      font-size: 10px; letter-spacing: 1px;
      border: 1px solid var(--line);
      color: var(--cream-ghost);
    }

    /* ── Project Grid ── */
    .project-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
      gap: 16px;
    }

    .project-card {
      border: 1px solid var(--line);
      padding: 22px;
      cursor: pointer;
      transition: border-color 0.22s, background 0.22s, transform 0.22s;
      text-decoration: none;
      display: block;
    }
    .project-card:hover {
      border-color: var(--line-strong);
      background: var(--bg-hover);
      transform: translateY(-2px);
    }

    .project-card-header {
      display: flex; align-items: center; gap: 12px; margin-bottom: 10px;
    }

    .project-card-icon {
      width: 34px; height: 34px;
      display: flex; align-items: center; justify-content: center;
      font-size: 14px; flex-shrink: 0;
      border: 1px solid var(--line); overflow: hidden;
    }

    .project-card-name {
      font-size: 14px; color: var(--cream-bright);
      letter-spacing: 1px; font-family: var(--font-serif);
    }

    .project-card-desc {
      font-size: 12px; color: var(--cream-dim); line-height: 1.65; margin-bottom: 12px;
    }

    .project-card-tags { display: flex; gap: 6px; flex-wrap: wrap; }

    .project-tag {
      padding: 2px 8px; font-size: 10px; letter-spacing: 1px;
      border: 1px solid var(--line); color: var(--cream-ghost);
    }

    /* ── Blog List ── */
    .blog-list { display: flex; flex-direction: column; }

    .blog-item {
      padding: 18px 0; display: flex; align-items: flex-start; gap: 20px;
      border-bottom: 1px solid var(--line);
      cursor: pointer;
      transition: background 0.15s;
    }
    .blog-item:first-child { padding-top: 0; }
    .blog-item:last-child { border-bottom: none; }
    .blog-item:hover { background: var(--bg-hover); padding-left: 8px; }

    .blog-item-date {
      font-size: 11px; color: var(--cream-ghost); flex-shrink: 0;
      width: 86px; padding-top: 2px;
      font-family: "Cascadia Code", monospace; letter-spacing: 0.5px;
    }

    .blog-item-info { flex: 1; display: flex; flex-direction: column; gap: 5px; }

    .blog-item-title {
      font-size: 15px; color: var(--cream-bright);
      font-family: var(--font-serif); letter-spacing: 0.5px; line-height: 1.4;
    }

    .blog-item-excerpt { font-size: 12px; color: var(--cream-dim); line-height: 1.6; }

    .blog-item-tag {
      font-size: 10px; letter-spacing: 1.5px; color: var(--cream-ghost);
      display: inline-block; margin-top: 2px;
    }

    /* ── Blog Loading ── */
    .blog-loading {
      text-align: center; padding: 48px 0;
      color: var(--cream-ghost); font-size: 13px;
      letter-spacing: 1px;
    }

    /* ── Blog Detail ── */
    .blog-detail { animation: fadeUp 0.3s ease forwards; }

    .blog-detail-header {
      display: flex; align-items: center; gap: 14px;
      padding-bottom: 16px; margin-bottom: 20px;
      border-bottom: 1px solid var(--line);
    }

    .blog-detail-icon {
      display: flex; align-items: center; justify-content: center;
      color: var(--cream-dim); cursor: pointer;
      transition: color 0.2s;
    }
    .blog-detail-icon:hover { color: var(--cream-bright); }
    .blog-detail-icon svg { width: 36px; height: 36px; }

    .blog-detail-info { flex: 1; display: flex; flex-direction: column; gap: 3px; }

    .blog-crumb {
      color: var(--cream-dim); font-size: 13px; letter-spacing: 1px;
      cursor: pointer; transition: color 0.2s;
      display: inline-flex; align-items: center; gap: 8px;
      font-family: var(--font-serif);
    }
    .blog-crumb:hover { color: var(--cream); }
    .blog-crumb-sep { color: var(--cream-ghost); }
    .blog-crumb-current { color: var(--cream-bright); }
    .blog-detail-meta { font-size: 11px; color: var(--cream-ghost); letter-spacing: 1px; font-family: "Cascadia Code", monospace; }

    .blog-detail-article {
      color: var(--cream-dim); line-height: 2; font-size: 14px;
    }
    .blog-detail-article h1, .blog-detail-article h2, .blog-detail-article h3 {
      color: var(--cream-bright); margin: 28px 0 12px;
      font-family: var(--font-serif); font-weight: 400; letter-spacing: 2px;
    }
    .blog-detail-article h1 { font-size: 24px; }
    .blog-detail-article h2 { font-size: 19px; }
    .blog-detail-article h3 { font-size: 15px; }
    .blog-detail-article p { margin: 10px 0; }
    .blog-detail-article code {
      background: var(--bg-card); padding: 2px 6px;
      font-size: 12px; font-family: "Cascadia Code", monospace;
      border: 1px solid var(--line);
    }
    .blog-detail-article pre {
      background: rgba(0,0,0,0.25); padding: 18px 22px;
      overflow-x: auto; margin: 16px 0;
      border: 1px solid var(--line);
    }
    .blog-detail-article pre code { background: none; padding: 0; font-size: 12px; line-height: 1.65; border: none; }
    .blog-detail-article blockquote {
      border-left: 2px solid var(--cream-ghost); padding-left: 16px;
      margin: 12px 0; color: var(--cream-ghost);
    }
    .blog-detail-article ul, .blog-detail-article ol { padding-left: 24px; margin: 10px 0; }
    .blog-detail-article li { margin: 4px 0; }
    .blog-detail-article strong { color: var(--cream-bright); }
    .blog-detail-article hr { border: none; border-top: 1px solid var(--line); margin: 24px 0; }

    /* ── Friend Grid ── */
    .friend-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
      gap: 14px;
    }

    .friend-card {
      border: 1px solid var(--line);
      padding: 18px;
      display: flex; align-items: center; gap: 14px;
      cursor: pointer; text-decoration: none;
      transition: border-color 0.2s, background 0.2s, transform 0.2s;
    }
    .friend-card:hover {
      border-color: var(--line-strong);
      background: var(--bg-hover);
      transform: translateY(-2px);
    }

    .friend-avatar {
      width: 40px; height: 40px; flex-shrink: 0;
      display: flex; align-items: center; justify-content: center;
      font-size: 16px; font-weight: 600; color: var(--bg);
      overflow: hidden;
    }

    .friend-info { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

    .friend-name { font-size: 13px; color: var(--cream-bright); font-family: var(--font-serif); letter-spacing: 0.5px; }

    .friend-desc {
      font-size: 11px; color: var(--cream-dim);
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }

    /* ── TTS highlight ── */
    .tts-highlight { background: rgba(200,188,150,0.15); border-radius: 1px; transition: background 0.1s; }
    .tts-highlight-current { background: rgba(200,188,150,0.35); }

    /* ── Status dot ── */
    .status-dot {
      display: inline-block; width: 6px; height: 6px;
      border-radius: 50%; background: var(--cream-dim);
      margin-right: 6px; opacity: 0.6;
    }

    /* ── Mobile nav ── */
    .sidebar-nav-mobile { display: none; }

    .mobile-more-menu {
      display: none; position: fixed;
      bottom: calc(env(safe-area-inset-bottom,0px) + 72px);
      right: calc(env(safe-area-inset-right,0px) + 16px);
      background: var(--bg-surface); border: 1px solid var(--line);
      padding: 6px; min-width: 140px;
      box-shadow: var(--shadow); z-index: 100;
      animation: menuIn 0.2s cubic-bezier(0.16,1,0.3,1);
    }
    @keyframes menuIn {
      from { opacity: 0; transform: translateY(8px) scale(0.95); }
      to   { opacity: 1; transform: translateY(0) scale(1); }
    }
    .mobile-more-menu.show { display: block; }

    .mobile-more-item {
      display: flex; align-items: center; gap: 10px;
      padding: 10px 14px; font-size: 13px; letter-spacing: 1px;
      color: var(--cream-dim); cursor: pointer;
      border: none; background: transparent; width: 100%; text-align: left;
      transition: color 0.15s, background 0.15s;
      font-family: var(--font-serif);
    }
    .mobile-more-item:hover { background: var(--bg-hover); color: var(--cream); }
    .mobile-more-item.active { color: var(--cream-bright); }
    .mobile-more-item svg { width: 14px; height: 14px; flex-shrink: 0; }

    .mobile-version {
      display: none; position: fixed;
      bottom: calc(env(safe-area-inset-bottom,0px) + 60px);
      right: calc(env(safe-area-inset-right,0px) + 16px);
      font-size: 10px; color: var(--cream-ghost); z-index: 50;
      letter-spacing: 1px; font-family: "Cascadia Code", monospace;
    }

    /* ── Responsive ── */
    @media (max-width: 768px) {
      #cursor { display: none !important; }
      .deco-corner { display: none; }

      .layout {
        flex-direction: column; padding: 0; gap: 0;
        height: 100vh;
      }

      .sidebar {
        width: 100%; flex-direction: row;
        padding: calc(env(safe-area-inset-top,0px) + 12px) 16px 10px;
        border-bottom: 1px solid var(--line);
        align-items: center;
      }

      .sidebar-logo { margin-bottom: 0; }
      .logo-mark { width: 32px; height: 32px; }
      .sidebar-name { display: none; }
      .sidebar-rule { display: none; }
      .sidebar-nav.sidebar-nav-desktop { display: none; }
      .sidebar-footer { display: none; }

      .sidebar-nav-mobile {
        display: flex; flex-direction: row;
        position: fixed; bottom: 0; left: 0; right: 0;
        background: var(--bg-surface);
        border-top: 1px solid var(--line);
        z-index: 50;
        padding: 8px calc(env(safe-area-inset-right,0px) + 12px)
                 calc(env(safe-area-inset-bottom,0px) + 8px)
                 calc(env(safe-area-inset-left,0px) + 12px);
        gap: 4px;
      }

      .sidebar-nav-mobile .nav-item {
        flex: 1; flex-direction: column; gap: 4px;
        padding: 8px 4px; font-size: 11px; letter-spacing: 1px;
        justify-content: center; align-items: center;
        border-radius: 2px;
      }
      .sidebar-nav-mobile .nav-item::before { display: none; }
      .sidebar-nav-mobile .nav-item-num { display: none; }

      .content {
        flex: 1; margin-left: 0; border-left: none;
        border-top: 1px solid var(--line);
        padding: 20px 16px calc(env(safe-area-inset-bottom,0px) + 72px) 16px;
        overflow-y: auto;
      }

      .mobile-version { display: block; }

      .hero { flex-direction: column; gap: 32px; }
      .hero-left { width: 100%; }
      .hero-name { font-size: 36px; letter-spacing: 8px; }
      .hero-panel { width: 100%; }

      .project-grid, .friend-grid { grid-template-columns: 1fr; }
      .page-title { font-size: 18px; }
    }
  </style>
</head>

<body>
  <!-- Custom Cursor -->
  <div id="cursor">
    <div class="cc tl"></div>
    <div class="cc tr"></div>
    <div class="cc bl"></div>
    <div class="cc br"></div>
  </div>

  <!-- Mountain SVG Background -->
  <svg id="mountains" viewBox="0 0 1440 900" preserveAspectRatio="xMidYMax slice" xmlns="http://www.w3.org/2000/svg">
    <defs>
      <linearGradient id="skyGrad" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stop-color="#4a4640"/>
        <stop offset="100%" stop-color="#3e3b37"/>
      </linearGradient>
    </defs>
    <rect width="1440" height="900" fill="url(#skyGrad)"/>
    <!-- Far mountains layer 1 -->
    <polygon points="0,900 0,580 80,440 160,520 250,380 340,490 430,350 520,460 610,310 700,430 790,290 880,420 970,340 1060,470 1150,310 1240,440 1330,370 1440,480 1440,900"
             fill="rgba(40,38,35,0.45)"/>
    <!-- Mid mountains layer 2 -->
    <polygon points="0,900 0,640 100,520 200,590 300,480 400,560 500,430 600,540 700,390 800,510 900,420 1000,530 1100,460 1200,570 1300,500 1440,580 1440,900"
             fill="rgba(36,34,31,0.55)"/>
    <!-- Near mountains layer 3 -->
    <polygon points="0,900 0,700 120,600 240,670 360,560 480,640 600,540 720,640 840,560 960,660 1080,580 1200,670 1320,610 1440,680 1440,900"
             fill="rgba(32,30,27,0.60)"/>
  </svg>

  <!-- Decorative corners -->
  <div class="deco-corner bl"></div>
  <div class="deco-corner tr-lines"><span></span><span></span></div>
  <div class="deco-corner br-lines"><span></span><span></span></div>

  <div class="layout">
    <!-- Sidebar -->
    <aside class="sidebar">
      <div class="sidebar-logo">
        <!-- Geometric logo mark (sacred geometry: triangle in circle in square) -->
        <div class="logo-mark">
          <svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
            <rect x="4" y="4" width="40" height="40" stroke="rgba(200,188,150,0.35)" stroke-width="0.8" fill="none"/>
            <circle cx="24" cy="24" r="17" stroke="rgba(200,188,150,0.3)" stroke-width="0.8" fill="none"/>
            <polygon points="24,8 40,36 8,36" stroke="rgba(200,188,150,0.45)" stroke-width="0.8" fill="none"/>
            <line x1="24" y1="8" x2="24" y2="4"  stroke="rgba(200,188,150,0.25)" stroke-width="0.8"/>
            <line x1="40" y1="36" x2="44" y2="36" stroke="rgba(200,188,150,0.25)" stroke-width="0.8"/>
            <line x1="8"  y1="36" x2="4"  y2="36" stroke="rgba(200,188,150,0.25)" stroke-width="0.8"/>
          </svg>
        </div>
        <div class="sidebar-name">正来の小站</div>
      </div>

      <div class="sidebar-rule"></div>

      <nav class="sidebar-nav">
        <button class="nav-item active" data-section="home">
          <span class="nav-item-num">01</span>
          <svg class="nav-icon" viewBox="0 0 24 24">
            <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
            <polyline points="9 22 9 12 15 12 15 22"/>
          </svg>
          主页
        </button>
        <button class="nav-item" data-section="blog">
          <span class="nav-item-num">02</span>
          <svg class="nav-icon" viewBox="0 0 24 24">
            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
            <polyline points="14 2 14 8 20 8"/>
            <line x1="16" y1="13" x2="8" y2="13"/>
            <line x1="16" y1="17" x2="8" y2="17"/>
          </svg>
          博客
        </button>
        <button class="nav-item" data-section="friends">
          <span class="nav-item-num">03</span>
          <svg class="nav-icon" viewBox="0 0 24 24">
            <circle cx="12" cy="12" r="10"/>
            <path d="M10.59 13.41a2 2 0 0 1 0-2.82l2-2a2 2 0 1 1 2.82 2.82l-1.29 1.3"/>
            <circle cx="6" cy="18" r="2"/>
            <circle cx="18" cy="18" r="2"/>
          </svg>
          友链
        </button>
        <button class="nav-item" data-section="projects">
          <span class="nav-item-num">04</span>
          <svg class="nav-icon" viewBox="0 0 24 24">
            <polyline points="16 18 22 12 16 6"/>
            <polyline points="8 6 2 12 8 18"/>
          </svg>
          项目
        </button>
      </nav>

      <div class="sidebar-footer">
        <div class="sidebar-rule"></div>
        <button class="tts-btn" id="tts-btn" onclick="toggleTTS()">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
            <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
            <path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
            <path d="M19.07 4.93a10 10 0 0 1 0 14.14"/>
          </svg>
          朗读
        </button>
        <div class="sidebar-ver">v1.0.0</div>
      </div>
    </aside>

    <!-- Content -->
    <main class="content" id="content">
      <!-- 主页 -->
      <section class="section active" id="section-home">
        <div class="hero">
          <div class="hero-left">
            <div class="hero-eyebrow">// about · 关于</div>
            <div class="hero-name">正来</div>
            <div class="hero-bio">
              镜子里的左后方是台北市的东边，在春天的清晨六点零五分，太阳开始升起。一个不需要任何人说任何字的画面，橘色和金黄色的光，踏着缓慢而确定的脚步。<br><br>
              蒙蒙的脑袋突然想象起一个有限的世界，一个完备、一致、可判定的世界。<br>
              生命是冷硬的，没有意外、没有惊喜，绝对的具体。<br>
              我飘到哪了？专心看路，你正在跑步。
            </div>
            <div class="hero-links">
              <a class="hero-link" href="https://github.com/Haraguse" target="_blank" rel="noopener">GitHub</a>
              <a class="hero-link" href="https://space.bilibili.com/1217022368" target="_blank" rel="noopener">Bilibili</a>
              <a class="hero-link" href="mailto:chihuyou90@gmail.com">Email</a>
              <a class="hero-link" href="javascript:void(0)" onclick="switchSection('friends')">友链</a>
            </div>
          </div>

          <div class="hero-panel">
            <div class="term-line">
              <span class="term-cmd"><span class="prompt">&gt;</span> whoami</span>
              <span class="term-out"><span class="hl">正来</span></span>
            </div>
            <div class="term-line">
              <span class="term-cmd"><span class="prompt">&gt;</span> cat bio.txt</span>
              <span class="term-out">
                かいはつ者 / 眠りがち<br>
                喜欢捣鼓有趣的东西
              </span>
            </div>
            <div class="term-line">
              <span class="term-cmd"><span class="prompt">&gt;</span> ls -la skills/</span>
              <span class="term-out">
                <a href="javascript:void(0)" onclick="switchSection('projects')">前端 &nbsp;Python &nbsp;运维</a>
              </span>
            </div>
            <div class="term-line">
              <span class="term-cmd"><span class="prompt">&gt;</span> cat status.txt</span>
              <span class="term-out">
                <span class="status-dot"></span>在线中
              </span>
            </div>
          </div>
        </div>
      </section>

      <!-- 博客 -->
      <section class="section" id="section-blog">
        <div class="page-head">
          <div class="page-title">博客</div>
          <div class="page-sub">// blog</div>
        </div>
        <div class="blog-list" id="blog-list">
          <div class="blog-loading">正在加载博客...</div>
        </div>

        <div class="blog-detail" id="blog-detail" style="display:none;">
          <div class="blog-detail-header">
            <div class="blog-detail-icon" onclick="closeBlogDetail(event)">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                <path d="M19 12H5M12 19l-7-7 7-7"/>
              </svg>
            </div>
            <div class="blog-detail-info">
              <div class="blog-crumb" onclick="closeBlogDetail(event)">
                博客<span class="blog-crumb-sep">›</span>
                <span class="blog-crumb-current" id="blog-detail-title"></span>
              </div>
              <div class="blog-detail-meta" id="blog-detail-meta"></div>
            </div>
          </div>
          <article class="blog-detail-article" id="blog-detail-content"></article>
        </div>
      </section>

      <!-- 友链 -->
      <section class="section" id="section-friends">
        <div class="page-head">
          <div class="page-title">友链</div>
          <div class="page-sub">// friends</div>
        </div>
        <p style="font-size:12px;color:var(--cream-ghost);letter-spacing:1px;margin-bottom:24px;">以下是我经常拜访的朋友们，排名不分先后。</p>
        <div class="friend-grid" id="friend-grid">
          <div class="blog-loading">正在加载友链...</div>
        </div>
      </section>

      <!-- 项目 -->
      <section class="section" id="section-projects">
        <div class="page-head">
          <div class="page-title">项目</div>
          <div class="page-sub">// projects</div>
        </div>
        <div class="project-grid" id="project-grid">
          <div class="blog-loading">正在加载项目...</div>
        </div>
      </section>
    </main>
  </div>

  <!-- Mobile bottom nav -->
  <div class="sidebar-nav-mobile">
    <button class="nav-item active" data-section="home">
      <svg class="nav-icon" viewBox="0 0 24 24">
        <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
        <polyline points="9 22 9 12 15 12 15 22"/>
      </svg>
      主页
    </button>
    <button class="nav-item" data-section="blog">
      <svg class="nav-icon" viewBox="0 0 24 24">
        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
        <polyline points="14 2 14 8 20 8"/>
        <line x1="16" y1="13" x2="8" y2="13"/>
        <line x1="16" y1="17" x2="8" y2="17"/>
      </svg>
      博客
    </button>
    <button class="nav-item" data-section="friends">
      <svg class="nav-icon" viewBox="0 0 24 24">
        <circle cx="12" cy="12" r="10"/>
        <path d="M10.59 13.41a2 2 0 0 1 0-2.82l2-2a2 2 0 1 1 2.82 2.82l-1.29 1.3"/>
        <circle cx="6" cy="18" r="2"/>
        <circle cx="18" cy="18" r="2"/>
      </svg>
      友链
    </button>
    <button class="nav-item" data-section="projects">
      <svg class="nav-icon" viewBox="0 0 24 24">
        <polyline points="16 18 22 12 16 6"/>
        <polyline points="8 6 2 12 8 18"/>
      </svg>
      项目
    </button>
  </div>

  <!-- Mobile More Menu -->
  <div class="mobile-more-menu" id="mobile-more-menu">
    <button class="mobile-more-item" id="mobile-tts-btn" onclick="toggleTTS(); closeMobileMore();">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
        <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
        <path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
        <path d="M19.07 4.93a10 10 0 0 1 0 14.14"/>
      </svg>
      朗读
    </button>
  </div>
  <div class="mobile-version">v1.0.0</div>

  <script>
    // ── Section Switching ──
    const allNavItems = document.querySelectorAll('.nav-item[data-section]');
    const sections = document.querySelectorAll('.section');

    function switchSection(name) {
      allNavItems.forEach(el => el.classList.toggle('active', el.dataset.section === name));
      sections.forEach(s => s.classList.remove('active'));
      const t = document.getElementById('section-' + name);
      if (t) { t.classList.add('active'); document.getElementById('content').scrollTop = 0; }
    }

    allNavItems.forEach(el => el.addEventListener('click', () => switchSection(el.dataset.section)));

    // ── Custom Cursor ──
    (function () {
      const cursor = document.getElementById('cursor');
      const sel = 'a, button, [onclick], .nav-item, .hero-link, .card-item-clickable, .blog-item, .project-card, .friend-card';
      let mx = 0, my = 0;

      document.addEventListener('mousemove', e => {
        mx = e.clientX; my = e.clientY;
        cursor.style.left = mx + 'px';
        cursor.style.top  = my + 'px';
      });
      document.addEventListener('mouseenter', () => cursor.style.opacity = '1', true);
      document.addEventListener('mouseleave', () => cursor.style.opacity = '0', true);

      function bindEl(el) {
        if (el.dataset.cursorBound) return;
        el.dataset.cursorBound = '1';
        el.addEventListener('mouseenter', function () {
          const r = this.getBoundingClientRect();
          cursor.classList.add('expanded');
          cursor.style.width  = r.width  + 16 + 'px';
          cursor.style.height = r.height + 16 + 'px';
          cursor.style.left   = r.left + r.width  / 2 + 'px';
          cursor.style.top    = r.top  + r.height / 2 + 'px';
        });
        el.addEventListener('mouseleave', function () {
          cursor.classList.remove('expanded');
          cursor.style.width = '24px'; cursor.style.height = '24px';
          cursor.style.left = mx + 'px'; cursor.style.top = my + 'px';
        });
      }

      document.querySelectorAll(sel).forEach(bindEl);
      document.body.addEventListener('mouseover', e => {
        const t = e.target.closest(sel);
        if (t) bindEl(t);
      });
    })();

    // ── Blog Loader ──
    const BLOG_STORE = {};

    function parseBlogFilename(name) {
      const m = name.match(/^(\d{4})(\d{2})(\d{2})-(.+)\.md$/);
      if (!m) return null;
      return { year:m[1], month:m[2], day:m[3], date:`${m[1]}-${m[2]}-${m[3]}`, tag:m[4], sortKey:m[1]+m[2]+m[3] };
    }

    function parseBlogContent(text) {
      const lines = text.split('\n');
      let title = '', excerptLines = [], bodyLines = [];
      let phase = 'title';

      for (const line of lines) {
        if (phase === 'title') {
          const m = line.match(/^#\s+(.+)/);
          if (m) { title = m[1].trim(); phase = 'excerpt'; continue; }
          if (line.trim() && !title) { title = line.trim(); phase = 'excerpt'; continue; }
        } else if (phase === 'excerpt') {
          if (/^---+$/.test(line.trim())) { phase = 'body'; continue; }
          if (line.trim()) excerptLines.push(line);
          else if (excerptLines.length > 0) phase = 'body';
        } else {
          bodyLines.push(line);
        }
      }

      if (bodyLines.length === 0 && excerptLines.length > 0) {
        bodyLines = excerptLines;
        excerptLines = excerptLines.slice(0, 1);
      }

      return { title, excerpt: excerptLines.join(' ').trim().slice(0, 120), body: bodyLines.join('\n').trim() };
    }

    function renderMarkdown(md) {
      return md
        .replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>')
        .replace(/`([^`\n]+)`/g, '<code>$1</code>')
        .replace(/^### (.+)$/gm, '<h3>$1</h3>')
        .replace(/^## (.+)$/gm, '<h2>$1</h2>')
        .replace(/^# (.+)$/gm, '<h1>$1</h1>')
        .replace(/^\> (.+)$/gm, '<blockquote>$1</blockquote>')
        .replace(/^\- (.+)$/gm, '<li>$1</li>')
        .replace(/^(\d+)\. (.+)$/gm, '<li>$2</li>')
        .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
        .replace(/\n{2,}/g, '</p><p>')
        .replace(/\n/g, '<br>');
    }

    async function loadBlogs() {
      const listEl = document.getElementById('blog-list');
      listEl.innerHTML = '<div class="blog-loading">正在加载博客...</div>';
      let fileNames = [];

      try {
        const r = await fetch('blogs/manifest.json');
        if (r.ok) { const m = await r.json(); if (Array.isArray(m.files)) fileNames = m.files.filter(f => /\.md$/i.test(f)); }
      } catch {}

      if (fileNames.length === 0) {
        try {
          const r = await fetch('blogs/'); const t = await r.text();
          const links = t.match(/href="([^"]+\.md)"/g);
          if (links) fileNames = links.map(l => { const m = l.match(/href="([^"]+)"/); return m ? m[1].replace(/\\/g,'/').split('/').pop() : null; }).filter(Boolean);
        } catch {}
      }

      if (fileNames.length === 0) {
        listEl.innerHTML = '<div class="blog-loading">blogs 文件夹下暂无文章<br><small style="color:var(--cream-ghost)">请放入 YYYYMMDD-tag.md 格式的文件</small></div>';
        return;
      }

      const posts = [];
      for (const name of fileNames) {
        try {
          const r = await fetch(`blogs/${name}`);
          const text = await r.text();
          const meta = parseBlogFilename(name);
          const content = parseBlogContent(text);
          if (meta && content.title) posts.push({ ...meta, ...content, filename: name });
        } catch {}
      }

      posts.sort((a, b) => b.sortKey.localeCompare(a.sortKey));

      if (posts.length === 0) { listEl.innerHTML = '<div class="blog-loading">未找到有效博客文章</div>'; return; }

      BLOG_STORE.posts = posts;
      listEl.innerHTML = '';
      posts.forEach((post, i) => {
        const el = document.createElement('div');
        el.className = 'blog-item';
        el.dataset.index = i;
        el.innerHTML = `
          <span class="blog-item-date">${post.date}</span>
          <div class="blog-item-info">
            <span class="blog-item-title">${post.title}</span>
            <span class="blog-item-excerpt">${post.excerpt || '暂无简介'}</span>
            <span class="blog-item-tag"># ${post.tag}</span>
          </div>`;
        el.addEventListener('click', () => showBlogDetail(i));
        listEl.appendChild(el);
      });
    }

    function showBlogDetail(index) {
      const post = BLOG_STORE.posts[index];
      if (!post) return;
      document.getElementById('blog-list').style.display = 'none';
      const detail = document.getElementById('blog-detail');
      detail.style.display = 'block';
      document.getElementById('blog-detail-title').textContent = post.title;
      document.getElementById('blog-detail-meta').textContent = `${post.date} · # ${post.tag}`;
      document.getElementById('blog-detail-content').innerHTML = renderMarkdown(post.body);
      document.getElementById('content').scrollTop = 0;
    }

    function closeBlogDetail() {
      document.getElementById('blog-detail').style.display = 'none';
      document.getElementById('blog-list').style.display = '';
    }

    loadBlogs();

    // ── Text-to-Speech ──
    let ttsEnabled = false;
    let cachedVoices = [];
    let ttsSpanRefs = [];

    speechSynthesis.addEventListener('voiceschanged', () => cachedVoices = speechSynthesis.getVoices());
    cachedVoices = speechSynthesis.getVoices();

    function clearTTSHighlights() {
      ttsSpanRefs.forEach(s => s.classList.remove('tts-highlight', 'tts-highlight-current'));
      ttsSpanRefs = [];
    }

    function toggleTTS() {
      ttsEnabled = !ttsEnabled;
      document.getElementById('tts-btn').classList.toggle('active', ttsEnabled);
      document.getElementById('mobile-tts-btn').classList.toggle('active', ttsEnabled);
      if (!ttsEnabled) { if (speechSynthesis.speaking) speechSynthesis.cancel(); clearTTSHighlights(); }
    }

    function toggleMobileMore() {
      const menu = document.getElementById('mobile-more-menu');
      menu.classList.toggle('show');
    }

    function closeMobileMore() {
      document.getElementById('mobile-more-menu').classList.remove('show');
    }

    document.addEventListener('click', e => {
      const menu = document.getElementById('mobile-more-menu');
      if (menu && !menu.contains(e.target)) menu.classList.remove('show');
    });

    document.addEventListener('mouseup', () => {
      if (!ttsEnabled) return;
      const selection = window.getSelection();
      const text = selection.toString().trim();
      if (!text) return;
      if (speechSynthesis.speaking) speechSynthesis.cancel();
      clearTTSHighlights();

      const range = selection.getRangeAt(0);
      const textNodes = [];
      const walker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_TEXT, {
        acceptNode: node => {
          const nr = document.createRange(); nr.selectNodeContents(node);
          return range.compareBoundaryPoints(Range.END_TO_START, nr) < 0 &&
                 range.compareBoundaryPoints(Range.START_TO_END, nr) > 0
            ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
        }
      });
      while (walker.nextNode()) textNodes.push(walker.currentNode);

      let charIndex = 0;
      const spanMap = [];

      textNodes.forEach(textNode => {
        const startOffset = textNode === range.startContainer ? range.startOffset : 0;
        const endOffset   = textNode === range.endContainer   ? range.endOffset   : textNode.length;
        const fragment = document.createDocumentFragment();
        const subText = textNode.textContent.substring(startOffset, endOffset);

        for (let i = 0; i < subText.length; i++) {
          const span = document.createElement('span');
          span.textContent = subText[i];
          span.classList.add('tts-highlight');
          span.dataset.ttsIdx = charIndex;
          fragment.appendChild(span);
          spanMap.push({ span, idx: charIndex });
          charIndex++;
        }

        const before = textNode.textContent.substring(0, startOffset);
        const after  = textNode.textContent.substring(endOffset);
        const parent = textNode.parentNode;
        const repl   = document.createDocumentFragment();
        if (before) repl.appendChild(document.createTextNode(before));
        repl.appendChild(fragment);
        if (after)  repl.appendChild(document.createTextNode(after));
        parent.replaceChild(repl, textNode);
      });

      ttsSpanRefs = spanMap.map(s => s.span);

      const utt = new SpeechSynthesisUtterance(text);
      utt.lang = 'zh-CN'; utt.rate = 1; utt.pitch = 1;
      const isEdge = navigator.userAgent.includes('Edg/');
      if (isEdge && cachedVoices.length) {
        const v = cachedVoices.find(v => v.name === 'Microsoft Yunxi Online (Natural) - Chinese (Mainland)');
        if (v) utt.voice = v;
      }

      let lastIdx = -1;
      utt.addEventListener('boundary', e => {
        if (e.name !== 'word' && e.name !== 'character') return;
        const idx = e.charIndex;
        if (lastIdx >= 0) for (let i = lastIdx; i < idx && i < ttsSpanRefs.length; i++) ttsSpanRefs[i].classList.remove('tts-highlight-current');
        if (idx < ttsSpanRefs.length) { ttsSpanRefs[idx].classList.add('tts-highlight-current'); ttsSpanRefs[idx].scrollIntoView({ block: 'nearest', behavior: 'smooth' }); }
        lastIdx = idx;
      });
      utt.addEventListener('end', clearTTSHighlights);
      utt.addEventListener('error', clearTTSHighlights);
      speechSynthesis.speak(utt);
    });

    // ── Friends Loader ──
    async function loadFriends() {
      const grid = document.getElementById('friend-grid');
      try {
        const resp = await fetch('friends.xml');
        const xmlText = await resp.text();
        const xml = new DOMParser().parseFromString(xmlText, 'text/xml');
        const items = xml.querySelectorAll('friend');

        if (items.length === 0) { grid.innerHTML = '<div class="blog-loading">暂无友链数据</div>'; return; }

        grid.innerHTML = '';
        items.forEach(item => {
          const name      = item.querySelector('name')?.textContent || '';
          const desc      = item.querySelector('desc')?.textContent || '';
          const url       = item.querySelector('url')?.textContent || '#';
          const avatarRaw = (item.querySelector('avatar')?.textContent || '').trim();
          const isImg     = /^https?:\/\/|^\//.test(avatarRaw);

          let avatarHtml;
          if (isImg) {
            avatarHtml = `<div class="friend-avatar" style="padding:0;overflow:hidden;"><img src="${avatarRaw}" alt="${name}" style="width:100%;height:100%;object-fit:cover;display:block;"></div>`;
          } else {
            avatarHtml = `<div class="friend-avatar" style="background:var(--cream-faint);color:var(--cream);">${name.charAt(0).toUpperCase()}</div>`;
          }

          const card = document.createElement('a');
          card.className = 'friend-card';
          card.href = url; card.target = '_blank'; card.rel = 'noopener'; card.style.textDecoration = 'none';
          card.innerHTML = `${avatarHtml}<div class="friend-info"><span class="friend-name">${name}</span><span class="friend-desc">${desc}</span></div>`;
          grid.appendChild(card);
        });
      } catch { grid.innerHTML = '<div class="blog-loading">友链加载失败</div>'; }
    }

    loadFriends();

    // ── Projects Loader ──
    async function loadProjects() {
      const grid = document.getElementById('project-grid');
      try {
        const resp = await fetch('projects.xml');
        const xmlText = await resp.text();
        const xml = new DOMParser().parseFromString(xmlText, 'text/xml');
        const items = xml.querySelectorAll('project');

        if (items.length === 0) { grid.innerHTML = '<div class="blog-loading">暂无项目数据</div>'; return; }

        grid.innerHTML = '';
        items.forEach(item => {
          const name      = item.querySelector('name')?.textContent || '';
          const desc      = item.querySelector('desc')?.textContent || '';
          const url       = item.querySelector('url')?.textContent || '';
          const avatarRaw = (item.querySelector('avatar')?.textContent || '').trim();
          const color     = item.querySelector('color')?.textContent || 'rgba(200,188,150,0.06)';
          const tagsRaw   = item.querySelector('tags')?.textContent || '';
          const tags      = tagsRaw.split(',').map(t => t.trim()).filter(Boolean);
          const isImg     = /^https?:\/\/|^\//.test(avatarRaw);

          let iconHtml;
          if (isImg) {
            iconHtml = `<div class="project-card-icon" style="background:${color};padding:0;overflow:hidden;"><img src="${avatarRaw}" alt="${name}" style="width:100%;height:100%;object-fit:cover;display:block;"></div>`;
          } else {
            iconHtml = `<div class="project-card-icon" style="background:var(--cream-faint);">${name.charAt(0)}</div>`;
          }

          const card = document.createElement(url ? 'a' : 'div');
          card.className = 'project-card';
          if (url) { card.href = url; card.target = '_blank'; card.rel = 'noopener'; card.style.textDecoration = 'none'; }
          card.innerHTML = `
            <div class="project-card-header">${iconHtml}<span class="project-card-name">${name}</span></div>
            <p class="project-card-desc">${desc}</p>
            <div class="project-card-tags">${tags.map(t => `<span class="project-tag">${t}</span>`).join('')}</div>`;
          grid.appendChild(card);
        });
      } catch { grid.innerHTML = '<div class="blog-loading">项目加载失败</div>'; }
    }

    loadProjects();
  </script>
</body>

</html>
