visuddhinanda 2 주 전
부모
커밋
08f7190a85
100개의 변경된 파일7543개의 추가작업 그리고 0개의 파일을 삭제
  1. 16 0
      api-v13/resources/css/base/_reset.css
  2. 13 0
      api-v13/resources/css/base/_typography.css
  3. 39 0
      api-v13/resources/css/base/_variables.css
  4. 4 0
      api-v13/resources/css/components/_badge.css
  5. 199 0
      api-v13/resources/css/components/_card-book.css
  6. 287 0
      api-v13/resources/css/components/_card.css
  7. 64 0
      api-v13/resources/css/components/_pagination.css
  8. 22 0
      api-v13/resources/css/components/_search-input.css
  9. 138 0
      api-v13/resources/css/components/_search-results.css
  10. 92 0
      api-v13/resources/css/layout/_drawer.css
  11. 4 0
      api-v13/resources/css/layout/_footer.css
  12. 85 0
      api-v13/resources/css/layout/_grid.css
  13. 76 0
      api-v13/resources/css/layout/_hero.css
  14. 119 0
      api-v13/resources/css/layout/_navbar.css
  15. 4 0
      api-v13/resources/css/layout/_toolbar.css
  16. 33 0
      api-v13/resources/css/library.css
  17. 284 0
      api-v13/resources/css/modules/_anthology.css
  18. 308 0
      api-v13/resources/css/modules/_library-index.css
  19. 313 0
      api-v13/resources/css/modules/_reader.css
  20. 397 0
      api-v13/resources/css/modules/_tipitaka.css
  21. 942 0
      api-v13/resources/css/modules/_wiki.css
  22. 6 0
      api-v13/resources/css/reader.css
  23. 360 0
      api-v13/resources/css/tufte.css
  24. 8 0
      api-v13/resources/js/app.js
  25. 4 0
      api-v13/resources/js/bootstrap.js
  26. 30 0
      api-v13/resources/js/modules/navbar.js
  27. 233 0
      api-v13/resources/js/modules/term-tooltip.js
  28. 91 0
      api-v13/resources/js/search-suggest.js
  29. 227 0
      api-v13/resources/js/term-tooltip.js
  30. 83 0
      api-v13/resources/js/term-tooltip.old.js
  31. 20 0
      api-v13/resources/lang/en/auth.php
  32. 5 0
      api-v13/resources/lang/en/buttons.php
  33. 7 0
      api-v13/resources/lang/en/labels.php
  34. 10 0
      api-v13/resources/lang/en/language.php
  35. 19 0
      api-v13/resources/lang/en/pagination.php
  36. 22 0
      api-v13/resources/lang/en/passwords.php
  37. 22 0
      api-v13/resources/lang/en/site.php
  38. 162 0
      api-v13/resources/lang/en/validation.php
  39. 20 0
      api-v13/resources/lang/zh-Hans/auth.php
  40. 5 0
      api-v13/resources/lang/zh-Hans/buttons.php
  41. 199 0
      api-v13/resources/lang/zh-Hans/grammar.php
  42. 4 0
      api-v13/resources/lang/zh-Hans/label.php
  43. 7 0
      api-v13/resources/lang/zh-Hans/labels.php
  44. 19 0
      api-v13/resources/lang/zh-Hans/pagination.php
  45. 22 0
      api-v13/resources/lang/zh-Hans/passwords.php
  46. 22 0
      api-v13/resources/lang/zh-Hans/site.php
  47. 162 0
      api-v13/resources/lang/zh-Hans/validation.php
  48. 20 0
      api-v13/resources/lang/zh-Hant/auth.php
  49. 5 0
      api-v13/resources/lang/zh-Hant/buttons.php
  50. 5 0
      api-v13/resources/lang/zh-Hant/labels.php
  51. 19 0
      api-v13/resources/lang/zh-Hant/pagination.php
  52. 22 0
      api-v13/resources/lang/zh-Hant/passwords.php
  53. 22 0
      api-v13/resources/lang/zh-Hant/site.php
  54. 162 0
      api-v13/resources/lang/zh-Hant/validation.php
  55. 6 0
      api-v13/resources/mustache/article/html/footnote.html
  56. 9 0
      api-v13/resources/mustache/article/html/glossary.html
  57. 87 0
      api-v13/resources/mustache/article/html/main.html
  58. 5 0
      api-v13/resources/mustache/article/html/section.html
  59. 3 0
      api-v13/resources/mustache/article/md/footnote.md
  60. 9 0
      api-v13/resources/mustache/article/md/glossary.md
  61. 3 0
      api-v13/resources/mustache/article/md/main.md
  62. 8 0
      api-v13/resources/mustache/article/md/section.md
  63. 6 0
      api-v13/resources/mustache/chapter/html/footnote.html
  64. 15 0
      api-v13/resources/mustache/chapter/html/glossary.html
  65. 51 0
      api-v13/resources/mustache/chapter/html/main.html
  66. 6 0
      api-v13/resources/mustache/chapter/html/paragraph.html
  67. 3 0
      api-v13/resources/mustache/chapter/html/section.html
  68. 6 0
      api-v13/resources/mustache/chapter/html/sentence.html
  69. 3 0
      api-v13/resources/mustache/chapter/md/footnote.md
  70. 15 0
      api-v13/resources/mustache/chapter/md/glossary.md
  71. 3 0
      api-v13/resources/mustache/chapter/md/main.md
  72. 8 0
      api-v13/resources/mustache/chapter/md/paragraph.md
  73. 4 0
      api-v13/resources/mustache/chapter/md/section.md
  74. 7 0
      api-v13/resources/mustache/chapter/md/sentence.md
  75. 20 0
      api-v13/resources/mustache/chapter/tex/main.tex
  76. 6 0
      api-v13/resources/mustache/chapter/tex/paragraph.tex
  77. 3 0
      api-v13/resources/mustache/chapter/tex/section.tex
  78. 5 0
      api-v13/resources/mustache/chapter/tex/sentence.tex
  79. 158 0
      api-v13/resources/mustache/my_han_crop.tpl
  80. 11 0
      api-v13/resources/mustache/nissaya_ending_card.tpl
  81. BIN
      api-v13/resources/template/docx/paper.docx
  82. 323 0
      api-v13/resources/views/ananke.blade.php
  83. 67 0
      api-v13/resources/views/blog/category.blade.php
  84. 93 0
      api-v13/resources/views/blog/index.blade.php
  85. 318 0
      api-v13/resources/views/blog/layouts/app.blade.php
  86. 74 0
      api-v13/resources/views/book.blade.php
  87. 165 0
      api-v13/resources/views/components/language-switcher.blade.php
  88. 24 0
      api-v13/resources/views/components/library/footer.blade.php
  89. 51 0
      api-v13/resources/views/components/library/header.blade.php
  90. 62 0
      api-v13/resources/views/components/library/navbar.blade.php
  91. 20 0
      api-v13/resources/views/components/term-drawer.blade.php
  92. 40 0
      api-v13/resources/views/components/ui/author-avatar.blade.php
  93. 46 0
      api-v13/resources/views/components/ui/book-cover.blade.php
  94. 21 0
      api-v13/resources/views/components/ui/book-grid.blade.php
  95. 69 0
      api-v13/resources/views/components/ui/card-anthology.blade.php
  96. 52 0
      api-v13/resources/views/components/ui/card-book.blade.php
  97. 35 0
      api-v13/resources/views/components/ui/empty-state.blade.php
  98. 54 0
      api-v13/resources/views/components/ui/search-input.blade.php
  99. 95 0
      api-v13/resources/views/components/wiki/entry-actions.blade.php
  100. 11 0
      api-v13/resources/views/components/wiki/entry-header.blade.php

+ 16 - 0
api-v13/resources/css/base/_reset.css

@@ -0,0 +1,16 @@
+/* resources/css/base/_reset.css
+   最小化 reset,补充 Tabler 未覆盖的部分。
+   不覆盖 Tabler 已处理好的规则。
+*/
+
+*,
+*::before,
+*::after {
+    box-sizing: border-box;
+}
+
+img,
+video {
+    max-width: 100%;
+    height: auto;
+}

+ 13 - 0
api-v13/resources/css/base/_typography.css

@@ -0,0 +1,13 @@
+/* resources/css/base/_typography.css
+   全站基础排版。
+   Noto Serif 已在 library.css 入口处 @import,此处只设置使用规则。
+*/
+
+/* 正文衬线字体作用域由各 module 自行声明(如 .wiki-content-body)。
+   全站默认保持 Tabler 的 sans-serif 体系,不在此全局覆盖。 */
+
+/* 标题基础 */
+h1, h2, h3, h4, h5, h6 {
+    line-height: 1.3;
+    font-weight: 600;
+}

+ 39 - 0
api-v13/resources/css/base/_variables.css

@@ -0,0 +1,39 @@
+/* resources/css/base/_variables.css
+   全站 CSS 变量
+   - 第一层:覆盖 Tabler 默认值
+   - 第二层:WikiPali 自定义 token
+   所有颜色、间距、字体 token 统一在此定义,各 module 只引用变量,不硬编码色值
+*/
+
+/* ── Tabler 变量覆盖 ── */
+:root {
+    --tblr-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+    --tblr-border-radius: 0.375rem;
+    --tblr-border-radius-lg: 0.5rem;
+}
+
+/* ── WikiPali 品牌色 token ── */
+:root {
+    /* 主色:暖金/琥珀,源自 main.css --sf */
+    --wp-brand:        #c8860a;
+    --wp-brand-light:  #f5e6c8;
+    --wp-brand-pale:   #fdf8f0;
+    --wp-brand-dark:   #9a6508;
+
+    /* 文字层级,源自 main.css --ink */
+    --wp-ink:          #1a1208;
+    --wp-ink-soft:     #4a3f2f;
+    --wp-ink-muted:    #8a7a68;
+
+    /* 边框 / 背景 */
+    --wp-border:       #e8ddd0;
+    --wp-card-bg:      #fffdf9;
+    --wp-surface-alt:  #fdf8f0;
+}
+
+/* ── 阅读页专属 token(reader.css 会用到) ── */
+:root {
+    --wp-reader-font-size:    1rem;
+    --wp-reader-line-height:  1.875;
+    --wp-reader-max-width:    720px;
+}

+ 4 - 0
api-v13/resources/css/components/_badge.css

@@ -0,0 +1,4 @@
+/* resources/css/components/_badge.css
+   通用徽章。wiki 质量徽章(.wiki-quality-badge)在 modules/_wiki.css 中定义。
+   此文件预留全站徽章扩展。
+*/

+ 199 - 0
api-v13/resources/css/components/_card-book.css

@@ -0,0 +1,199 @@
+/* resources/css/components/_card-book.css
+   书籍封面组件(.book-cover)+ 纵向书籍卡片(.card-book)。
+   .book-cover 从 _anthology.css 提取,供 anthology / tipitaka 共用。
+*/
+
+/* ══════════════════════════════════════════
+   一、书籍封面(.book-cover)
+   ══════════════════════════════════════════ */
+
+.book-cover {
+    position: relative;
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+}
+
+/* 尺寸 */
+.book-cover--sm { width: 34px;  min-width: 34px;  height: 46px; }
+.book-cover--md { width: 130px; min-width: 130px; height: 180px; }
+.book-cover--lg { width: 155px; min-width: 155px; height: 215px; border-radius: 3px 9px 9px 3px; }
+
+/* 3D 书脊 */
+.book-cover--3d {
+    box-shadow:
+        -4px 0 0 rgba(0,0,0,.3),
+        -6px 4px 14px rgba(0,0,0,.4),
+        4px 4px 18px rgba(0,0,0,.3);
+}
+
+.book-cover--3d::before {
+    content: '';
+    position: absolute;
+    left: 0; top: 0; bottom: 0;
+    width: 13px;
+    background: linear-gradient(to right, rgba(0,0,0,.4), rgba(0,0,0,.1));
+    border-radius: 3px 0 0 3px;
+    z-index: 2;
+}
+
+/* 纹理叠加 */
+.book-cover::after {
+    content: '';
+    position: absolute;
+    inset: 0;
+    background: repeating-linear-gradient(
+        45deg, transparent, transparent 8px,
+        rgba(255,255,255,.015) 8px, rgba(255,255,255,.015) 9px
+    );
+    z-index: 1;
+}
+
+/* 图片封面 */
+.book-cover__img {
+    position: absolute;
+    inset: 0;
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+}
+
+/* 文字封面 */
+.book-cover__text {
+    position: relative;
+    z-index: 3;
+    text-align: center;
+    padding: 0 .5rem;
+}
+
+.book-cover__title {
+    font-family: 'Noto Serif SC', 'Noto Serif', Georgia, serif;
+    font-size: 1rem;
+    font-weight: 600;
+    color: #fff;
+    line-height: 1.6;
+    letter-spacing: .12em;
+    word-break: break-all;
+}
+
+.book-cover--sm .book-cover__title {
+    font-size: .6rem;
+    letter-spacing: .04em;
+    line-height: 1.3;
+}
+
+.book-cover__divider {
+    width: 28px;
+    height: 1px;
+    background: var(--wp-brand);
+    margin: .5rem auto;
+}
+
+.book-cover__subtitle {
+    font-size: .65rem;
+    color: rgba(255,255,255,.45);
+    letter-spacing: .04em;
+}
+
+/* ══════════════════════════════════════════
+   二、纵向书籍卡片(.card-book)
+   tipitaka 列表页使用
+   ══════════════════════════════════════════ */
+
+.card-book {
+    transition: transform 0.2s, box-shadow 0.2s;
+}
+
+.card-book:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 8px 24px rgba(0,0,0,.1);
+}
+
+.card-book__link {
+    display: block;
+    text-decoration: none;
+    color: inherit;
+}
+
+.card-book__link:hover {
+    text-decoration: none;
+    color: inherit;
+}
+
+/* 封面撑满卡片宽度 */
+.card-book .book-cover--md {
+    width: 100%;
+    min-width: unset;
+    height: 200px;
+    border-radius: var(--tblr-border-radius);
+}
+
+@media (max-width: 768px) {
+    .card-book .book-cover--md { height: 150px; }
+}
+
+.card-book__info { padding: .75rem 0 0; }
+
+.card-book__title {
+    font-size: 0.9375rem;
+    font-weight: 600;
+    color: var(--tblr-body-color);
+    margin-bottom: .25rem;
+    line-height: 1.4;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+}
+
+.card-book:hover .card-book__title { color: var(--tblr-primary); }
+
+.card-book__author,
+.card-book__publisher {
+    font-size: .8125rem;
+    color: var(--tblr-secondary);
+    margin-bottom: .25rem;
+}
+
+.card-book__publisher-link {
+    color: var(--tblr-primary);
+    text-decoration: none;
+}
+
+.card-book__publisher-link:hover { text-decoration: underline; }
+
+.card-book__badges {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 4px;
+    margin-top: .375rem;
+}
+
+.card-book__badge {
+    display: inline-block;
+    padding: 2px 8px;
+    background: var(--tblr-bg-surface-secondary);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: 20px;
+    font-size: .6875rem;
+    color: var(--tblr-secondary);
+}
+
+/* ══════════════════════════════════════════
+   三、书籍网格
+   ══════════════════════════════════════════ */
+
+.book-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+    gap: 1.25rem;
+}
+
+@media (max-width: 575px) {
+    .book-grid {
+        grid-template-columns: repeat(2, 1fr);
+        gap: .875rem;
+    }
+}

+ 287 - 0
api-v13/resources/css/components/_card.css

@@ -0,0 +1,287 @@
+/* resources/css/components/_card.css
+   全站通用卡片、侧边栏、列表组件。
+   从 _wiki.css 提取,供 wiki / tipitaka / anthology / search 共用。
+   wiki 专属样式(质量徽章、条目头部、term popover 等)保留在 modules/_wiki.css。
+*/
+
+/* ══════════════════════════════════════════
+   一、通用卡片
+   ══════════════════════════════════════════ */
+
+.wiki-card {
+    background: var(--tblr-bg-surface);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: var(--tblr-border-radius-lg);
+    padding: 1.5rem;
+}
+
+/* ══════════════════════════════════════════
+   二、侧边栏区块
+   ══════════════════════════════════════════ */
+
+.wiki-sidebar-section {
+    background: var(--tblr-bg-surface);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: var(--tblr-border-radius-lg);
+    padding: 1rem 1.125rem;
+    margin-bottom: 1rem;
+}
+
+.wiki-sidebar-title {
+    font-size: 0.6875rem;
+    font-weight: 500;
+    letter-spacing: 0.05em;
+    text-transform: uppercase;
+    color: var(--tblr-secondary);
+    margin-bottom: 0.75rem;
+}
+
+/* ══════════════════════════════════════════
+   三、分类列表
+   ══════════════════════════════════════════ */
+
+.wiki-cat-list {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+}
+
+.wiki-cat-list li {
+    margin-bottom: 2px;
+}
+
+.wiki-cat-list a {
+    display: flex;
+    align-items: center;
+    font-size: 0.8125rem;
+    color: var(--tblr-body-color);
+    text-decoration: none;
+    padding: 5px 8px;
+    border-radius: var(--tblr-border-radius);
+    transition: background 0.12s;
+}
+
+.wiki-cat-list a:hover {
+    background: var(--tblr-bg-surface-secondary);
+}
+
+.wiki-cat-list a.active {
+    background: var(--tblr-bg-surface-secondary);
+    font-weight: 500;
+    color: var(--tblr-primary);
+}
+
+.wiki-cat-count {
+    font-size: 0.6875rem;
+    background: var(--tblr-bg-surface-secondary);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: 20px;
+    padding: 1px 7px;
+    color: var(--tblr-secondary);
+    margin-left: auto;
+    flex-shrink: 0;
+}
+
+/* ══════════════════════════════════════════
+   四、TOC 列表
+   ══════════════════════════════════════════ */
+
+.wiki-toc-list {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+}
+
+.wiki-toc-list li {
+    border-bottom: 1px solid var(--tblr-border-color);
+}
+
+.wiki-toc-list li:last-child {
+    border-bottom: none;
+}
+
+.wiki-toc-list a {
+    display: block;
+    font-size: 0.8125rem;
+    color: var(--tblr-secondary);
+    text-decoration: none;
+    padding: 5px 0;
+    transition: color 0.12s;
+}
+
+.wiki-toc-list a:hover  { color: var(--tblr-body-color); }
+.wiki-toc-list a.active { color: var(--tblr-body-color); font-weight: 500; }
+
+.wiki-toc-list .toc-level-2 a { padding-left: 0.5rem; }
+.wiki-toc-list .toc-level-3 a { padding-left: 1rem; }
+
+.wiki-toc-num {
+    color: var(--tblr-secondary);
+    margin-right: 5px;
+    font-size: 0.75rem;
+}
+
+/* ══════════════════════════════════════════
+   五、相关条目列表
+   ══════════════════════════════════════════ */
+
+.wiki-related-list {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+}
+
+.wiki-related-list li {
+    border-bottom: 1px solid var(--tblr-border-color);
+}
+
+.wiki-related-list li:last-child { border-bottom: none; }
+
+.wiki-related-list a {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    font-size: 0.8125rem;
+    color: var(--tblr-primary);
+    text-decoration: none;
+    padding: 6px 0;
+}
+
+.wiki-related-zh {
+    font-size: 0.75rem;
+    color: var(--tblr-secondary);
+}
+
+/* ══════════════════════════════════════════
+   六、元信息表格
+   ══════════════════════════════════════════ */
+
+.wiki-meta-table {
+    width: 100%;
+    font-size: 0.8125rem;
+    border-collapse: collapse;
+}
+
+.wiki-meta-table td { padding: 3px 0; }
+
+.wiki-meta-table td:last-child {
+    text-align: right;
+    color: var(--tblr-secondary);
+}
+
+/* ══════════════════════════════════════════
+   七、条目头部(通用标题区)
+   ══════════════════════════════════════════ */
+
+.wiki-entry-header { margin-bottom: 1.25rem; }
+
+.wiki-entry-title {
+    font-family: "Noto Serif", Georgia, serif;
+    font-size: 1.75rem;
+    font-weight: 600;
+    line-height: 1.25;
+    margin: 0.375rem 0 0.75rem;
+    color: var(--tblr-body-color);
+}
+
+/* ══════════════════════════════════════════
+   八、精选卡片网格(tipitaka index / wiki index 共用)
+   ══════════════════════════════════════════ */
+
+.wiki-featured-grid {
+    display: grid;
+    grid-template-columns: repeat(3, minmax(0, 1fr));
+    gap: 8px;
+}
+
+.wiki-featured-card {
+    border: 1px solid var(--tblr-border-color);
+    border-radius: var(--tblr-border-radius);
+    padding: 10px 12px;
+    cursor: pointer;
+    text-decoration: none;
+    display: block;
+    transition: background 0.12s;
+    color: var(--tblr-body-color);
+}
+
+.wiki-featured-card:hover {
+    background: var(--tblr-bg-surface-secondary);
+    color: var(--tblr-body-color);
+}
+
+.wiki-featured-label {
+    font-size: 0.6875rem;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 0.05em;
+    color: var(--tblr-secondary);
+    margin-bottom: 5px;
+}
+
+.wiki-featured-title {
+    font-size: 0.875rem;
+    font-weight: 500;
+    margin-bottom: 2px;
+}
+
+.wiki-featured-pali {
+    font-size: 0.75rem;
+    font-style: italic;
+    color: var(--tblr-secondary);
+}
+
+@media (max-width: 768px) {
+    .wiki-featured-grid {
+        grid-template-columns: repeat(2, minmax(0, 1fr));
+    }
+}
+
+/* ══════════════════════════════════════════
+   九、作者头像组件(从 _anthology.css 提取)
+   ══════════════════════════════════════════ */
+
+.author-avatar {
+    display: flex;
+    align-items: center;
+    gap: .5rem;
+}
+
+.author-avatar__img,
+.author-avatar__initials {
+    border-radius: 50%;
+    flex-shrink: 0;
+    object-fit: cover;
+}
+
+.author-avatar--sm .author-avatar__img,
+.author-avatar--sm .author-avatar__initials { width: 24px; height: 24px; font-size: .65rem; }
+
+.author-avatar--md .author-avatar__img,
+.author-avatar--md .author-avatar__initials { width: 28px; height: 28px; font-size: .68rem; }
+
+.author-avatar--lg .author-avatar__img,
+.author-avatar--lg .author-avatar__initials { width: 48px; height: 48px; font-size: .95rem; }
+
+.author-avatar__initials {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-weight: 700;
+    color: #fff;
+}
+
+.author-avatar__name {
+    font-size: .8rem;
+    color: var(--tblr-body-color);
+    font-weight: 500;
+    display: block;
+}
+
+.author-avatar--lg .author-avatar__name { font-size: .9rem; }
+
+.author-avatar__sub {
+    font-size: .72rem;
+    color: var(--tblr-secondary);
+    display: block;
+}

+ 64 - 0
api-v13/resources/css/components/_pagination.css

@@ -0,0 +1,64 @@
+/* resources/css/components/_pagination.css
+   全站通用分页组件。
+   来源:wiki.css / wiki-search.css 的 .wiki-pagination 段落(两处相同,已去重)。
+*/
+
+.wiki-pagination {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 4px;
+    padding: 1.5rem 0 0.5rem;
+    flex-wrap: wrap;
+}
+
+.wiki-page-btn {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    min-width: 34px;
+    height: 34px;
+    padding: 0 6px;
+    border-radius: var(--tblr-border-radius);
+    border: 1px solid var(--tblr-border-color);
+    font-size: 0.875rem;
+    color: var(--tblr-body-color);
+    text-decoration: none;
+    background: var(--tblr-bg-surface);
+    transition: background 0.12s, border-color 0.12s;
+    user-select: none;
+}
+
+.wiki-page-btn:hover:not(.wiki-page-btn--active):not(.wiki-page-btn--disabled) {
+    background: var(--tblr-bg-surface-secondary);
+    border-color: var(--tblr-border-color-dark, #adb5bd);
+    color: var(--tblr-body-color);
+    text-decoration: none;
+}
+
+.wiki-page-btn--active {
+    background: var(--tblr-primary);
+    border-color: var(--tblr-primary);
+    color: #fff;
+    font-weight: 500;
+    cursor: default;
+    pointer-events: none;
+}
+
+.wiki-page-btn--disabled {
+    color: var(--tblr-secondary);
+    cursor: default;
+    pointer-events: none;
+    opacity: 0.5;
+}
+
+.wiki-page-ellipsis {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    min-width: 28px;
+    height: 34px;
+    font-size: 0.875rem;
+    color: var(--tblr-secondary);
+    user-select: none;
+}

+ 22 - 0
api-v13/resources/css/components/_search-input.css

@@ -0,0 +1,22 @@
+/* resources/css/components/_search-input.css
+   搜索输入框 + 提示下拉。
+   主要样式由 Tabler input-group 提供,此处补充 WikiPali 专属覆盖。
+*/
+
+.search-box {
+    background: white;
+    border-radius: 0.5rem;
+    padding: 0.5rem;
+    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+    max-width: 500px;
+    margin: 0 auto;
+}
+
+.search-suggest-dropdown {
+    max-height: 300px;
+    overflow-y: auto;
+}
+
+.search-suggest-dropdown .dropdown-item {
+    cursor: pointer;
+}

+ 138 - 0
api-v13/resources/css/components/_search-results.css

@@ -0,0 +1,138 @@
+/* resources/css/components/_search-results.css
+   统一搜索结果页组件样式。
+   来源:wiki-search.css(与 wiki.css 末尾重复内容已去重,以 wiki 栏目为准)。
+   适用于 /library/search?type= 所有类型。
+*/
+
+/* ── 搜索栏 ── */
+.wiki-search-bar-wrap {
+    margin-bottom: 0.875rem;
+}
+
+.wiki-search-form .wiki-search-input {
+    font-size: 0.9375rem;
+}
+
+/* ── 结果摘要行 ── */
+.wiki-search-summary {
+    font-size: 0.875rem;
+    color: var(--tblr-secondary);
+    margin-bottom: 1rem;
+    padding: 0 0.25rem;
+}
+
+.wiki-search-summary strong {
+    color: var(--tblr-body-color);
+    font-weight: 500;
+}
+
+/* ── 搜索结果列表容器 ── */
+.wiki-search-results {
+    padding: 0;
+}
+
+/* ── 搜索结果卡片 ── */
+.wiki-search-card {
+    padding: 1rem 1.5rem;
+    border-bottom: 1px solid var(--tblr-border-color);
+}
+
+.wiki-search-card:last-child {
+    border-bottom: none;
+}
+
+.wiki-search-card:hover {
+    background: var(--tblr-bg-surface-secondary);
+}
+
+.wiki-search-card-header {
+    display: flex;
+    align-items: baseline;
+    gap: 10px;
+    margin-bottom: 5px;
+    flex-wrap: wrap;
+}
+
+.wiki-search-card-title {
+    font-size: 1rem;
+    font-weight: 500;
+    color: var(--tblr-primary);
+    text-decoration: none;
+    display: inline-flex;
+    align-items: baseline;
+    gap: 6px;
+}
+
+.wiki-search-card-title:hover {
+    text-decoration: underline;
+}
+
+.wiki-search-card-word {
+    font-family: "Noto Serif", Georgia, serif;
+    font-size: 0.875rem;
+    font-style: italic;
+    color: var(--tblr-secondary);
+    font-weight: 400;
+}
+
+.wiki-search-card-snippet {
+    font-size: 0.875rem;
+    color: var(--tblr-secondary);
+    line-height: 1.6;
+    margin: 0 0 6px;
+}
+
+.wiki-search-card-snippet mark {
+    background: #faeeda;
+    color: #854f0b;
+    padding: 1px 2px;
+    border-radius: 3px;
+    font-style: normal;
+}
+
+.wiki-search-card-meta {
+    font-size: 0.75rem;
+    color: var(--tblr-secondary);
+    display: flex;
+    align-items: center;
+    gap: 5px;
+}
+
+.wiki-search-card-sep {
+    color: var(--tblr-border-color-dark, #adb5bd);
+}
+
+/* ── 空状态 ── */
+.wiki-empty-state {
+    text-align: center;
+    padding: 3rem 2rem;
+}
+
+.wiki-empty-icon {
+    width: 56px;
+    height: 56px;
+    border-radius: 50%;
+    background: var(--tblr-bg-surface-secondary);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin: 0 auto 1rem;
+    color: var(--tblr-secondary);
+}
+
+.wiki-empty-title {
+    font-size: 1rem;
+    font-weight: 500;
+    margin-bottom: 0.5rem;
+}
+
+.wiki-empty-desc {
+    font-size: 0.875rem;
+    color: var(--tblr-secondary);
+    line-height: 1.6;
+}
+
+.wiki-empty-desc a {
+    color: var(--tblr-primary);
+    text-decoration: none;
+}

+ 92 - 0
api-v13/resources/css/layout/_drawer.css

@@ -0,0 +1,92 @@
+/* resources/css/layout/_drawer.css
+   右侧 mobile 导航抽屉(nav drawer)。
+   来源:main.css 的 .bc-mobile-overlay / .bc-mobile-drawer 段落。
+
+   左侧内容抽屉(TOC、侧边栏)复用 Tabler Offcanvas,
+   通过 <x-ui.drawer side="start|end"> 组件控制方向,无需额外 CSS。
+*/
+
+/* ── 遮罩 ── */
+.bc-mobile-overlay {
+    display: none;
+    position: fixed;
+    inset: 0;
+    background: rgba(0, 0, 0, 0.4);
+    z-index: 1040;
+}
+
+.bc-mobile-overlay.open {
+    display: block;
+}
+
+/* ── 抽屉面板 ── */
+.bc-mobile-drawer {
+    position: fixed;
+    top: 0;
+    right: -100%;
+    width: 240px;
+    height: 100vh;
+    background: var(--wp-card-bg);
+    border-left: 1px solid var(--wp-border);
+    z-index: 1050;
+    transition: right 0.25s ease;
+    padding: 1rem 0;
+    box-shadow: -4px 0 20px rgba(0, 0, 0, 0.1);
+}
+
+.bc-mobile-drawer.open {
+    right: 0;
+}
+
+/* ── 抽屉头部 ── */
+.bc-mobile-drawer-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0.5rem 1.25rem 0.75rem;
+    border-bottom: 1px solid var(--wp-border);
+    margin-bottom: 0.5rem;
+}
+
+.bc-mobile-drawer-header span {
+    font-size: 0.85rem;
+    font-weight: 600;
+    color: var(--wp-ink-soft);
+}
+
+.bc-mobile-drawer-close {
+    background: none;
+    border: none;
+    cursor: pointer;
+    color: var(--wp-ink-muted);
+    font-size: 1rem;
+    line-height: 1;
+    padding: 2px;
+}
+
+/* ── 抽屉导航链接 ── */
+.bc-mobile-nav {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+}
+
+.bc-mobile-nav li a {
+    display: block;
+    padding: 0.65rem 1.25rem;
+    font-size: 0.9rem;
+    color: var(--wp-ink-soft);
+    text-decoration: none;
+    border-bottom: 1px solid rgba(232, 221, 208, 0.5);
+    transition: background 0.15s;
+}
+
+.bc-mobile-nav li a:hover {
+    background: var(--wp-surface-alt);
+    color: var(--wp-brand);
+}
+
+.bc-mobile-nav li a.active {
+    color: var(--wp-brand);
+    font-weight: 600;
+}

+ 4 - 0
api-v13/resources/css/layout/_footer.css

@@ -0,0 +1,4 @@
+/* resources/css/layout/_footer.css
+   全站 footer 样式。
+   当前 library 无独立 footer,此文件预留,待 footer 组件建立后填充。
+*/

+ 85 - 0
api-v13/resources/css/layout/_grid.css

@@ -0,0 +1,85 @@
+/* resources/css/layout/_grid.css
+   全站断点定义 + 通用栏容器。
+   包含从 _wiki.css 提取的三栏布局,供所有栏目复用。
+*/
+
+/* ── 断点 token ── */
+:root {
+    --bp-sm:  576px;
+    --bp-md:  768px;
+    --bp-lg:  992px;
+    --bp-xl:  1200px;
+}
+
+/* ══════════════════════════════════════════
+   三栏布局(原 .wiki-layout,全站通用)
+   ══════════════════════════════════════════ */
+
+.wiki-layout {
+    display: grid;
+    grid-template-columns: 200px 1fr 200px;
+    grid-template-areas: "left main right";
+    gap: 1.5rem;
+    align-items: start;
+    padding-top: 1.5rem;
+    padding-bottom: 3rem;
+}
+
+.wiki-sidebar-left  { grid-area: left; }
+.wiki-sidebar-right { grid-area: right; }
+
+.wiki-main {
+    grid-area: main;
+    min-width: 0;
+    display: flex;
+    flex-direction: column;
+    gap: 1rem;
+}
+
+@media (max-width: 992px) {
+    .wiki-layout {
+        grid-template-columns: 180px 1fr;
+        grid-template-areas:
+            "left main"
+            "left right";
+    }
+
+    .wiki-sidebar-right {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 1rem;
+    }
+
+    .wiki-sidebar-right .wiki-sidebar-section {
+        flex: 1 1 180px;
+        margin-bottom: 0;
+    }
+}
+
+@media (max-width: 768px) {
+    .wiki-layout {
+        grid-template-columns: 1fr;
+        grid-template-areas: "main" "right" "left";
+    }
+
+    .wiki-sidebar-left,
+    .wiki-sidebar-right {
+        display: none;
+    }
+}
+
+/* ── 通用两栏容器(预留) ── */
+.layout-2col {
+    display: grid;
+    grid-template-columns: 220px 1fr;
+    gap: 1.5rem;
+    align-items: start;
+    padding-top: 1.5rem;
+    padding-bottom: 3rem;
+}
+
+@media (max-width: 768px) {
+    .layout-2col {
+        grid-template-columns: 1fr;
+    }
+}

+ 76 - 0
api-v13/resources/css/layout/_hero.css

@@ -0,0 +1,76 @@
+/* resources/css/layout/_hero.css
+   Hero 区域(封面图 + 叠加层 + 标题文字)。
+   来源:main.css 的 .hero-section、.hero-overlay、.hero-content 段落。
+*/
+
+.hero-wrapper {
+    position: relative;
+}
+
+.hero-section {
+    height: 250px;
+    width: 100%;
+    background-size: cover;
+    background-position: center;
+    background-repeat: no-repeat;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.hero-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.2);
+}
+
+.hero-content {
+    position: relative;
+    z-index: 2;
+    text-align: center;
+    color: white;
+    max-width: 600px;
+    padding: 0 1rem;
+}
+
+.hero-title {
+    font-size: 2.5rem;
+    font-weight: bold;
+    margin-bottom: 1rem;
+    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
+}
+
+.hero-subtitle {
+    font-size: 1.2rem;
+    margin-bottom: 2rem;
+    text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
+}
+
+/* ── 响应式 ── */
+@media (max-width: 768px) {
+    .hero-section {
+        height: 250px;
+    }
+
+    .hero-title {
+        font-size: 2rem;
+    }
+
+    .hero-subtitle {
+        font-size: 1rem;
+    }
+}
+
+@media (max-width: 576px) {
+    .hero-title {
+        font-size: 1.5rem;
+    }
+
+    .hero-subtitle {
+        font-size: 0.9rem;
+    }
+}

+ 119 - 0
api-v13/resources/css/layout/_navbar.css

@@ -0,0 +1,119 @@
+/* resources/css/layout/_navbar.css
+   顶部导航栏:面包屑 bar + desktop 导航链接 + mobile 汉堡按钮。
+   来源:main.css 的 .anthology-breadcrumb-bar、.bc-nav、.bc-hamburger 段落。
+   以 wiki.css 中同名规则为准(main.css 无冲突,wiki.css 中无同名规则,直接迁移)。
+*/
+
+/* ── Breadcrumb bar ── */
+.anthology-breadcrumb-bar {
+    background: rgba(255, 255, 255, 0.55);
+    border-bottom: 1px solid var(--wp-border);
+    padding: 0.5rem 0;
+}
+
+.anthology-breadcrumb-bar .bc-inner {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 1rem;
+}
+
+.anthology-breadcrumb-bar .breadcrumb {
+    margin: 0;
+    font-size: 0.78rem;
+    flex-shrink: 0;
+}
+
+.anthology-breadcrumb-bar .breadcrumb-item a {
+    color: var(--wp-brand);
+    text-decoration: none;
+}
+
+.anthology-breadcrumb-bar .breadcrumb-item.active {
+    color: var(--wp-ink-muted);
+}
+
+.anthology-breadcrumb-bar .breadcrumb-item + .breadcrumb-item::before {
+    color: var(--wp-ink-muted);
+}
+
+/* ── Hero 覆盖状态:breadcrumb bar 透明悬浮于 hero 之上 ── */
+.hero-wrapper:has(.hero-section) .anthology-breadcrumb-bar {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 10;
+    background: transparent;
+    border-bottom: none;
+}
+
+.hero-wrapper:has(.hero-section) .bc-nav li a {
+    color: white;
+}
+
+.hero-wrapper:has(.hero-section) .bc-nav li a:hover {
+    color: rgba(255, 255, 255, 0.75);
+}
+
+.hero-wrapper:has(.hero-section) .bc-hamburger {
+    border-color: rgba(255, 255, 255, 0.6);
+    color: white;
+}
+
+/* ── Desktop 导航链接 ── */
+.bc-nav {
+    display: flex;
+    align-items: center;
+    gap: 1.25rem;
+    list-style: none;
+    margin: 0;
+    padding: 0;
+    flex-shrink: 0;
+}
+
+.bc-nav li a {
+    font-size: 0.82rem;
+    color: var(--wp-ink-soft);
+    text-decoration: none;
+    white-space: nowrap;
+    transition: color 0.15s;
+}
+
+.bc-nav li a:hover {
+    color: var(--wp-brand);
+}
+
+.bc-nav li a.active {
+    color: var(--wp-brand);
+    font-weight: 600;
+}
+
+/* ── 汉堡按钮(mobile) ── */
+.bc-hamburger {
+    display: none;
+    background: none;
+    border: 1px solid var(--wp-border);
+    border-radius: 5px;
+    padding: 4px 8px;
+    cursor: pointer;
+    color: var(--wp-ink-soft);
+    line-height: 1;
+}
+
+.bc-hamburger:hover {
+    border-color: var(--wp-brand);
+    color: var(--wp-brand);
+}
+
+/* ── 响应式 ── */
+@media (max-width: 640px) {
+    .bc-nav {
+        display: none;
+    }
+
+    .bc-hamburger {
+        display: inline-flex;
+        align-items: center;
+    }
+}

+ 4 - 0
api-v13/resources/css/layout/_toolbar.css

@@ -0,0 +1,4 @@
+/* resources/css/layout/_toolbar.css
+   页内工具条(过滤器、排序、批量操作等)。
+   当前以 Tabler .page-header 为基础,此文件预留扩展。
+*/

+ 33 - 0
api-v13/resources/css/library.css

@@ -0,0 +1,33 @@
+/* resources/css/library.css
+   library/* 页面 CSS 入口。
+   只做 @import,不写任何样式规则。
+   各栏目专属样式通过页面级 @push('styles') 按需追加。
+*/
+
+/* 1. Tabler 核心 */
+@import "@tabler/core/dist/css/tabler.min.css";
+@import "@tabler/icons-webfont/dist/tabler-icons.min.css";
+
+/* 2. 全站字体 */
+@import url("https://fonts.googleapis.com/css2?family=Noto+Serif:ital,wght@0,400;0,600;1,400&display=swap");
+
+/* 3. 基础变量 */
+@import "./base/_variables.css";
+@import "./base/_reset.css";
+@import "./base/_typography.css";
+
+/* 4. 布局层 */
+@import "./layout/_grid.css";
+@import "./layout/_navbar.css";
+@import "./layout/_drawer.css";
+@import "./layout/_hero.css";
+@import "./layout/_footer.css";
+@import "./layout/_toolbar.css";
+
+/* 5. 公共组件 */
+@import "./components/_search-input.css";
+@import "./components/_search-results.css";
+@import "./components/_card.css";
+@import "./components/_card-book.css";
+@import "./components/_badge.css";
+@import "./components/_pagination.css";

+ 284 - 0
api-v13/resources/css/modules/_anthology.css

@@ -0,0 +1,284 @@
+/* resources/css/modules/_anthology.css
+   文集栏目专属样式。
+   已提取到公共层:
+     - components/_card-book.css → .book-cover 封面组件
+     - components/_card.css      → .author-avatar 头像组件
+*/
+
+/* ══════════════════════════════════════════
+   一、文集卡片(.anthology-card)横向布局
+   ══════════════════════════════════════════ */
+
+.anthology-card {
+    background: var(--wp-card-bg);
+    border: 1px solid var(--wp-border);
+    border-radius: 10px;
+    overflow: hidden;
+    display: flex;
+    transition: box-shadow .25s, transform .25s;
+    margin-bottom: 1.1rem;
+    text-decoration: none;
+    color: inherit;
+}
+
+.anthology-card:hover {
+    box-shadow: 0 8px 28px rgba(200,134,10,.12), 0 2px 8px rgba(0,0,0,.06);
+    transform: translateY(-2px);
+    color: inherit;
+    text-decoration: none;
+}
+
+.anthology-card .book-cover { border-radius: 0; }
+
+.anthology-card__body {
+    padding: 1.1rem 1.4rem;
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    min-width: 0;
+}
+
+.anthology-card__title {
+    font-family: 'Noto Serif SC', 'Noto Serif', Georgia, serif;
+    font-size: 1.1rem;
+    font-weight: 600;
+    color: var(--wp-ink);
+    margin-bottom: .35rem;
+    line-height: 1.4;
+}
+
+.anthology-card:hover .anthology-card__title { color: var(--wp-brand); }
+
+.anthology-card__desc {
+    font-size: .8rem;
+    color: var(--wp-ink-muted);
+    margin-bottom: .65rem;
+    line-height: 1.65;
+}
+
+.anthology-card__author { margin-bottom: .7rem; }
+
+.anthology-card__tags {
+    display: flex;
+    flex-wrap: wrap;
+    gap: .3rem;
+    margin-top: auto;
+}
+
+.anthology-tag {
+    font-size: .7rem;
+    color: var(--wp-ink-muted);
+    background: var(--wp-brand-light);
+    border: 1px solid var(--wp-border);
+    padding: 1px 7px;
+    border-radius: 4px;
+    white-space: nowrap;
+}
+
+.anthology-tag--more {
+    background: transparent;
+    border-color: transparent;
+    color: var(--wp-brand);
+}
+
+.anthology-card__meta {
+    display: flex;
+    align-items: center;
+    gap: .85rem;
+    margin-top: .65rem;
+    padding-top: .65rem;
+    border-top: 1px solid var(--wp-border);
+}
+
+.anthology-meta-item {
+    font-size: .72rem;
+    color: var(--wp-ink-muted);
+    display: flex;
+    align-items: center;
+    gap: .25rem;
+}
+
+/* ══════════════════════════════════════════
+   二、页面头部(index 页)
+   ══════════════════════════════════════════ */
+
+.anthology-page-header {
+    background: linear-gradient(135deg, var(--wp-ink) 0%, #2d2010 100%);
+    padding: 2.25rem 0 2rem;
+    position: relative;
+    overflow: hidden;
+}
+
+.anthology-page-header::before {
+    content: '藏';
+    font-family: 'Noto Serif SC', serif;
+    font-size: 16rem;
+    font-weight: 700;
+    color: rgba(255,255,255,.03);
+    position: absolute;
+    right: -1rem;
+    top: -2.5rem;
+    line-height: 1;
+    pointer-events: none;
+}
+
+.anthology-page-header h1 {
+    font-family: 'Noto Serif SC', 'Noto Serif', Georgia, serif;
+    font-size: 1.75rem;
+    font-weight: 600;
+    color: #fff;
+    margin: 0 0 .3rem;
+    letter-spacing: .08em;
+}
+
+.anthology-page-header p { color: rgba(255,255,255,.45); font-size: .85rem; margin: 0; }
+
+.result-badge {
+    background: var(--wp-brand);
+    color: var(--wp-ink);
+    font-size: .75rem;
+    font-weight: 700;
+    padding: 2px 9px;
+    border-radius: 20px;
+    margin-left: .6rem;
+    vertical-align: middle;
+}
+
+/* ══════════════════════════════════════════
+   三、侧边栏卡片(index + show 共用)
+   ══════════════════════════════════════════ */
+
+.sb-card {
+    background: var(--wp-card-bg);
+    border: 1px solid var(--wp-border);
+    border-radius: 10px;
+    overflow: hidden;
+    margin-bottom: 1.15rem;
+}
+
+.sb-head {
+    padding: .8rem 1.2rem;
+    border-bottom: 1px solid var(--wp-border);
+    font-family: 'Noto Serif SC', 'Noto Serif', Georgia, serif;
+    font-size: .875rem;
+    font-weight: 600;
+    color: var(--wp-ink-soft);
+    letter-spacing: .04em;
+    display: flex;
+    align-items: center;
+    gap: .45rem;
+}
+
+.sb-head::before {
+    content: '';
+    display: block;
+    width: 3px;
+    height: 13px;
+    background: var(--wp-brand);
+    border-radius: 2px;
+}
+
+.smeta-row {
+    display: flex;
+    padding: .7rem 1.2rem;
+    border-bottom: 1px solid var(--wp-border);
+    font-size: .8rem;
+    align-items: flex-start;
+    gap: .45rem;
+}
+.smeta-row:last-child { border-bottom: none; }
+.smeta-label { color: var(--wp-ink-muted); min-width: 65px; flex-shrink: 0; }
+.smeta-value { color: var(--wp-ink-soft); font-weight: 500; }
+.smeta-value a { color: var(--wp-brand); text-decoration: none; }
+.smeta-value a:hover { text-decoration: underline; }
+
+.author-block { display: flex; align-items: center; gap: .8rem; padding: 1.1rem 1.2rem; }
+.author-block-name { font-weight: 600; font-size: .9rem; color: var(--wp-ink); margin-bottom: .18rem; }
+.author-block-stats { font-size: .75rem; color: var(--wp-ink-muted); }
+.author-bio {
+    font-size: .78rem; color: var(--wp-ink-muted); line-height: 1.65;
+    padding: .9rem 1.2rem 1.1rem;
+    border-top: 1px solid var(--wp-border);
+}
+
+.related-ul { list-style: none; padding: 0; margin: 0; }
+.related-ul li a {
+    display: flex; align-items: center; gap: .7rem;
+    padding: .7rem 1.2rem;
+    border-bottom: 1px solid var(--wp-border);
+    text-decoration: none;
+    transition: background .15s;
+}
+.related-ul li:last-child a { border-bottom: none; }
+.related-ul li a:hover { background: var(--wp-surface-alt); }
+.related-t { font-size: .8rem; color: var(--wp-ink-soft); font-weight: 500; margin-bottom: .18rem; line-height: 1.3; }
+.related-ul li a:hover .related-t { color: var(--wp-brand); }
+.related-a { font-size: .7rem; color: var(--wp-ink-muted); }
+
+.author-ul { list-style: none; padding: .35rem 0; margin: 0; }
+.author-ul li a {
+    display: flex; align-items: center; gap: .6rem;
+    padding: .45rem 1.15rem;
+    text-decoration: none;
+    transition: background .15s;
+}
+.author-ul li a:hover { background: var(--wp-surface-alt); }
+
+/* ══════════════════════════════════════════
+   四、文章目录(show 页)
+   ══════════════════════════════════════════ */
+
+.sec-card { background: var(--wp-card-bg); border: 1px solid var(--wp-border); border-radius: 10px; overflow: hidden; margin-bottom: 1.3rem; }
+.sec-header { padding: .85rem 1.4rem; border-bottom: 1px solid var(--wp-border); display: flex; align-items: center; gap: .55rem; }
+.sec-bar { width: 3px; height: 15px; background: var(--wp-brand); border-radius: 2px; flex-shrink: 0; }
+.sec-title { font-family: 'Noto Serif SC', 'Noto Serif', Georgia, serif; font-size: .9rem; font-weight: 600; color: var(--wp-ink-soft); letter-spacing: .04em; }
+.sec-count { margin-left: auto; font-size: .75rem; color: var(--wp-ink-muted); background: var(--wp-brand-light); padding: 2px 8px; border-radius: 10px; }
+.sec-body { padding: 1.15rem 1.4rem; font-size: .855rem; color: var(--wp-ink-soft); line-height: 1.95; }
+.sec-body p { margin-bottom: .8rem; }
+.sec-body p:last-child { margin-bottom: 0; }
+
+.toc-ul { list-style: none; padding: .35rem 0; margin: 0; }
+.toc-ul li a { display: flex; align-items: center; padding: .65rem 1.4rem; text-decoration: none; border-bottom: 1px solid rgba(232,221,208,.5); transition: background .15s; }
+.toc-ul li:last-child a { border-bottom: none; }
+.toc-ul li a:hover { background: var(--wp-surface-alt); }
+.toc-num { font-size: .72rem; color: var(--wp-ink-muted); width: 26px; flex-shrink: 0; }
+.toc-name { font-size: .855rem; color: var(--wp-ink-soft); flex: 1; line-height: 1.4; }
+.toc-ul li a:hover .toc-name { color: var(--wp-brand); }
+.toc-arrow { color: var(--wp-border); font-size: .85rem; }
+.toc-ul li a:hover .toc-arrow { color: var(--wp-brand); }
+
+/* ══════════════════════════════════════════
+   五、Hero(show 页)
+   ══════════════════════════════════════════ */
+
+.anthology-hero { background: linear-gradient(135deg, var(--wp-ink) 0%, #2d2010 100%); padding: 2.5rem 0; }
+.hero-inner { display: flex; gap: 2.25rem; align-items: flex-start; }
+.hero-content { flex: 1; min-width: 0; }
+
+.hero-title { font-family: 'Noto Serif SC', 'Noto Serif', Georgia, serif; font-size: 1.75rem; font-weight: 700; color: #fff; line-height: 1.3; margin-bottom: .4rem; }
+.hero-subtitle { font-size: .88rem; color: rgba(255,255,255,.45); font-style: italic; letter-spacing: .04em; margin-bottom: 1.1rem; }
+.hero-tags { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: 1.3rem; }
+.hero-tag { font-size: .72rem; padding: 2px 9px; border-radius: 20px; background: rgba(200,134,10,.2); color: var(--wp-brand); border: 1px solid rgba(200,134,10,.3); }
+.hero-info-row { display: flex; flex-wrap: wrap; gap: 1.4rem; margin-bottom: 1.3rem; }
+.hi-item { display: flex; align-items: center; gap: .45rem; }
+.hi-label { font-size: .72rem; color: rgba(255,255,255,.4); letter-spacing: .04em; display: block; }
+.hi-value { font-size: .83rem; color: rgba(255,255,255,.82); display: block; }
+.hero-desc { font-size: .85rem; color: rgba(255,255,255,.6); line-height: 1.85; margin-bottom: 1.6rem; max-width: 600px; }
+
+.btn-read-primary { background: var(--wp-brand); color: var(--wp-ink); font-weight: 700; font-size: .88rem; padding: .55rem 1.6rem; border-radius: 6px; border: none; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; gap: .45rem; transition: background .2s, transform .15s; }
+.btn-read-primary:hover { background: #dea020; color: var(--wp-ink); transform: translateY(-1px); }
+.btn-outline-hero { background: transparent; color: rgba(255,255,255,.7); font-size: .85rem; padding: .5rem 1.3rem; border-radius: 6px; border: 1px solid rgba(255,255,255,.2); cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; gap: .4rem; transition: all .2s; margin-left: .65rem; }
+.btn-outline-hero:hover { border-color: rgba(255,255,255,.5); color: #fff; }
+
+/* ══════════════════════════════════════════
+   六、响应式
+   ══════════════════════════════════════════ */
+
+@media (max-width: 900px) {
+    .hero-inner { flex-direction: column; align-items: center; }
+}
+
+@media (max-width: 768px) {
+    .anthology-card { flex-direction: column; }
+    .anthology-card .book-cover--md { width: 100%; min-width: unset; height: 90px; }
+}

+ 308 - 0
api-v13/resources/css/modules/_library-index.css

@@ -0,0 +1,308 @@
+/* resources/css/modules/_library-index.css
+   Library 门户首页专属样式。
+*/
+
+/* ══════════════════════════════════════════
+   一、区块通用结构
+   ══════════════════════════════════════════ */
+
+.lib-section {
+    margin-top: 2rem;
+    margin-bottom: 0.5rem;
+}
+
+.lib-section__header {
+    display: flex;
+    align-items: center;
+    gap: 0.75rem;
+    margin-bottom: 1rem;
+    padding-bottom: 0.625rem;
+    border-bottom: 1px solid var(--tblr-border-color);
+}
+
+.lib-section__title {
+    font-size: 1rem;
+    font-weight: 600;
+    color: var(--tblr-body-color);
+    margin: 0;
+    display: flex;
+    align-items: center;
+    gap: 0.4rem;
+    flex: 1;
+}
+
+.lib-section__title .ti {
+    font-size: 1.125rem;
+    color: var(--tblr-secondary);
+}
+
+.lib-section__more {
+    font-size: 0.8125rem;
+    color: var(--tblr-primary);
+    text-decoration: none;
+    white-space: nowrap;
+    display: flex;
+    align-items: center;
+    gap: 0.25rem;
+    flex-shrink: 0;
+}
+
+.lib-section__more:hover {
+    text-decoration: underline;
+}
+
+/* ══════════════════════════════════════════
+   二、三藏分类卡片头部
+   ══════════════════════════════════════════ */
+
+.lib-cat-card__head {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 0.75rem;
+}
+
+.lib-cat-card__name {
+    font-size: 0.9375rem;
+    font-weight: 600;
+    color: var(--tblr-body-color);
+}
+
+.lib-cat-card__more {
+    font-size: 0.75rem;
+    color: var(--tblr-primary);
+    text-decoration: none;
+    display: flex;
+    align-items: center;
+    gap: 0.2rem;
+    flex-shrink: 0;
+}
+
+.lib-cat-card__more:hover {
+    text-decoration: underline;
+}
+
+/* ══════════════════════════════════════════
+   三、"持续更新中" 标签
+   ══════════════════════════════════════════ */
+
+.lib-live-badge {
+    display: inline-flex;
+    align-items: center;
+    gap: 5px;
+    font-size: 0.6875rem;
+    font-weight: 500;
+    color: #3b6d11;
+    background: #eaf3de;
+    border: 1px solid #c0dd97;
+    border-radius: 20px;
+    padding: 2px 8px;
+    margin-left: 0.25rem;
+}
+
+.lib-live-dot {
+    width: 6px;
+    height: 6px;
+    border-radius: 50%;
+    background: #639922;
+    flex-shrink: 0;
+    animation: lib-live-pulse 2s ease-in-out infinite;
+}
+
+@keyframes lib-live-pulse {
+    0%, 100% { opacity: 1; }
+    50%       { opacity: 0.4; }
+}
+
+/* ══════════════════════════════════════════
+   四、最新译文列表
+   ══════════════════════════════════════════ */
+
+.lib-recent {
+    padding: 0;   /* 覆盖 wiki-card 默认 padding,由 item 自己管理 */
+}
+
+.lib-recent__item {
+    display: flex;
+    align-items: center;
+    gap: 0.875rem;
+    padding: 0.75rem 1.25rem;
+    border-bottom: 1px solid var(--tblr-border-color);
+    text-decoration: none;
+    color: inherit;
+    transition: background 0.12s;
+}
+
+.lib-recent__item:last-child {
+    border-bottom: none;
+}
+
+.lib-recent__item:hover {
+    background: var(--tblr-bg-surface-secondary);
+    color: inherit;
+    text-decoration: none;
+}
+
+/* 封面缩略图固定不缩 */
+.lib-recent__item .book-cover {
+    flex-shrink: 0;
+}
+
+.lib-recent__info {
+    flex: 1;
+    min-width: 0;
+}
+
+.lib-recent__title {
+    font-size: 0.9375rem;
+    font-weight: 500;
+    color: var(--tblr-body-color);
+    margin-bottom: 0.25rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.lib-recent__item:hover .lib-recent__title {
+    color: var(--tblr-primary);
+}
+
+.lib-recent__meta {
+    font-size: 0.75rem;
+    color: var(--tblr-secondary);
+    display: flex;
+    align-items: center;
+    gap: 0.375rem;
+    flex-wrap: wrap;
+}
+
+.lib-recent__sep {
+    opacity: 0.5;
+}
+
+.lib-recent__right {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-end;
+    gap: 4px;
+    flex-shrink: 0;
+}
+
+.lib-recent__time {
+    font-size: 0.75rem;
+    color: var(--tblr-secondary);
+    white-space: nowrap;
+}
+
+/* 新增 / 更新 徽章 */
+.lib-new-badge {
+    font-size: 0.625rem;
+    font-weight: 600;
+    padding: 1px 6px;
+    border-radius: 20px;
+    background: #e6f1fb;
+    color: #185fa5;
+    border: 1px solid #b5d4f4;
+    white-space: nowrap;
+}
+
+.lib-update-badge {
+    font-size: 0.625rem;
+    font-weight: 600;
+    padding: 1px 6px;
+    border-radius: 20px;
+    background: #f1efe8;
+    color: #5f5e5a;
+    border: 1px solid #d3d1c7;
+    white-space: nowrap;
+}
+
+/* ══════════════════════════════════════════
+   五、栏目导航卡片
+   ══════════════════════════════════════════ */
+
+.lib-nav-card {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    text-align: center;
+    padding: 1.25rem 1rem;
+    background: var(--tblr-bg-surface);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: var(--tblr-border-radius-lg);
+    text-decoration: none;
+    color: inherit;
+    transition: background 0.12s, transform 0.15s, box-shadow 0.15s;
+    height: 100%;
+}
+
+.lib-nav-card:hover {
+    background: var(--tblr-bg-surface-secondary);
+    transform: translateY(-2px);
+    box-shadow: 0 4px 12px rgba(0,0,0,.06);
+    color: inherit;
+    text-decoration: none;
+}
+
+.lib-nav-card__icon {
+    font-size: 1.75rem;
+    color: var(--tblr-primary);
+    margin-bottom: 0.625rem;
+    display: block;
+}
+
+.lib-nav-card__name {
+    font-size: 0.9375rem;
+    font-weight: 600;
+    color: var(--tblr-body-color);
+    margin-bottom: 0.25rem;
+}
+
+.lib-nav-card__desc {
+    font-size: 0.75rem;
+    color: var(--tblr-secondary);
+    line-height: 1.4;
+}
+
+/* ══════════════════════════════════════════
+   六、响应式
+   ══════════════════════════════════════════ */
+
+/* 最新译文:手机隐藏作者,只显示标题+分类+时间 */
+@media (max-width: 576px) {
+    .lib-recent__item {
+        padding: 0.625rem 1rem;
+        gap: 0.625rem;
+    }
+
+    .lib-recent__author,
+    .lib-recent__sep:last-of-type {
+        display: none;
+    }
+
+    .lib-recent__title {
+        font-size: 0.875rem;
+    }
+}
+
+/* 栏目导航:手机 2 列,平板 3 列,桌面 5 列(Bootstrap row g-3 自动处理) */
+@media (max-width: 575px) {
+    .lib-nav-card {
+        padding: 1rem 0.75rem;
+    }
+
+    .lib-nav-card__icon {
+        font-size: 1.5rem;
+    }
+
+    .lib-nav-card__desc {
+        display: none;   /* 手机隐藏描述,只显示图标+名称 */
+    }
+}
+
+/* 三藏分类卡片:手机 2 列已由 Bootstrap col-6 处理 */
+@media (max-width: 575px) {
+    .lib-cat-card__more {
+        display: none;  /* 手机隐藏"更多"链接,避免挤压 */
+    }
+}

+ 313 - 0
api-v13/resources/css/modules/_reader.css

@@ -0,0 +1,313 @@
+/* resources/css/modules/_reader.css
+   全站阅读页专属样式。
+   来源:原 resources/css/reader.css(旧)+ reader 重构新增内容合并。
+   以旧 reader.css 内容为准,新增部分追加在末尾。
+*/
+
+/* ══════════════════════════════════════════
+   一、基础
+   ══════════════════════════════════════════ */
+
+body {
+    font-family: "Inter", sans-serif;
+    transition: background-color 0.3s, color 0.3s;
+}
+
+/* ══════════════════════════════════════════
+   二、主布局容器
+   ══════════════════════════════════════════ */
+
+.main-container {
+    display: flex;
+    gap: 20px;
+    padding: 20px;
+    max-width: 1400px;
+    margin: 0 auto;
+}
+
+/* ── TOC 侧边栏(左) ── */
+.toc-sidebar {
+    width: 250px;
+    flex-shrink: 0;
+    display: none;
+    position: sticky;
+    top: 0;
+    height: 100vh;
+    overflow-y: auto;
+    overflow-x: hidden;
+    scrollbar-width: thin;
+}
+
+.toc-sidebar .card-body {
+    height: 100%;
+    overflow-y: auto;
+    overflow-x: hidden;
+}
+
+/* ── 正文区(中)── */
+.content-area {
+    flex: 1;            /* 替代原 flex-grow:1,配合 min-width 防止收缩 */
+    min-width: 0;       /* 修复:内容较窄时列不收缩 */
+}
+
+/* ── 右侧边栏 ── */
+.right-sidebar {
+    width: 220px;       /* 收窄,原 300px 偏宽 */
+    flex-shrink: 0;
+    display: none;
+}
+
+.related-books {
+    margin-top: 30px;
+}
+
+.card-img-container {
+    height: 150px;
+    overflow: hidden;
+}
+
+.card-img-container img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+}
+
+/* ══════════════════════════════════════════
+   三、响应式
+   ══════════════════════════════════════════ */
+
+@media (max-width: 767px) {
+    .content-area {
+        width: 100%;
+    }
+
+    .main-container {
+        padding: 0;
+    }
+
+    .card {
+        border: none;
+    }
+}
+
+@media (min-width: 768px) {
+    .toc-sidebar {
+        display: block;
+    }
+    /* content-area 不设 max-width,由 flex:1 + min-width:0 自动占满剩余空间 */
+}
+
+@media (min-width: 992px) {
+    .right-sidebar {
+        display: block;
+    }
+    /* 同上,不限制 max-width */
+}
+
+/* ══════════════════════════════════════════
+   四、暗色模式
+   ══════════════════════════════════════════ */
+
+.dark-mode {
+    background-color: #1a1a1a;
+    color: #ffffff;
+}
+
+.dark-mode .card {
+    background-color: #2a2a2a;
+    border-color: #3a3a3a;
+    color: #ffffff;
+}
+
+.dark-mode .navbar {
+    background-color: #2a2a2a;
+}
+
+.dark-mode .offcanvas {
+    background-color: #2a2a2a;
+    color: #ffffff;
+}
+
+.dark-mode .offcanvas .nav-link {
+    color: #ffffff;
+}
+
+.dark-mode .toc-sidebar,
+.dark-mode .right-sidebar {
+    background-color: #2a2a2a;
+}
+
+/* ══════════════════════════════════════════
+   五、目录样式(wiki toc 风格)
+   ══════════════════════════════════════════ */
+
+.toc-sidebar ul,
+.offcanvas-body ul {
+    list-style: none;
+    padding: 0;
+}
+
+.toc-sidebar ul li,
+.offcanvas-body ul li {
+    padding: 5px 0;
+}
+
+.toc-sidebar ul li a,
+.offcanvas-body ul li a {
+    color: #206bc4;
+    text-decoration: none;
+}
+
+.toc-sidebar ul li a:hover      { text-decoration: none; }
+.offcanvas-body ul li a:hover   { text-decoration: underline; }
+
+.dark-mode .toc-sidebar ul li a,
+.dark-mode .offcanvas-body ul li a {
+    color: #4dabf7;
+}
+
+.toc-level-1 { padding-left: 0 !important; }
+.toc-level-2 { padding-left: 16px !important; }
+.toc-level-3 { padding-left: 24px !important; }
+.toc-level-4 { padding-left: 36px !important; }
+
+.toc-disabled {
+    color: #6c757d;
+    cursor: not-allowed;
+    pointer-events: none;
+}
+
+.dark-mode .toc-disabled { color: #adb5bd; }
+
+.toc_item {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.toc_item:hover {
+    color: #206bc4 !important;
+    background: rgba(32, 107, 196, 0.15);
+    border-radius: 2px;
+    cursor: pointer;
+}
+
+.toc_item a,
+.toc_item span {
+    display: block;
+    width: 100%;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.toc-active {
+    color: #206bc4 !important;
+    font-weight: 600;
+    background: rgba(32, 107, 196, 0.08);
+    border-left: 3px solid #206bc4;
+    border-radius: 2px;
+    cursor: default;
+    pointer-events: none;
+}
+
+.dark-mode .toc-active {
+    color: #4dabf7 !important;
+    background: rgba(77, 171, 247, 0.1);
+    border-left-color: #4dabf7;
+}
+
+/* ══════════════════════════════════════════
+   六、正文内容
+   ══════════════════════════════════════════ */
+
+.origin {
+    color: darkred;
+}
+
+/* 术语引用 */
+.term-ref {
+    cursor: pointer;
+    text-decoration: underline dotted;
+    text-underline-offset: 4px;
+}
+
+.term-ref:hover {
+    color: var(--tblr-primary);
+}
+
+.term_invalid {
+    color: red;
+}
+
+/* ══════════════════════════════════════════
+   七、Navbar 图标尺寸修正
+   ti 图标显式设定字号,防止继承后偏小
+   ══════════════════════════════════════════ */
+
+.navbar .nav-link .ti {
+    font-size: 1.125rem;
+    vertical-align: middle;
+}
+
+/* ══════════════════════════════════════════
+   八、版本卡片(右侧边栏,wiki 侧边栏同款)
+   ══════════════════════════════════════════ */
+
+.reader-channel-card {
+    background: var(--tblr-bg-surface);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: var(--tblr-border-radius-lg);
+    padding: 1rem 1.125rem;
+    margin-bottom: 1rem;
+}
+
+.dark-mode .reader-channel-card {
+    background-color: #2a2a2a;
+    border-color: #3a3a3a;
+}
+
+.reader-channel-title {
+    font-size: 0.6875rem;
+    font-weight: 500;
+    letter-spacing: 0.05em;
+    text-transform: uppercase;
+    color: var(--tblr-secondary);
+    margin-bottom: 0.75rem;
+}
+
+.reader-channel-list {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+}
+
+.reader-channel-list li {
+    margin-bottom: 2px;
+}
+
+.reader-channel-list a {
+    display: block;
+    font-size: 0.8125rem;
+    color: var(--tblr-body-color);
+    text-decoration: none;
+    padding: 5px 8px;
+    border-radius: var(--tblr-border-radius);
+    transition: background 0.12s;
+}
+
+.reader-channel-list a:hover {
+    background: var(--tblr-bg-surface-secondary);
+}
+
+.reader-channel-list a.active {
+    background: var(--tblr-bg-surface-secondary);
+    font-weight: 500;
+    color: var(--tblr-primary);
+}
+
+.reader-channel-lang {
+    font-size: 0.75rem;
+    color: var(--tblr-secondary);
+    display: block;
+}

+ 397 - 0
api-v13/resources/css/modules/_tipitaka.css

@@ -0,0 +1,397 @@
+/* resources/css/modules/_tipitaka.css
+   Tipitaka 栏目专属样式。
+   来源:book-list.blade.php / book-item.blade.php 内联样式提取,
+   去除 CDN 引入,风格与 wiki 对齐。
+*/
+
+/* ══════════════════════════════════════════
+   一、书籍卡片(.card-book)
+   纵向:封面上 + 信息下
+   ══════════════════════════════════════════ */
+
+.card-book {
+    transition: transform 0.2s, box-shadow 0.2s;
+}
+
+.card-book:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
+}
+
+.card-book__link {
+    display: block;
+    text-decoration: none;
+    color: inherit;
+}
+
+.card-book__link:hover {
+    text-decoration: none;
+    color: inherit;
+}
+
+/* 封面:撑满卡片宽度 */
+.card-book .book-cover--md {
+    width: 100%;
+    min-width: unset;
+    height: 200px;
+    border-radius: var(--tblr-border-radius);
+}
+
+@media (max-width: 768px) {
+    .card-book .book-cover--md {
+        height: 150px;
+    }
+}
+
+/* 信息区 */
+.card-book__info {
+    padding: 0.75rem 0 0;
+}
+
+.card-book__title {
+    font-size: 0.9375rem;
+    font-weight: 600;
+    color: var(--tblr-body-color);
+    margin-bottom: 0.25rem;
+    line-height: 1.4;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+}
+
+.card-book:hover .card-book__title {
+    color: var(--tblr-primary);
+}
+
+.card-book__author {
+    font-size: 0.8125rem;
+    color: var(--tblr-secondary);
+    margin-bottom: 0.25rem;
+}
+
+.card-book__publisher {
+    font-size: 0.8125rem;
+    color: var(--tblr-secondary);
+    margin-bottom: 0.375rem;
+}
+
+.card-book__publisher-link {
+    color: var(--tblr-primary);
+    text-decoration: none;
+}
+
+.card-book__publisher-link:hover {
+    text-decoration: underline;
+}
+
+.card-book__badges {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 4px;
+    margin-top: 0.375rem;
+}
+
+.card-book__badge {
+    display: inline-block;
+    padding: 2px 8px;
+    background: var(--tblr-bg-surface-secondary);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: 20px;
+    font-size: 0.6875rem;
+    color: var(--tblr-secondary);
+}
+
+/* ══════════════════════════════════════════
+   二、书籍网格(.book-grid)
+   ══════════════════════════════════════════ */
+
+.book-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+    gap: 1.25rem;
+}
+
+@media (max-width: 575px) {
+    .book-grid {
+        grid-template-columns: repeat(2, 1fr);
+        gap: 0.875rem;
+    }
+}
+
+/* ══════════════════════════════════════════
+   三、show 页 — 文字截断
+   ══════════════════════════════════════════ */
+
+.line2 {
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+/* ══════════════════════════════════════════
+   四、子分类网格
+   ══════════════════════════════════════════ */
+
+.tipitaka-sub-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+    gap: 8px;
+}
+
+/* ══════════════════════════════════════════
+   五、过滤器区
+   ══════════════════════════════════════════ */
+
+.tipitaka-filters {
+    display: flex;
+    flex-direction: column;
+    gap: 0.875rem;
+}
+
+.tipitaka-filter-row {
+    display: flex;
+    align-items: flex-start;
+    gap: 0.75rem;
+}
+
+.tipitaka-filter-label {
+    font-size: 0.75rem;
+    font-weight: 500;
+    color: var(--tblr-secondary);
+    white-space: nowrap;
+    padding-top: 4px;
+    min-width: 2.5rem;
+}
+
+.tipitaka-filter-pills {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 6px;
+    flex: 1;
+}
+
+.tipitaka-pill {
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+    font-size: 0.8125rem;
+    padding: 3px 10px;
+    border-radius: 20px;
+    border: 1px solid var(--tblr-border-color);
+    color: var(--tblr-body-color);
+    background: var(--tblr-bg-surface);
+    text-decoration: none;
+    transition: background 0.12s, border-color 0.12s, color 0.12s;
+    white-space: nowrap;
+}
+
+.tipitaka-pill:hover {
+    background: var(--tblr-bg-surface-secondary);
+    border-color: var(--tblr-border-color-dark, #adb5bd);
+    color: var(--tblr-body-color);
+    text-decoration: none;
+}
+
+.tipitaka-pill--active {
+    background: var(--tblr-primary);
+    border-color: var(--tblr-primary);
+    color: #fff;
+    pointer-events: none;
+}
+
+.tipitaka-pill--active:hover {
+    background: var(--tblr-primary);
+    color: #fff;
+}
+
+.tipitaka-pill-count {
+    font-size: 0.6875rem;
+    opacity: 0.75;
+}
+
+.tipitaka-author-select {
+    max-width: 220px;
+}
+
+.tipitaka-filter-clear {
+    display: flex;
+    justify-content: flex-end;
+    padding-top: 0.25rem;
+    border-top: 1px solid var(--tblr-border-color);
+}
+
+.tipitaka-clear-btn {
+    font-size: 0.75rem;
+    color: var(--tblr-secondary);
+    text-decoration: none;
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+    transition: color 0.12s;
+}
+
+.tipitaka-clear-btn:hover {
+    color: var(--tblr-danger);
+}
+
+/* ══════════════════════════════════════════
+   六、排序栏
+   ══════════════════════════════════════════ */
+
+.tipitaka-sort-bar {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 0.25rem;
+}
+
+.tipitaka-sort-bar__count {
+    font-size: 0.8125rem;
+    color: var(--tblr-secondary);
+}
+
+.tipitaka-sort-bar__count strong {
+    color: var(--tblr-body-color);
+    font-weight: 500;
+}
+
+.tipitaka-sort-bar__right {
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+}
+
+.tipitaka-sort-bar__label {
+    font-size: 0.8125rem;
+    color: var(--tblr-secondary);
+    white-space: nowrap;
+}
+
+.tipitaka-sort-select {
+    width: auto;
+    min-width: 80px;
+}
+
+/* ══════════════════════════════════════════
+   七、活跃译者列表
+   ══════════════════════════════════════════ */
+
+.tipitaka-author-list {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+}
+
+.tipitaka-author-list li {
+    border-bottom: 1px solid var(--tblr-border-color);
+}
+
+.tipitaka-author-list li:last-child {
+    border-bottom: none;
+}
+
+.tipitaka-author-item {
+    display: flex;
+    align-items: center;
+    gap: 0.625rem;
+    padding: 6px 0;
+    text-decoration: none;
+    color: inherit;
+    transition: background 0.12s;
+}
+
+.tipitaka-author-item:hover {
+    color: var(--tblr-primary);
+    text-decoration: none;
+}
+
+.tipitaka-author-item__info {
+    display: flex;
+    flex-direction: column;
+    min-width: 0;
+}
+
+.tipitaka-author-item__name {
+    font-size: 0.8125rem;
+    font-weight: 500;
+    color: var(--tblr-body-color);
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.tipitaka-author-item:hover .tipitaka-author-item__name {
+    color: var(--tblr-primary);
+}
+
+.tipitaka-author-item__count {
+    font-size: 0.6875rem;
+    color: var(--tblr-secondary);
+}
+
+/* ══════════════════════════════════════════
+   八、响应式
+   ══════════════════════════════════════════ */
+
+@media (max-width: 768px) {
+    /* 过滤器:label 和 pills 垂直排列 */
+    .tipitaka-filter-row {
+        flex-direction: column;
+        gap: 0.375rem;
+    }
+
+    .tipitaka-filter-label {
+        padding-top: 0;
+    }
+
+    /* 作者下拉撑满 */
+    .tipitaka-author-select {
+        max-width: 100%;
+        width: 100%;
+    }
+}
+
+/* 目录项布局 */
+.toc-item {
+    display: flex;
+    flex-direction: column; /* 默认:手机上下 */
+    gap: 0.4rem;
+}
+
+/* 标题 */
+.toc-title {
+    font-size: 0.95rem;
+}
+
+/* 进度区 */
+.toc-progress {
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+}
+
+/* 百分比 */
+.toc-progress-text {
+    font-size: 0.7rem;
+    color: var(--tblr-secondary);
+    white-space: nowrap;
+}
+
+/* ≥ md(平板/桌面):左右分布 */
+@media (min-width: 768px) {
+    .toc-item {
+        flex-direction: row;
+        align-items: center;
+    }
+
+    .toc-title {
+        flex: 1;
+    }
+
+    .toc-progress {
+        width: 140px;
+    }
+}

+ 942 - 0
api-v13/resources/css/modules/_wiki.css

@@ -0,0 +1,942 @@
+/* resources/css/modules/_wiki.css
+   Wiki 栏目专属样式。
+   公共部分(布局、卡片、侧边栏、TOC、相关列表、meta表格、条目头部、精选网格)
+   已提取至:
+     - layout/_grid.css      → .wiki-layout 三栏布局
+     - components/_card.css  → .wiki-card / .wiki-sidebar-* / .wiki-cat-list /
+                               .wiki-toc-list / .wiki-related-list / .wiki-meta-table /
+                               .wiki-entry-header / .wiki-featured-grid / .author-avatar
+   本文件只保留 wiki 专属内容。
+*/
+
+/* ══════════════════════════════════════════
+   一、语言版本切换
+   ══════════════════════════════════════════ */
+
+.wiki-entry-langs-inline {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 6px;
+    margin-bottom: 0.5rem;
+}
+
+.wiki-lang-pill {
+    font-size: 0.75rem;
+    padding: 3px 10px;
+    border-radius: 20px;
+    border: 1px solid var(--tblr-border-color);
+    color: var(--tblr-secondary);
+}
+
+.wiki-lang-pill strong {
+    font-weight: 500;
+    color: var(--tblr-body-color);
+    margin-right: 3px;
+}
+
+.wiki-entry-lang-switcher {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    gap: 6px;
+    padding: 0.625rem 0.875rem;
+    background: var(--tblr-bg-surface-secondary);
+    border-radius: var(--tblr-border-radius);
+    margin-bottom: 1.25rem;
+    font-size: 0.8125rem;
+}
+
+.wiki-entry-lang-label {
+    color: var(--tblr-secondary);
+}
+
+.wiki-entry-lang-btn {
+    font-size: 0.75rem;
+    padding: 3px 10px;
+    border-radius: 20px;
+    border: 1px solid var(--tblr-border-color);
+    color: var(--tblr-primary);
+    text-decoration: none;
+    transition: background 0.12s;
+}
+
+.wiki-entry-lang-btn:hover {
+    background: var(--tblr-bg-surface);
+}
+
+/* ══════════════════════════════════════════
+   二、质量标签
+   ══════════════════════════════════════════ */
+
+.wiki-quality-badge {
+    display: inline-flex;
+    align-items: center;
+    gap: 5px;
+    font-size: 0.6875rem;
+    padding: 3px 10px;
+    border-radius: 20px;
+    font-weight: 500;
+    margin-bottom: 4px;
+}
+
+.wiki-quality-dot {
+    width: 6px;
+    height: 6px;
+    border-radius: 50%;
+    flex-shrink: 0;
+}
+
+.wiki-badge--featured {
+    background: #eaf3de;
+    color: #3b6d11;
+    border: 1px solid #c0dd97;
+}
+.wiki-badge--featured .wiki-quality-dot {
+    background: #639922;
+}
+
+.wiki-badge--standard {
+    background: rgba(var(--tblr-primary-rgb), 0.12);
+    color: var(--tblr-primary);
+    border: 1px solid rgba(var(--tblr-primary-rgb), 0.32);
+}
+.wiki-badge--standard .wiki-quality-dot {
+    background: var(--tblr-primary);
+}
+
+.wiki-badge--draft {
+    background: rgba(var(--tblr-orange-rgb), 0.12);
+    color: var(--tblr-orange);
+    border: 1px solid rgba(var(--tblr-orange-rgb), 0.32);
+}
+.wiki-badge--draft .wiki-quality-dot {
+    background: var(--tblr-orange);
+}
+
+.wiki-badge--pending {
+    background: rgba(var(--tblr-secondary-rgb), 0.12);
+    color: var(--tblr-secondary);
+    border: 1px solid rgba(var(--tblr-secondary-rgb), 0.32);
+}
+.wiki-badge--pending .wiki-quality-dot {
+    background: var(--tblr-secondary);
+}
+
+/* ══════════════════════════════════════════
+   三、标签
+   ══════════════════════════════════════════ */
+
+.wiki-tags {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 6px;
+    padding-top: 1rem;
+    border-top: 1px solid var(--tblr-border-color);
+    margin-top: 1.5rem;
+}
+
+.wiki-tag {
+    font-size: 0.75rem;
+    padding: 3px 10px;
+    border-radius: 20px;
+    border: 1px solid var(--tblr-border-color);
+    color: var(--tblr-secondary);
+    text-decoration: none;
+    transition: background 0.12s;
+}
+
+.wiki-tag:hover {
+    background: var(--tblr-bg-surface-secondary);
+    color: var(--tblr-body-color);
+}
+
+/* ══════════════════════════════════════════
+   四、首页组件
+   ══════════════════════════════════════════ */
+
+.wiki-today-banner {
+    background: var(--tblr-bg-surface-secondary);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: var(--tblr-border-radius-lg);
+    padding: 1.25rem 1.5rem;
+    display: flex;
+    gap: 1.25rem;
+    align-items: flex-start;
+}
+
+.wiki-today-icon {
+    width: 48px;
+    height: 48px;
+    border-radius: var(--tblr-border-radius);
+    background: #e1f5ee;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 1.375rem;
+    flex-shrink: 0;
+}
+
+.wiki-today-label {
+    font-size: 0.6875rem;
+    text-transform: uppercase;
+    letter-spacing: 0.05em;
+    color: var(--tblr-secondary);
+    margin-bottom: 4px;
+}
+
+.wiki-today-title {
+    font-family: "Noto Serif", Georgia, serif;
+    font-size: 1.125rem;
+    font-weight: 600;
+    margin-bottom: 6px;
+    color: var(--tblr-body-color);
+}
+
+.wiki-today-snippet {
+    font-size: 0.875rem;
+    color: var(--tblr-secondary);
+    line-height: 1.6;
+}
+
+.wiki-today-link {
+    font-size: 0.8125rem;
+    color: var(--tblr-primary);
+    text-decoration: none;
+    margin-top: 10px;
+    display: inline-block;
+}
+
+/* ══════════════════════════════════════════
+   五、term-ref 行内术语标记
+   ══════════════════════════════════════════ */
+
+.term-ref {
+    font-style: italic;
+    color: var(--tblr-primary);
+    border-bottom: 1px dotted var(--tblr-primary);
+    cursor: pointer;
+}
+
+/* ══════════════════════════════════════════
+   六、Term Popover(桌面)
+   ══════════════════════════════════════════ */
+
+.wiki-term-popover.popover {
+    max-width: 280px;
+    font-size: 0.8125rem;
+    border: 0.5px solid var(--tblr-border-color);
+    border-radius: var(--tblr-border-radius-lg);
+    box-shadow: none;
+}
+
+.wiki-term-popover .popover-arrow {
+    display: none;
+}
+.wiki-term-popover .popover-body {
+    padding: 0;
+}
+
+.wiki-popover-word {
+    font-family: "Noto Serif", Georgia, serif;
+    font-size: 0.9375rem;
+    font-style: italic;
+    font-weight: 600;
+    color: var(--tblr-primary);
+    padding: 10px 14px;
+    border-bottom: 0.5px solid var(--tblr-border-color);
+}
+
+.wiki-popover-meaning {
+    font-size: 0.9rem;
+    font-weight: 500;
+    color: var(--tblr-body-color);
+    padding: 8px 14px 4px;
+}
+
+.wiki-popover-summary {
+    font-size: 0.8125rem;
+    color: var(--tblr-secondary);
+    line-height: 1.55;
+    padding: 0 14px 12px;
+}
+
+/* ══════════════════════════════════════════
+   七、Term Drawer(移动端,所有阅读页公用)
+   ══════════════════════════════════════════ */
+
+.wiki-term-drawer {
+    border-radius: 1rem 1rem 0 0;
+    max-height: 55vh;
+}
+
+.wiki-term-drawer .offcanvas-header {
+    padding-top: 0.6rem;
+    padding-bottom: 0.6rem;
+}
+
+.wiki-drawer-word {
+    font-family: "Noto Serif", Georgia, serif;
+    font-size: 1.125rem;
+    font-style: italic;
+    font-weight: 600;
+    color: var(--tblr-primary);
+}
+
+.wiki-drawer-meaning {
+    font-size: 0.9375rem;
+    font-weight: 500;
+    color: var(--tblr-body-color);
+    margin-top: 2px;
+}
+
+.wiki-drawer-summary {
+    font-size: 0.9375rem;
+    color: var(--tblr-body-color);
+    line-height: 1.65;
+}
+
+.wiki-drawer-link {
+    display: inline-block;
+    margin-top: 1rem;
+    font-size: 0.875rem;
+    color: var(--tblr-primary);
+    text-decoration: none;
+}
+
+.wiki-term-card-word {
+    font-family: "Noto Serif", Georgia, serif;
+    font-size: 0.9375rem;
+    font-style: italic;
+    font-weight: 600;
+    color: var(--tblr-primary);
+    padding: 8px;
+    margin-bottom: 8px;
+    border-bottom: 0.5px solid var(--tblr-border-color);
+}
+
+.wiki-term-card-meaning {
+    font-size: 0.875rem;
+    font-weight: 500;
+    color: var(--tblr-body-color);
+    margin-bottom: 5px;
+}
+
+.wiki-term-card-summary {
+    font-size: 0.8125rem;
+    color: var(--tblr-secondary);
+    line-height: 1.55;
+}
+
+/* Skeleton */
+.wiki-term-skeleton-word,
+.wiki-term-skeleton-line {
+    background: linear-gradient(90deg, #e8e8e8 25%, #f5f5f5 50%, #e8e8e8 75%);
+    background-size: 200% 100%;
+    animation: wiki-skeleton-shimmer 1.2s infinite;
+    border-radius: 4px;
+}
+
+.wiki-term-skeleton-word {
+    height: 18px;
+    width: 120px;
+    margin-bottom: 8px;
+}
+.wiki-term-skeleton-line {
+    height: 13px;
+    width: 100%;
+    margin-bottom: 6px;
+}
+.wiki-term-skeleton-line.short {
+    width: 60%;
+}
+
+@keyframes wiki-skeleton-shimmer {
+    0% {
+        background-position: 200% 0;
+    }
+    100% {
+        background-position: -200% 0;
+    }
+}
+
+/* ══════════════════════════════════════════
+   八、正文排版(.wiki-content-body)
+   ══════════════════════════════════════════ */
+
+.wiki-content-body {
+    font-family: "Noto Serif", Georgia, "Times New Roman", serif;
+    font-size: 1rem;
+    line-height: 1.875;
+    color: var(--tblr-body-color);
+    word-break: break-word;
+    overflow-wrap: break-word;
+}
+
+.wiki-content-body p {
+    margin-top: 0;
+    margin-bottom: 1.125em;
+}
+
+.wiki-content-body h1,
+.wiki-content-body h2,
+.wiki-content-body h3,
+.wiki-content-body h4,
+.wiki-content-body h5,
+.wiki-content-body h6 {
+    font-family: "Noto Serif", Georgia, serif;
+    font-weight: 600;
+    line-height: 1.3;
+    color: var(--tblr-body-color);
+    margin-top: 2em;
+    margin-bottom: 0.6em;
+    scroll-margin-top: 80px;
+}
+
+.wiki-content-body h1 {
+    font-size: 1.375rem;
+    padding-bottom: 0.4em;
+    border-bottom: 2px solid var(--tblr-border-color);
+}
+.wiki-content-body h2 {
+    font-size: 1.25rem;
+    padding-bottom: 0.4em;
+    border-bottom: 1px solid var(--tblr-border-color);
+}
+.wiki-content-body h3 {
+    font-size: 1.0625rem;
+}
+.wiki-content-body h4 {
+    font-size: 0.9375rem;
+    font-weight: 600;
+    color: var(--tblr-secondary);
+}
+
+.wiki-content-body strong,
+.wiki-content-body b {
+    font-weight: 600;
+}
+.wiki-content-body em,
+.wiki-content-body i {
+    font-style: italic;
+}
+
+.wiki-content-body a {
+    color: var(--tblr-primary);
+    text-decoration: none;
+    border-bottom: 1px solid transparent;
+    transition: border-color 0.12s;
+}
+.wiki-content-body a:hover {
+    border-bottom-color: var(--tblr-primary);
+}
+
+.wiki-content-body blockquote {
+    margin: 1.5em 0;
+    padding: 0.875rem 1.125rem;
+    border-left: 3px solid var(--tblr-border-color-dark, #adb5bd);
+    background: var(--tblr-bg-surface-secondary);
+    border-radius: 0 var(--tblr-border-radius) var(--tblr-border-radius) 0;
+    font-size: 0.9375rem;
+    line-height: 1.75;
+}
+
+.wiki-content-body blockquote p {
+    margin-bottom: 0.5em;
+}
+.wiki-content-body blockquote p:last-child {
+    margin-bottom: 0;
+}
+.wiki-content-body blockquote cite {
+    display: block;
+    margin-top: 0.625rem;
+    font-size: 0.8125rem;
+    font-style: normal;
+    color: var(--tblr-secondary);
+}
+
+.wiki-content-body ul,
+.wiki-content-body ol {
+    padding-left: 1.5em;
+    margin-bottom: 1.125em;
+}
+.wiki-content-body li {
+    margin-bottom: 0.375em;
+    line-height: 1.75;
+}
+
+.wiki-content-body table {
+    width: 100%;
+    border-collapse: collapse;
+    font-size: 0.9rem;
+    margin-bottom: 1.5em;
+}
+.wiki-content-body th {
+    font-family: "Noto Serif", Georgia, serif;
+    font-weight: 600;
+    font-size: 0.8125rem;
+    text-align: left;
+    padding: 8px 12px;
+    background: var(--tblr-bg-surface-secondary);
+    border-bottom: 2px solid var(--tblr-border-color);
+    color: var(--tblr-secondary);
+    text-transform: uppercase;
+    letter-spacing: 0.04em;
+}
+.wiki-content-body td {
+    padding: 8px 12px;
+    border-bottom: 1px solid var(--tblr-border-color);
+    vertical-align: top;
+}
+.wiki-content-body tr:last-child td {
+    border-bottom: none;
+}
+
+.wiki-content-body code {
+    font-family: var(
+        --tblr-font-monospace,
+        "SFMono-Regular",
+        Consolas,
+        monospace
+    );
+    font-size: 0.875em;
+    background: var(--tblr-bg-surface-secondary);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: 4px;
+    padding: 1px 5px;
+}
+
+.wiki-content-body hr {
+    border: none;
+    border-top: 1px solid var(--tblr-border-color);
+    margin: 2em 0;
+}
+.wiki-content-body img {
+    max-width: 100%;
+    height: auto;
+    border-radius: var(--tblr-border-radius);
+    margin: 0.75em 0;
+}
+.wiki-content-body figure {
+    margin: 1.5em 0;
+    text-align: center;
+}
+.wiki-content-body figcaption {
+    font-size: 0.8125rem;
+    color: var(--tblr-secondary);
+    margin-top: 0.5em;
+    font-style: italic;
+}
+
+/* ══════════════════════════════════════════
+   九、Wiki 首页
+   ══════════════════════════════════════════ */
+
+.wiki-home-container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    min-height: calc(100vh - 300px);
+    padding: 3rem 1.5rem;
+}
+
+.wiki-home-wheel {
+    margin-bottom: 1.5rem;
+}
+.wiki-home-wheel-img {
+    width: 120px;
+    height: auto;
+    opacity: 0.85;
+    transition: transform 0.3s ease;
+}
+.wiki-home-wheel-img:hover {
+    transform: scale(1.05);
+}
+
+.wiki-home-title {
+    text-align: center;
+    margin-bottom: 2rem;
+}
+.wiki-home-title h1 {
+    font-family: "Noto Serif", Georgia, serif;
+    font-size: 2.25rem;
+    font-weight: 600;
+    color: var(--tblr-body-color);
+    margin-bottom: 0.5rem;
+}
+
+.wiki-home-search {
+    width: 100%;
+    max-width: 640px;
+    margin: 0 auto 1.5rem;
+}
+
+.wiki-home-hot-tags {
+    text-align: center;
+    margin-bottom: 2.5rem;
+    font-size: 0.9rem;
+}
+.wiki-hot-tag {
+    display: inline-block;
+    padding: 0.3rem 0.75rem;
+    background: var(--tblr-bg-surface-secondary);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: 20px;
+    color: var(--tblr-secondary);
+    font-size: 0.8125rem;
+    text-decoration: none;
+    margin: 2px;
+    transition: background 0.12s, color 0.12s;
+}
+.wiki-hot-tag:hover {
+    background: var(--wp-brand-light);
+    border-color: var(--wp-brand);
+    color: var(--wp-brand-dark);
+}
+
+.wiki-home-languages {
+    width: 100%;
+    max-width: 800px;
+    margin: 0 auto 2rem;
+}
+.wiki-home-divider {
+    display: flex;
+    align-items: center;
+    text-align: center;
+    margin-bottom: 1.5rem;
+    gap: 1.5rem;
+    color: var(--tblr-secondary);
+    font-size: 0.875rem;
+}
+.wiki-home-divider::before,
+.wiki-home-divider::after {
+    content: "";
+    flex: 1;
+    border-bottom: 1px solid var(--tblr-border-color);
+}
+
+.wiki-language-tags {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    gap: 0.75rem;
+}
+.wiki-language-tag {
+    display: inline-block;
+    padding: 0.4rem 1.125rem;
+    background: var(--tblr-bg-surface-secondary);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: 30px;
+    color: var(--tblr-body-color);
+    font-size: 0.875rem;
+    text-decoration: none;
+    transition: background 0.12s, border-color 0.12s;
+}
+.wiki-language-tag:hover {
+    background: var(--wp-brand-light);
+    border-color: var(--wp-brand);
+    color: var(--wp-brand-dark);
+}
+.wiki-language-tag.active {
+    background: var(--wp-brand);
+    border-color: var(--wp-brand);
+    color: #fff;
+}
+.wiki-language-tag.active:hover {
+    background: var(--wp-brand-dark);
+    border-color: var(--wp-brand-dark);
+}
+
+.wiki-home-stats {
+    text-align: center;
+    font-size: 0.875rem;
+    padding-top: 1.5rem;
+    border-top: 1px solid var(--tblr-border-color);
+    width: 100%;
+    max-width: 640px;
+}
+
+@media (max-width: 768px) {
+    .wiki-home-container {
+        min-height: calc(100vh - 200px);
+        padding: 2rem 1rem;
+    }
+    .wiki-home-wheel-img {
+        width: 90px;
+    }
+    .wiki-home-title h1 {
+        font-size: 1.75rem;
+    }
+    .wiki-language-tag {
+        padding: 0.35rem 0.875rem;
+        font-size: 0.8125rem;
+    }
+}
+
+/* ── 追加到 resources/css/wiki.css 末尾 ── */
+
+/* ── 二级分类大块容器 ── */
+.wiki-subcat-block {
+    padding: 0;
+}
+
+.wiki-subcat-block-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 1rem 1.5rem 0.875rem;
+    border-bottom: 1px solid var(--tblr-border-color);
+}
+
+.wiki-subcat-block-title {
+    font-size: 0.9375rem;
+    font-weight: 600;
+    color: var(--tblr-body-color);
+}
+
+.wiki-subcat-block-more {
+    font-size: 0.8125rem;
+    color: var(--tblr-primary);
+    text-decoration: none;
+}
+
+.wiki-subcat-block-more:hover {
+    text-decoration: underline;
+}
+
+/* ── 单个二级分类 ── */
+.wiki-subcat {
+    padding: 1rem 1.5rem;
+    border-bottom: 1px solid var(--tblr-border-color);
+}
+
+.wiki-subcat:last-child {
+    border-bottom: none;
+}
+
+.wiki-subcat-header {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    margin-bottom: 0.625rem;
+}
+
+.wiki-subcat-title {
+    font-size: 0.8125rem;
+    font-weight: 500;
+    color: var(--tblr-secondary);
+    text-transform: uppercase;
+    letter-spacing: 0.04em;
+}
+
+.wiki-subcat-count {
+    font-size: 0.6875rem;
+    color: var(--tblr-secondary);
+    background: var(--tblr-bg-surface-secondary);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: 20px;
+    padding: 1px 7px;
+}
+
+/* ── 词条列表 (flex wrap) ── */
+.wiki-subcat-entries {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 4px 0;
+}
+
+/* ── 单个术语链接 ── */
+.wiki-term-link {
+    display: inline-flex;
+    align-items: baseline;
+    gap: 4px;
+    padding: 3px 8px;
+    border-radius: var(--tblr-border-radius);
+    text-decoration: none;
+    font-size: 0.875rem;
+    transition: background 0.1s;
+    color: var(--tblr-primary);
+}
+
+.wiki-term-link:hover {
+    background: var(--tblr-bg-surface-secondary);
+    text-decoration: none;
+    color: var(--tblr-primary);
+}
+
+.wiki-term-link-word {
+    font-style: italic;
+    color: var(--tblr-secondary);
+}
+
+.wiki-term-link-zh {
+    font-size: 0.8125rem;
+}
+
+/* =========================
+   Wiki Term Link States
+   典范条目 featured(蓝色 + 星标)
+   规范条目 standard(蓝色)
+   草稿 draft(橙色)
+   待定 pending(灰色 + 删除线)
+   ========================= */
+
+/* 通用链接颜色继承 */
+.wiki-term-link--featured,
+.wiki-term-link--standard,
+.wiki-term-link--draft,
+.wiki-term-link--pending {
+    position: relative;
+}
+
+.wiki-term-link--featured:hover,
+.wiki-term-link--standard:hover,
+.wiki-term-link--draft:hover,
+.wiki-term-link--pending:hover {
+    color: inherit;
+}
+
+/* =========================
+   典范条目 Featured
+   ========================= */
+.wiki-term-link--featured {
+    color: var(--tblr-green);
+}
+
+.wiki-term-link--featured::before {
+    content: "★";
+    display: inline-block;
+    margin-right: 0.2em;
+    font-size: 0.75em;
+    vertical-align: middle;
+    opacity: 0.9;
+}
+
+/* 若想改圆点可用:
+content: "●";
+*/
+
+/* =========================
+   规范条目 Standard
+   ========================= */
+.wiki-term-link--standard {
+    color: var(--tblr-primary);
+}
+
+/* =========================
+   草稿 Draft
+   ========================= */
+.wiki-term-link--draft {
+    color: var(--tblr-orange);
+}
+
+/* =========================
+   待定 Pending
+   (保持原有效果)
+   ========================= */
+.wiki-term-link--pending {
+    color: var(--tblr-secondary);
+}
+
+.wiki-term-link--pending .wiki-term-link-word {
+    text-decoration: line-through;
+    text-decoration-color: var(--tblr-border-color-dark, #adb5bd);
+}
+
+.wiki-quality-filter-item {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    gap: 8px;
+}
+
+.wiki-quality-filter-item span:last-child {
+    font-size: 0.75rem;
+    color: var(--tblr-secondary);
+    margin-left: auto;
+    flex-shrink: 0;
+}
+
+/* ── 条目操作按钮 ── */
+.wiki-entry-card {
+    position: relative;
+}
+
+.wiki-entry-actions {
+    position: absolute;
+    top: 1.25rem;
+    right: 1.5rem;
+    display: flex;
+    gap: 4px;
+}
+
+.wiki-action-btn {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    width: 32px;
+    height: 32px;
+    border-radius: var(--tblr-border-radius);
+    border: 1px solid var(--tblr-border-color);
+    background: var(--tblr-bg-surface);
+    color: var(--tblr-secondary);
+    cursor: pointer;
+    text-decoration: none;
+    transition: background 0.12s, color 0.12s;
+}
+
+.wiki-action-btn:hover {
+    background: var(--tblr-bg-surface-secondary);
+    color: var(--tblr-body-color);
+}
+
+/* ── 微信二维码弹窗 ── */
+.wiki-wechat-modal {
+    position: fixed;
+    inset: 0;
+    z-index: 1050;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.wiki-wechat-modal-backdrop {
+    position: absolute;
+    inset: 0;
+    background: rgba(0, 0, 0, 0.45);
+}
+
+.wiki-wechat-modal-box {
+    position: relative;
+    background: var(--tblr-bg-surface);
+    border-radius: var(--tblr-border-radius-lg);
+    padding: 1.5rem;
+    text-align: center;
+    width: 220px;
+    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+}
+
+.wiki-wechat-modal-title {
+    font-weight: 600;
+    margin-bottom: 4px;
+}
+
+.wiki-wechat-modal-desc {
+    font-size: 0.8125rem;
+    color: var(--tblr-secondary);
+    margin-bottom: 1rem;
+}
+
+.wiki-wechat-qr img {
+    border-radius: var(--tblr-border-radius);
+}
+
+.wiki-wechat-modal-close {
+    margin-top: 1rem;
+    font-size: 0.8125rem;
+    color: var(--tblr-secondary);
+    background: none;
+    border: none;
+    cursor: pointer;
+    text-decoration: underline;
+}

+ 6 - 0
api-v13/resources/css/reader.css

@@ -0,0 +1,6 @@
+/* resources/css/reader.css
+   全站阅读页 CSS 入口。
+   只做 @import,不写任何样式规则。
+*/
+
+@import "./modules/_reader.css";

+ 360 - 0
api-v13/resources/css/tufte.css

@@ -0,0 +1,360 @@
+@charset "UTF-8";
+
+/* Import ET Book styles
+   adapted from https://github.com/edwardtufte/et-book/blob/gh-pages/et-book.css */
+
+/* Tufte CSS styles */
+
+hr {
+    display: block;
+    height: 1px;
+    width: 55%;
+    border: 0;
+    border-top: 1px solid #ccc;
+    margin: 1em 0;
+    padding: 0;
+}
+
+p {
+    line-height: 1.5;
+}
+p.subtitle {
+    font-style: italic;
+    margin-top: 1rem;
+    margin-bottom: 1rem;
+    display: block;
+}
+
+.numeral {
+    font-family: et-book-roman-old-style;
+}
+
+.danger {
+    color: red;
+}
+
+article {
+    padding: 5rem 0rem;
+}
+
+section {
+    padding-top: 1rem;
+    padding-bottom: 1rem;
+}
+
+p,
+dl,
+ol,
+ul {
+    line-height: 1.5rem;
+}
+
+p {
+    margin-bottom: 1.4rem;
+    padding-right: 0;
+    vertical-align: baseline;
+}
+
+/* Chapter Epigraphs */
+div.epigraph {
+    margin: 5em 0;
+}
+
+div.epigraph > blockquote {
+    margin-top: 3em;
+    margin-bottom: 3em;
+}
+
+div.epigraph > blockquote,
+div.epigraph > blockquote > p {
+    font-style: italic;
+}
+
+div.epigraph > blockquote > footer {
+    font-style: normal;
+}
+
+div.epigraph > blockquote > footer > cite {
+    font-style: italic;
+}
+/* end chapter epigraphs styles */
+
+blockquote {
+    font-size: 1.4rem;
+}
+
+blockquote p {
+    width: 55%;
+    margin-right: 40px;
+}
+
+blockquote footer {
+    width: 55%;
+    font-size: 1.1rem;
+    text-align: right;
+}
+
+section > p,
+section > footer,
+section > table {
+    width: 55%;
+}
+
+/* 50 + 5 == 55, to be the same width as paragraph */
+section > dl,
+section > ol,
+section > ul {
+    width: 50%;
+    -webkit-padding-start: 5%;
+}
+
+dt:not(:first-child),
+li:not(:first-child) {
+    margin-top: 0.25rem;
+}
+
+figure {
+    padding: 0;
+    border: 0;
+    font-size: 100%;
+    font: inherit;
+    vertical-align: baseline;
+    max-width: 55%;
+    -webkit-margin-start: 0;
+    -webkit-margin-end: 0;
+    margin: 0 0 3em 0;
+}
+
+figcaption {
+    float: right;
+    clear: right;
+    margin-top: 0;
+    margin-bottom: 0;
+    font-size: 1.1rem;
+    line-height: 1.6;
+    vertical-align: baseline;
+    position: relative;
+    max-width: 40%;
+}
+
+figure.fullwidth figcaption {
+    margin-right: 24%;
+}
+
+a:link,
+a:visited {
+    color: inherit;
+    text-underline-offset: 0.1em;
+    text-decoration-thickness: 0.05em;
+}
+
+/* Sidenotes, margin notes, figures, captions */
+img {
+    max-width: 100%;
+}
+
+.sidenote,
+.marginnote {
+    float: right;
+    clear: right;
+    margin-right: -60%;
+    width: 50%;
+    margin-top: 0.3rem;
+    margin-bottom: 0;
+    font-size: 100%;
+    line-height: 1.3;
+    vertical-align: baseline;
+    position: relative;
+}
+
+.sidenote-number {
+    counter-increment: sidenote-counter;
+}
+
+.sidenote-number:after,
+.sidenote:before {
+    font-family: et-book-roman-old-style;
+    position: relative;
+    vertical-align: baseline;
+}
+
+.sidenote-number:after {
+    content: counter(sidenote-counter);
+    font-size: 100%;
+    top: -0.5rem;
+    left: 0.1rem;
+}
+
+.sidenote:before {
+    content: counter(sidenote-counter) " ";
+    font-size: 100%;
+    top: -0.5rem;
+}
+
+blockquote .sidenote,
+blockquote .marginnote {
+    margin-right: -82%;
+    min-width: 59%;
+    text-align: left;
+}
+
+div.fullwidth,
+table.fullwidth {
+    width: 100%;
+}
+
+div.table-wrapper {
+    overflow-x: auto;
+    font-family: "Trebuchet MS", "Gill Sans", "Gill Sans MT", sans-serif;
+}
+
+.sans {
+    font-family: "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
+    letter-spacing: 0.03em;
+}
+
+code,
+pre > code {
+    font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
+    font-size: 1rem;
+    line-height: 1.42;
+    -webkit-text-size-adjust: 100%; /* Prevent adjustments of font size after orientation changes in iOS. See https://github.com/edwardtufte/tufte-css/issues/81#issuecomment-261953409 */
+}
+
+.sans > code {
+    font-size: 1.2rem;
+}
+
+h1 > code,
+h2 > code,
+h3 > code {
+    font-size: 0.8em;
+}
+
+.marginnote > code,
+.sidenote > code {
+    font-size: 100%;
+}
+
+pre > code {
+    font-size: 0.9rem;
+    width: 52.5%;
+    margin-left: 2.5%;
+    overflow-x: auto;
+    display: block;
+}
+
+pre.fullwidth > code {
+    width: 90%;
+}
+
+.fullwidth {
+    max-width: 90%;
+    clear: both;
+}
+
+span.newthought {
+    font-variant: small-caps;
+    font-size: 1.2em;
+}
+
+input.margin-toggle {
+    display: none;
+}
+
+label.sidenote-number {
+    display: inline-block;
+    max-height: 1.5rem; /* should be less than or equal to paragraph line-height */
+}
+
+label.margin-toggle:not(.sidenote-number) {
+    display: none;
+}
+
+.iframe-wrapper {
+    position: relative;
+    padding-bottom: 56.25%; /* 16:9 */
+    padding-top: 25px;
+    height: 0;
+}
+
+.iframe-wrapper iframe {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+}
+
+@media (max-width: 760px) {
+    hr,
+    section > p,
+    section > footer,
+    section > table {
+        width: 100%;
+    }
+
+    pre > code {
+        width: 97%;
+    }
+
+    section > dl,
+    section > ol,
+    section > ul {
+        width: 90%;
+    }
+
+    figure {
+        max-width: 90%;
+    }
+
+    figcaption,
+    figure.fullwidth figcaption {
+        margin-right: 0%;
+        max-width: none;
+    }
+
+    blockquote {
+        margin-left: 1.5em;
+        margin-right: 0em;
+    }
+
+    blockquote p,
+    blockquote footer {
+        width: 100%;
+    }
+
+    label.margin-toggle:not(.sidenote-number) {
+        display: inline;
+    }
+
+    .sidenote,
+    .marginnote {
+        display: none;
+    }
+
+    .margin-toggle:checked + .sidenote,
+    .margin-toggle:checked + .marginnote {
+        display: block;
+        float: left;
+        left: 1rem;
+        clear: both;
+        width: 95%;
+        margin: 1rem 2.5%;
+        vertical-align: baseline;
+        position: relative;
+    }
+
+    label {
+        cursor: pointer;
+    }
+
+    div.table-wrapper,
+    table {
+        width: 85%;
+    }
+
+    img {
+        width: 100%;
+    }
+}

+ 8 - 0
api-v13/resources/js/app.js

@@ -0,0 +1,8 @@
+// resources/js/app.js
+import "./bootstrap";
+import "./search-suggest";
+import { initNavbar } from "./modules/navbar";
+
+document.addEventListener("DOMContentLoaded", () => {
+    initNavbar();
+});

+ 4 - 0
api-v13/resources/js/bootstrap.js

@@ -0,0 +1,4 @@
+import axios from 'axios';
+window.axios = axios;
+
+window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

+ 30 - 0
api-v13/resources/js/modules/navbar.js

@@ -0,0 +1,30 @@
+// resources/js/modules/navbar.js
+// 顶部导航 mobile 抽屉开关逻辑。
+// 来源:library/layouts/app.blade.php 内联 script(去除了无效的 mobileMenu 引用)。
+
+export function initNavbar() {
+    const btn     = document.getElementById('bcHamburger');
+    const overlay = document.getElementById('bcOverlay');
+    const drawer  = document.getElementById('bcDrawer');
+    const close   = document.getElementById('bcDrawerClose');
+
+    if (!btn) return;
+
+    function open() {
+        drawer.classList.add('open');
+        overlay.classList.add('open');
+    }
+
+    function shut() {
+        drawer.classList.remove('open');
+        overlay.classList.remove('open');
+    }
+
+    btn.addEventListener('click', open);
+    overlay.addEventListener('click', shut);
+    close.addEventListener('click', shut);
+
+    drawer.querySelectorAll('a').forEach(a => {
+        a.addEventListener('click', shut);
+    });
+}

+ 233 - 0
api-v13/resources/js/modules/term-tooltip.js

@@ -0,0 +1,233 @@
+// resources/js/modules/term-tooltip.js
+// 原 term-tooltip.js 移至此处(从 js/ 根目录移入 modules/)。
+// 所有阅读页公用(tipitaka read、anthology read、wiki show、blog show)。
+// 在 reader.js 中 import,不在 app.js 全局加载(仅阅读页需要)。
+//
+// 文件内容:将现有 term-tooltip.js 内容直接复制至此,不做修改。
+// 待 reader.js 建立后在此 import:
+//   import './modules/term-tooltip';
+
+import * as bootstrap from "bootstrap";
+
+(function () {
+    "use strict";
+
+    // ── 缓存层 ────────────────────────────────────────────────────────
+    const cache = {};
+
+    async function fetchTerm(id) {
+        if (cache[id]) return cache[id];
+        const res = await fetch(`/api/v2/terms/${id}`);
+        if (!res.ok) throw new Error(`fetchTerm ${id} failed: ${res.status}`);
+        const json = await res.json();
+        cache[id] = json.data;
+        return json.data;
+    }
+
+    // ── 设备判断 ──────────────────────────────────────────────────────
+    const isMobile = () => window.innerWidth < 768;
+
+    // ── Skeleton 模板 ─────────────────────────────────────────────────
+    function buildSkeletonContent() {
+        return `
+            <div class="wiki-term-skeleton">
+                <div class="wiki-term-skeleton-word"></div>
+                <div class="wiki-term-skeleton-line"></div>
+                <div class="wiki-term-skeleton-line short"></div>
+            </div>
+        `;
+    }
+
+    // ── Popover 内容模板 ──────────────────────────────────────────────
+    function buildPopoverContent(data) {
+        const meaning = (data.meaning || "").trim();
+        const summary = (data.summary || "").trim();
+        const showSummary = summary && summary !== meaning;
+
+        return `
+            <div class="wiki-term-card-word">${data.word || ""}</div>
+            <div class="wiki-term-card-body" style="padding: 10px 14px 12px;">
+                ${
+                    meaning
+                        ? `<div class="wiki-term-card-meaning">${meaning}</div>`
+                        : ""
+                }
+                ${
+                    showSummary
+                        ? `<div class="wiki-term-card-summary">${summary}</div>`
+                        : ""
+                }
+            </div>
+            <div><a>查看完整条目</a></div>
+        `;
+    }
+
+    // ── 桌面端:Bootstrap Popover ─────────────────────────────────────
+    function initDesktopPopover(el, content) {
+        if (el._wikiPopover) return el._wikiPopover;
+
+        const popover = new bootstrap.Popover(el, {
+            trigger: "manual",
+            html: true,
+            placement: "bottom",
+            fallbackPlacements: ["top", "bottom"],
+            customClass: "wiki-term-popover",
+            content: content,
+            sanitize: false,
+        });
+
+        el._wikiPopover = popover;
+
+        let hideTimer = null;
+
+        function scheduleHide() {
+            hideTimer = setTimeout(() => {
+                const tipEl = document.querySelector(".wiki-term-popover.show");
+                if (tipEl && tipEl.matches(":hover")) return;
+                popover.hide();
+            }, 120);
+        }
+
+        function cancelHide() {
+            clearTimeout(hideTimer);
+        }
+
+        el.addEventListener("mouseenter", () => {
+            cancelHide();
+            popover.show();
+        });
+
+        el.addEventListener("mouseleave", scheduleHide);
+
+        el.addEventListener("shown.bs.popover", () => {
+            const tipEl = document.querySelector(".wiki-term-popover.show");
+            if (!tipEl) return;
+            tipEl.addEventListener("mouseenter", cancelHide);
+            tipEl.addEventListener("mouseleave", scheduleHide);
+        });
+
+        return popover;
+    }
+
+    function updateDesktopPopover(el, data) {
+        const popover = el._wikiPopover;
+        if (!popover) return;
+        // 更新内容
+        const tip = document.querySelector(".wiki-term-popover.show");
+        if (tip) {
+            tip.querySelector(".popover-body").innerHTML =
+                buildPopoverContent(data);
+        }
+        // 同步 popover 内部 config,供下次 show 使用
+        popover._config.content = buildPopoverContent(data);
+    }
+
+    // ── 移动端:Bootstrap Offcanvas ───────────────────────────────────
+    let offcanvasInstance = null;
+
+    function getOffcanvas() {
+        if (offcanvasInstance) return offcanvasInstance;
+        const el = document.getElementById("wikiTermDrawer");
+        if (!el) return null;
+        offcanvasInstance = new bootstrap.Offcanvas(el, { scroll: false });
+
+        el.addEventListener("hidden.bs.offcanvas", () => {
+            document
+                .querySelectorAll(".offcanvas-backdrop")
+                .forEach((b) => b.remove());
+            document.body.classList.remove("modal-open");
+            document.body.style.removeProperty("overflow");
+            document.body.style.removeProperty("padding-right");
+        });
+
+        return offcanvasInstance;
+    }
+
+    function showMobileDrawerSkeleton() {
+        const oc = getOffcanvas();
+        if (!oc) return;
+
+        document.getElementById("wikiTermDrawerWord").innerHTML =
+            '<div class="wiki-term-skeleton-word"></div>';
+        document.getElementById("wikiTermDrawerMeaning").innerHTML =
+            '<div class="wiki-term-skeleton-line short"></div>';
+        document.getElementById(
+            "wikiTermCardSlot"
+        ).innerHTML = `<div class="wiki-term-skeleton">
+                <div class="wiki-term-skeleton-line"></div>
+                <div class="wiki-term-skeleton-line short"></div>
+            </div>`;
+        document.getElementById("wikiTermDrawerLink").style.display = "none";
+
+        oc.show();
+    }
+
+    function fillMobileDrawer(data) {
+        const meaning = (data.meaning || "").trim();
+        const summary = (data.summary || "").trim();
+        const showSummary = summary && summary !== meaning;
+
+        document.getElementById("wikiTermDrawerWord").textContent =
+            data.word || "";
+        document.getElementById("wikiTermDrawerMeaning").textContent = meaning;
+        document.getElementById("wikiTermCardSlot").innerHTML = showSummary
+            ? `<div class="wiki-term-card-summary">${summary}</div>`
+            : "";
+    }
+
+    // ── 入口:扫描所有 .term-ref ──────────────────────────────────────
+    function init() {
+        const refs = document.querySelectorAll(".term-ref[data-id]");
+        if (!refs.length) return;
+
+        refs.forEach((el) => {
+            // 桌面:mouseenter 时先显示 skeleton,数据回来后更新
+            el.addEventListener("mouseenter", async function onFirstEnter() {
+                if (isMobile()) return;
+
+                // 立即显示 skeleton popover
+                const popover = initDesktopPopover(el, buildSkeletonContent());
+                popover.show();
+
+                try {
+                    const data = await fetchTerm(el.dataset.id);
+                    updateDesktopPopover(el, data);
+                } catch (e) {
+                    console.warn(
+                        "[WikiPāli] term fetch failed",
+                        el.dataset.id,
+                        e
+                    );
+                    popover.hide();
+                }
+
+                el.removeEventListener("mouseenter", onFirstEnter);
+            });
+
+            // 移动端:点击立即弹出 skeleton,数据回来后填充
+            el.addEventListener("click", async (e) => {
+                if (!isMobile()) return;
+                e.preventDefault();
+
+                showMobileDrawerSkeleton();
+
+                try {
+                    const data = await fetchTerm(el.dataset.id);
+                    fillMobileDrawer(data);
+                } catch (err) {
+                    console.warn(
+                        "[WikiPāli] term fetch failed",
+                        el.dataset.id,
+                        err
+                    );
+                }
+            });
+        });
+    }
+
+    if (document.readyState === "loading") {
+        document.addEventListener("DOMContentLoaded", init);
+    } else {
+        init();
+    }
+})();

+ 91 - 0
api-v13/resources/js/search-suggest.js

@@ -0,0 +1,91 @@
+document.addEventListener("DOMContentLoaded", () => {
+    const inputs = document.querySelectorAll(".search-input");
+
+    inputs.forEach((input) => {
+        const form = input.closest(".search-input-form");
+        const dropdown = form.querySelector(".search-suggest-dropdown");
+
+        let controller = null;
+
+        input.addEventListener("input", async () => {
+            const q = input.value.trim();
+
+            if (q.length < 2) {
+                dropdown.classList.remove("show");
+                return;
+            }
+
+            // 取消上一次请求(防抖 + 避免竞态)
+            if (controller) controller.abort();
+            controller = new AbortController();
+
+            try {
+                const url = new URL(
+                    input.dataset.suggestUrl,
+                    window.location.origin
+                );
+                url.searchParams.set("q", q);
+                url.searchParams.set("limit", 10);
+
+                const res = await fetch(url, {
+                    signal: controller.signal,
+                });
+
+                const json = await res.json();
+
+                renderSuggestions(json.data.suggestions || []);
+            } catch (e) {
+                if (e.name !== "AbortError") {
+                    console.error(e);
+                }
+            }
+        });
+
+        function renderSuggestions(list) {
+            if (!list.length) {
+                dropdown.classList.remove("show");
+                return;
+            }
+
+            dropdown.innerHTML = list
+                .map((item) => {
+                    return `
+                    <button type="button"
+                        class="dropdown-item"
+                        data-text="${item.text}">
+
+                        <div class="d-flex justify-content-between">
+                            <span>${item.text}</span>
+                            <small class="text-muted">${item.resource_type}</small>
+                        </div>
+                    </button>
+                `;
+                })
+                .join("");
+
+            dropdown.classList.add("show");
+        }
+
+        // 点击选择
+        dropdown.addEventListener("click", (e) => {
+            const btn = e.target.closest(".dropdown-item");
+            if (!btn) return;
+
+            input.value = btn.dataset.text;
+            dropdown.classList.remove("show");
+
+            form.submit(); // 或者只填充不提交
+        });
+
+        // 失焦隐藏
+        input.addEventListener("blur", () => {
+            setTimeout(() => dropdown.classList.remove("show"), 150);
+        });
+
+        input.addEventListener("focus", () => {
+            if (dropdown.innerHTML.trim()) {
+                dropdown.classList.add("show");
+            }
+        });
+    });
+});

+ 227 - 0
api-v13/resources/js/term-tooltip.js

@@ -0,0 +1,227 @@
+/**
+ * resources/js/term-tooltip.js
+ */
+import * as bootstrap from "bootstrap";
+
+(function () {
+    "use strict";
+
+    // ── 缓存层 ────────────────────────────────────────────────────────
+    const cache = {};
+
+    async function fetchTerm(id) {
+        if (cache[id]) return cache[id];
+        const res = await fetch(`/api/v2/terms/${id}`);
+        if (!res.ok) throw new Error(`fetchTerm ${id} failed: ${res.status}`);
+        const json = await res.json();
+        cache[id] = json.data;
+        return json.data;
+    }
+
+    // ── 设备判断 ──────────────────────────────────────────────────────
+    const isMobile = () => window.innerWidth < 768;
+
+    // ── Skeleton 模板 ─────────────────────────────────────────────────
+    function buildSkeletonContent() {
+        return `
+            <div class="wiki-term-skeleton">
+                <div class="wiki-term-skeleton-word"></div>
+                <div class="wiki-term-skeleton-line"></div>
+                <div class="wiki-term-skeleton-line short"></div>
+            </div>
+        `;
+    }
+
+    // ── Popover 内容模板 ──────────────────────────────────────────────
+    function buildPopoverContent(data) {
+        const meaning = (data.meaning || "").trim();
+        const summary = (data.summary || "").trim();
+        const showSummary = summary && summary !== meaning;
+
+        return `
+            <div class="wiki-term-card-word">${data.word || ""}</div>
+            <div class="wiki-term-card-body" style="padding: 10px 14px 12px;">
+                ${
+                    meaning
+                        ? `<div class="wiki-term-card-meaning">${meaning}</div>`
+                        : ""
+                }
+                ${
+                    showSummary
+                        ? `<div class="wiki-term-card-summary">${summary}</div>`
+                        : ""
+                }
+            </div>
+            <div><a>查看完整条目</a></div>
+        `;
+    }
+
+    // ── 桌面端:Bootstrap Popover ─────────────────────────────────────
+    function initDesktopPopover(el, content) {
+        if (el._wikiPopover) return el._wikiPopover;
+
+        const popover = new bootstrap.Popover(el, {
+            trigger: "manual",
+            html: true,
+            placement: "bottom",
+            fallbackPlacements: ["top", "bottom"],
+            customClass: "wiki-term-popover",
+            content: content,
+            sanitize: false,
+        });
+
+        el._wikiPopover = popover;
+
+        let hideTimer = null;
+
+        function scheduleHide() {
+            hideTimer = setTimeout(() => {
+                const tipEl = document.querySelector(".wiki-term-popover.show");
+                if (tipEl && tipEl.matches(":hover")) return;
+                popover.hide();
+            }, 120);
+        }
+
+        function cancelHide() {
+            clearTimeout(hideTimer);
+        }
+
+        el.addEventListener("mouseenter", () => {
+            cancelHide();
+            popover.show();
+        });
+
+        el.addEventListener("mouseleave", scheduleHide);
+
+        el.addEventListener("shown.bs.popover", () => {
+            const tipEl = document.querySelector(".wiki-term-popover.show");
+            if (!tipEl) return;
+            tipEl.addEventListener("mouseenter", cancelHide);
+            tipEl.addEventListener("mouseleave", scheduleHide);
+        });
+
+        return popover;
+    }
+
+    function updateDesktopPopover(el, data) {
+        const popover = el._wikiPopover;
+        if (!popover) return;
+        // 更新内容
+        const tip = document.querySelector(".wiki-term-popover.show");
+        if (tip) {
+            tip.querySelector(".popover-body").innerHTML =
+                buildPopoverContent(data);
+        }
+        // 同步 popover 内部 config,供下次 show 使用
+        popover._config.content = buildPopoverContent(data);
+    }
+
+    // ── 移动端:Bootstrap Offcanvas ───────────────────────────────────
+    let offcanvasInstance = null;
+
+    function getOffcanvas() {
+        if (offcanvasInstance) return offcanvasInstance;
+        const el = document.getElementById("wikiTermDrawer");
+        if (!el) return null;
+        offcanvasInstance = new bootstrap.Offcanvas(el, { scroll: false });
+
+        el.addEventListener("hidden.bs.offcanvas", () => {
+            document
+                .querySelectorAll(".offcanvas-backdrop")
+                .forEach((b) => b.remove());
+            document.body.classList.remove("modal-open");
+            document.body.style.removeProperty("overflow");
+            document.body.style.removeProperty("padding-right");
+        });
+
+        return offcanvasInstance;
+    }
+
+    function showMobileDrawerSkeleton() {
+        const oc = getOffcanvas();
+        if (!oc) return;
+
+        document.getElementById("wikiTermDrawerWord").innerHTML =
+            '<div class="wiki-term-skeleton-word"></div>';
+        document.getElementById("wikiTermDrawerMeaning").innerHTML =
+            '<div class="wiki-term-skeleton-line short"></div>';
+        document.getElementById(
+            "wikiTermCardSlot"
+        ).innerHTML = `<div class="wiki-term-skeleton">
+                <div class="wiki-term-skeleton-line"></div>
+                <div class="wiki-term-skeleton-line short"></div>
+            </div>`;
+        document.getElementById("wikiTermDrawerLink").style.display = "none";
+
+        oc.show();
+    }
+
+    function fillMobileDrawer(data) {
+        const meaning = (data.meaning || "").trim();
+        const summary = (data.summary || "").trim();
+        const showSummary = summary && summary !== meaning;
+
+        document.getElementById("wikiTermDrawerWord").textContent =
+            data.word || "";
+        document.getElementById("wikiTermDrawerMeaning").textContent = meaning;
+        document.getElementById("wikiTermCardSlot").innerHTML = showSummary
+            ? `<div class="wiki-term-card-summary">${summary}</div>`
+            : "";
+    }
+
+    // ── 入口:扫描所有 .term-ref ──────────────────────────────────────
+    function init() {
+        const refs = document.querySelectorAll(".term-ref[data-id]");
+        if (!refs.length) return;
+
+        refs.forEach((el) => {
+            // 桌面:mouseenter 时先显示 skeleton,数据回来后更新
+            el.addEventListener("mouseenter", async function onFirstEnter() {
+                if (isMobile()) return;
+
+                // 立即显示 skeleton popover
+                const popover = initDesktopPopover(el, buildSkeletonContent());
+                popover.show();
+
+                try {
+                    const data = await fetchTerm(el.dataset.id);
+                    updateDesktopPopover(el, data);
+                } catch (e) {
+                    console.warn(
+                        "[WikiPāli] term fetch failed",
+                        el.dataset.id,
+                        e
+                    );
+                    popover.hide();
+                }
+
+                el.removeEventListener("mouseenter", onFirstEnter);
+            });
+
+            // 移动端:点击立即弹出 skeleton,数据回来后填充
+            el.addEventListener("click", async (e) => {
+                if (!isMobile()) return;
+                e.preventDefault();
+
+                showMobileDrawerSkeleton();
+
+                try {
+                    const data = await fetchTerm(el.dataset.id);
+                    fillMobileDrawer(data);
+                } catch (err) {
+                    console.warn(
+                        "[WikiPāli] term fetch failed",
+                        el.dataset.id,
+                        err
+                    );
+                }
+            });
+        });
+    }
+
+    if (document.readyState === "loading") {
+        document.addEventListener("DOMContentLoaded", init);
+    } else {
+        init();
+    }
+})();

+ 83 - 0
api-v13/resources/js/term-tooltip.old.js

@@ -0,0 +1,83 @@
+/**
+ * <span
+    class="term-ref"
+    data-id="b45e7b10-2b75-4f5f-ac63-be686116043c"
+    data-term="anicca"
+>
+    无常
+</span>
+ */
+document.addEventListener("DOMContentLoaded", () => {
+    const cache = {};
+
+    const drawerEl = document.getElementById("termDrawer");
+
+    const drawer = new bootstrap.Offcanvas(drawerEl);
+
+    const drawerTitle = document.getElementById("termDrawerTitle");
+
+    const drawerBody = document.getElementById("termDrawerBody");
+
+    function isMobile() {
+        return window.innerWidth < 768;
+    }
+
+    async function fetchTerm(term) {
+        if (cache[term]) return cache[term];
+        console.info("term", term);
+        const res = await fetch(`/api/v2/terms/${term}`);
+
+        const data = await res.json();
+
+        cache[term] = data.data;
+
+        return data.data;
+    }
+
+    document.querySelectorAll(".term-ref").forEach((el) => {
+        let popover = null;
+
+        el.addEventListener("mouseenter", async () => {
+            console.info("mouseenter");
+            if (isMobile()) return;
+
+            if (popover) return;
+            const pali = el.dataset.term;
+            const data = await fetchTerm(el.dataset.id);
+
+            popover = new bootstrap.Popover(el, {
+                trigger: "manual",
+                html: true,
+                placement: "bottom",
+
+                content: `
+            <div style="max-width:300px">
+                <h4>${data.word}</h4>
+                <div>${data.summary}</div>
+            </div>
+        `,
+            });
+
+            popover.show();
+        });
+
+        el.addEventListener("mouseleave", () => {
+            if (popover) {
+                popover.dispose();
+                popover = null;
+            }
+        });
+
+        el.addEventListener("click", async () => {
+            if (!isMobile()) return;
+
+            const data = await fetchTerm(el.dataset.id);
+
+            drawerTitle.innerHTML = data.word;
+
+            drawerBody.innerHTML = data.summary;
+
+            drawer.show();
+        });
+    });
+});

+ 20 - 0
api-v13/resources/lang/en/auth.php

@@ -0,0 +1,20 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Authentication Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are used during authentication for various
+    | messages that we need to display to the user. You are free to modify
+    | these language lines according to your application's requirements.
+    |
+    */
+
+    'failed' => 'These credentials do not match our records.',
+    'password' => 'The provided password is incorrect.',
+    'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
+
+];

+ 5 - 0
api-v13/resources/lang/en/buttons.php

@@ -0,0 +1,5 @@
+<?php
+return [
+    'more' => 'More',
+    'online-read' =>  'Online Read',
+];

+ 7 - 0
api-v13/resources/lang/en/labels.php

@@ -0,0 +1,7 @@
+<?php
+return [
+    'home' => 'Home',
+    'translation' => 'Translation',
+    'original' => 'Original',
+    'nissaya' => 'Nissaya'
+];

+ 10 - 0
api-v13/resources/lang/en/language.php

@@ -0,0 +1,10 @@
+<?php
+// api-v12/resources/lang/en/language.php
+return [
+    'en' => 'English',
+    'pali' => 'pali',
+    'zh' => '中文',
+    'zh-Hans' => '简体中文',
+    'zh-Hant' => '繁体中文',
+    'my' => 'မြန်မာဘာသာ',
+];

+ 19 - 0
api-v13/resources/lang/en/pagination.php

@@ -0,0 +1,19 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Pagination Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are used by the paginator library to build
+    | the simple pagination links. You are free to change them to anything
+    | you want to customize your views to better match your application.
+    |
+    */
+
+    'previous' => '&laquo; Previous',
+    'next' => 'Next &raquo;',
+
+];

+ 22 - 0
api-v13/resources/lang/en/passwords.php

@@ -0,0 +1,22 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Password Reset Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are the default lines which match reasons
+    | that are given by the password broker for a password update attempt
+    | has failed, such as for an invalid token or invalid new password.
+    |
+    */
+
+    'reset' => 'Your password has been reset!',
+    'sent' => 'We have emailed your password reset link!',
+    'throttled' => 'Please wait before retrying.',
+    'token' => 'This password reset token is invalid.',
+    'user' => "We can't find a user with that email address.",
+
+];

+ 22 - 0
api-v13/resources/lang/en/site.php

@@ -0,0 +1,22 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | site Language Lines
+    |--------------------------------------------------------------------------
+    |
+    |
+    */
+
+    'logo' => '/logo.png',
+    'title' => 'wikipali',
+    'subhead' => 'wikipali',
+    'keywords' => ['pali','buddhistm'],
+    'description' => 'wikipali',
+    'copyright' => 'iapt 2022',
+    'author.name' => 'iapt',
+    'author.email' => 'visuddhindand@gmail.com',
+
+];

+ 162 - 0
api-v13/resources/lang/en/validation.php

@@ -0,0 +1,162 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Validation Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines contain the default error messages used by
+    | the validator class. Some of these rules have multiple versions such
+    | as the size rules. Feel free to tweak each of these messages here.
+    |
+    */
+
+    'accepted' => 'The :attribute must be accepted.',
+    'accepted_if' => 'The :attribute must be accepted when :other is :value.',
+    'active_url' => 'The :attribute is not a valid URL.',
+    'after' => 'The :attribute must be a date after :date.',
+    'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
+    'alpha' => 'The :attribute must only contain letters.',
+    'alpha_dash' => 'The :attribute must only contain letters, numbers, dashes and underscores.',
+    'alpha_num' => 'The :attribute must only contain letters and numbers.',
+    'array' => 'The :attribute must be an array.',
+    'before' => 'The :attribute must be a date before :date.',
+    'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
+    'between' => [
+        'numeric' => 'The :attribute must be between :min and :max.',
+        'file' => 'The :attribute must be between :min and :max kilobytes.',
+        'string' => 'The :attribute must be between :min and :max characters.',
+        'array' => 'The :attribute must have between :min and :max items.',
+    ],
+    'boolean' => 'The :attribute field must be true or false.',
+    'confirmed' => 'The :attribute confirmation does not match.',
+    'current_password' => 'The password is incorrect.',
+    'date' => 'The :attribute is not a valid date.',
+    'date_equals' => 'The :attribute must be a date equal to :date.',
+    'date_format' => 'The :attribute does not match the format :format.',
+    'declined' => 'The :attribute must be declined.',
+    'declined_if' => 'The :attribute must be declined when :other is :value.',
+    'different' => 'The :attribute and :other must be different.',
+    'digits' => 'The :attribute must be :digits digits.',
+    'digits_between' => 'The :attribute must be between :min and :max digits.',
+    'dimensions' => 'The :attribute has invalid image dimensions.',
+    'distinct' => 'The :attribute field has a duplicate value.',
+    'email' => 'The :attribute must be a valid email address.',
+    'ends_with' => 'The :attribute must end with one of the following: :values.',
+    'enum' => 'The selected :attribute is invalid.',
+    'exists' => 'The selected :attribute is exists.',
+    'file' => 'The :attribute must be a file.',
+    'filled' => 'The :attribute field must have a value.',
+    'gt' => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file' => 'The :attribute must be greater than :value kilobytes.',
+        'string' => 'The :attribute must be greater than :value characters.',
+        'array' => 'The :attribute must have more than :value items.',
+    ],
+    'gte' => [
+        'numeric' => 'The :attribute must be greater than or equal to :value.',
+        'file' => 'The :attribute must be greater than or equal to :value kilobytes.',
+        'string' => 'The :attribute must be greater than or equal to :value characters.',
+        'array' => 'The :attribute must have :value items or more.',
+    ],
+    'image' => 'The :attribute must be an image.',
+    'in' => 'The selected :attribute is invalid.',
+    'in_array' => 'The :attribute field does not exist in :other.',
+    'integer' => 'The :attribute must be an integer.',
+    'ip' => 'The :attribute must be a valid IP address.',
+    'ipv4' => 'The :attribute must be a valid IPv4 address.',
+    'ipv6' => 'The :attribute must be a valid IPv6 address.',
+    'mac_address' => 'The :attribute must be a valid MAC address.',
+    'json' => 'The :attribute must be a valid JSON string.',
+    'lt' => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file' => 'The :attribute must be less than :value kilobytes.',
+        'string' => 'The :attribute must be less than :value characters.',
+        'array' => 'The :attribute must have less than :value items.',
+    ],
+    'lte' => [
+        'numeric' => 'The :attribute must be less than or equal to :value.',
+        'file' => 'The :attribute must be less than or equal to :value kilobytes.',
+        'string' => 'The :attribute must be less than or equal to :value characters.',
+        'array' => 'The :attribute must not have more than :value items.',
+    ],
+    'max' => [
+        'numeric' => 'The :attribute must not be greater than :max.',
+        'file' => 'The :attribute must not be greater than :max kilobytes.',
+        'string' => 'The :attribute must not be greater than :max characters.',
+        'array' => 'The :attribute must not have more than :max items.',
+    ],
+    'mimes' => 'The :attribute must be a file of type: :values.',
+    'mimetypes' => 'The :attribute must be a file of type: :values.',
+    'min' => [
+        'numeric' => 'The :attribute must be at least :min.',
+        'file' => 'The :attribute must be at least :min kilobytes.',
+        'string' => 'The :attribute must be at least :min characters.',
+        'array' => 'The :attribute must have at least :min items.',
+    ],
+    'multiple_of' => 'The :attribute must be a multiple of :value.',
+    'not_in' => 'The selected :attribute is invalid.',
+    'not_regex' => 'The :attribute format is invalid.',
+    'numeric' => 'The :attribute must be a number.',
+    'password' => 'The password is incorrect.',
+    'present' => 'The :attribute field must be present.',
+    'prohibited' => 'The :attribute field is prohibited.',
+    'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',
+    'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',
+    'prohibits' => 'The :attribute field prohibits :other from being present.',
+    'regex' => 'The :attribute format is invalid.',
+    'required' => 'The :attribute field is required.',
+    'required_if' => 'The :attribute field is required when :other is :value.',
+    'required_unless' => 'The :attribute field is required unless :other is in :values.',
+    'required_with' => 'The :attribute field is required when :values is present.',
+    'required_with_all' => 'The :attribute field is required when :values are present.',
+    'required_without' => 'The :attribute field is required when :values is not present.',
+    'required_without_all' => 'The :attribute field is required when none of :values are present.',
+    'same' => 'The :attribute and :other must match.',
+    'size' => [
+        'numeric' => 'The :attribute must be :size.',
+        'file' => 'The :attribute must be :size kilobytes.',
+        'string' => 'The :attribute must be :size characters.',
+        'array' => 'The :attribute must contain :size items.',
+    ],
+    'starts_with' => 'The :attribute must start with one of the following: :values.',
+    'string' => 'The :attribute must be a string.',
+    'timezone' => 'The :attribute must be a valid timezone.',
+    'unique' => 'The :attribute has already been taken.',
+    'uploaded' => 'The :attribute failed to upload.',
+    'url' => 'The :attribute must be a valid URL.',
+    'uuid' => 'The :attribute must be a valid UUID.',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Custom Validation Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | Here you may specify custom validation messages for attributes using the
+    | convention "attribute.rule" to name the lines. This makes it quick to
+    | specify a specific custom language line for a given attribute rule.
+    |
+    */
+
+    'custom' => [
+        'attribute-name' => [
+            'rule-name' => 'custom-message',
+        ],
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Custom Validation Attributes
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are used to swap our attribute placeholder
+    | with something more reader friendly such as "E-Mail Address" instead
+    | of "email". This simply helps us make our message more expressive.
+    |
+    */
+
+    'attributes' => [],
+
+];

+ 20 - 0
api-v13/resources/lang/zh-Hans/auth.php

@@ -0,0 +1,20 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Authentication Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are used during authentication for various
+    | messages that we need to display to the user. You are free to modify
+    | these language lines according to your application's requirements.
+    |
+    */
+
+    'failed' => 'These credentials do not match our records.',
+    'password' => 'The provided password is incorrect.',
+    'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
+
+];

+ 5 - 0
api-v13/resources/lang/zh-Hans/buttons.php

@@ -0,0 +1,5 @@
+<?php
+return [
+    'more' => '更多',
+    'online-read' =>  '在线阅读',
+];

+ 199 - 0
api-v13/resources/lang/zh-Hans/grammar.php

@@ -0,0 +1,199 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | site Language Lines
+    |--------------------------------------------------------------------------
+    |
+    |
+    */
+
+    'n' => '名词',
+    'n.short' => '名',
+    'ti' => '三性',
+    'ti.short' => '三',
+    'v' => '动词',
+    'v.short' => '动',
+    'v:ind' => '动不变',
+    'v:ind.short' => '动不变',
+    'ind' => '不变',
+    'ind.short' => '不',
+    'm' => '阳性',
+    'm.short' => '阳',
+    'nt' => '中性',
+    'nt.short' => '中',
+    'f' => '阴性',
+    'f.short' => '阴',
+    'sg' => '单数',
+    'sg.short' => '单',
+    'pl' => '复数',
+    'pl.short' => '复',
+    'nom' => '主格',
+    'nom.short' => '主',
+    'acc' => '宾格',
+    'acc.short' => '宾',
+    'gen' => '属格',
+    'gen.short' => '属',
+    'dat' => '为格',
+    'dat.short' => '为',
+    'inst' => '工具格',
+    'inst.short' => '具',
+    'voc' => '呼格',
+    'voc.short' => '呼',
+    'abl' => '来源格',
+    'abl.short' => '源',
+    'loc' => '处格',
+    'loc.short' => '处',
+    'base' => '词干',
+    'base.short' => '干',
+    'imp' => '命令',
+    'imp.short' => '命令',
+    'cond' => '条件',
+    'cond.short' => '条件',
+    'opt' => '愿望',
+    'opt.short' => '愿望',
+    'pres' => '现',
+    'pres.short' => '现',
+    'aor' => '过',
+    'aor.short' => '过',
+    'pf' => '完',
+    'pf.short' => '完',
+    'fut' => '将',
+    'fut.short' => '将',
+    'act' => '主动',
+    'act.short' => '主动',
+    'refl' => '反照',
+    'refl.short' => '反',
+    '1p' => '第一',
+    '1p.short' => '一',
+    '2p' => '第二',
+    '2p.short' => '二',
+    '3p' => '第三',
+    '3p.short' => '三',
+    'prp' => '现在分词',
+    'prp.short' => '现分',
+    'prpp' => '被动现在分词',
+    'prpp.short' => '被现分',
+    'pp' => '过去分词',
+    'pp.short' => '过分',
+    'ppa' => '主过分',
+    'ppa.short' => '主过分',
+    'ppp' => '被过分',
+    'ppp.short' => '被过分',
+    'futp' => '未来分词',
+    'futp.short' => '未分',
+    'fpa.short' => '主未分',
+    'grd' => '义务',
+    'grd.short' => '义务',
+    'pass' => '被动',
+    'pass.short' => '被动',
+    'caus' => '使役',
+    'caus.short' => '使役',
+    'desid' => '意欲',
+    'desid.short' => '意欲',
+    'intens' => '强意',
+    'intens.short' => '强意',
+    'denom' => '名动',
+    'denom.short' => '名动',
+    'ger' => '连续',
+    'ger.short' => '连续',
+    'abs' => '绝对',
+    'abs.short' => '绝对',
+    'inf' => '不定',
+    'inf.short' => '不定',
+    'adj' => '形容词',
+    'adj.short' => '形',
+    'pron' => '代词',
+    'pron.short' => '代',
+    'num' => '数词',
+    'num.short' => '数',
+    'adv' => '副词',
+    'adv.short' => '副',
+    'conj' => '连词',
+    'conj.short' => '连',
+    'prep' => '介词',
+    'prep.short' => '介',
+    'interj' => '感叹',
+    'interj.short' => '感',
+    'pre' => '前缀',
+    'pre.short' => '前',
+    'suf' => '后缀',
+    'suf.short' => '后',
+    'end' => '语尾',
+    'end.short' => '尾',
+    'part' => '组份',
+    'part.short' => '合',
+    'un' => '连音',
+    'un.short' => '连音',
+    'none' => '无',
+    'none.short' => '_',
+    'null' => '空',
+    'null.short' => '_',
+    '?' => '?',
+    '?.short' => '?',
+    'ti:base' => '三性词干',
+    'ti:base.short' => '三性词干',
+    'n:base' => '名词干',
+    'n:base.short' => '名词干',
+    'v:base' => '动词干',
+    'v:base.short' => '动词干',
+    'adj:base' => '形词干',
+    'adj:base.short' => '形词干',
+    'fpp' => '未来被动分词',
+    'fpp.short' => '未被分',
+    'cp.short' => '合',
+    'cp' => '复合词组分',
+    'indconj.short' => '连',
+    'indconj' => '连词',
+    'pron:base.short' => '代干',
+    'pron:base' => '代词词干',
+    'note.short' => '注释',
+    'note' => '注释',
+    'vind.short' => '动不变',
+    'vind' => '动不变',
+    'vdn' => '衍生动名词',
+    'vdn.short' => '动名',
+    "relations.iad.label" => "同类修饰",
+    "relations.asv.label" => "施动者➡动词",
+    "relations.aov.label" => "受动者➡动词",
+    "relations.daso-p.label" => "主语➡系动词",
+    "relations.daso-s.label" => "表语➡系动词",
+    "relations.nio.label" => "被描述(主)➡定性(表)",
+    "relations.nid.label" => "待命名➡命名",
+    "relations.dasd-p.label" => "<命名>主➡系",
+    "relations.dasd-s.label" => "<命名>表➡系",
+    "relations.dao-p.label" => "被动语态双宾语-首要",
+    "relations.dao-s.label" => "被动语态双宾语-次要",
+    "relations.iov.label" => "受动者➡动词",
+    "relations.dio-p.label" => "双宾语<主要>➡动词",
+    "relations.dio-s.label" => "双宾语<次要>➡动词",
+    "relations.dis-p.label" => "主➡系(被动)",
+    "relations.dis-s.label" => "表➡系(被动)",
+    "relations.stc.label" => "时空连续 ➡ 持续动作",
+    "relations.adv.label" => "动词修饰词 ➡ 动词",
+    "relations.imp.label" => "方式 ➡ 动词",
+    "relations.soe.label" => "<带连词>伴随关系",
+    "relations.soi.label" => "<无连词>伴随关系",
+    "relations.isv.label" => "<非主格>施动者 ➡ 动词",
+    "relations.cau.label" => "因 ➡ 果/归因",
+    "relations.iov-c.label" => "被使役宾语 ➡ 动词",
+    "relations.adj.label" => "名词的形容 ➡ 被形容",
+    "relations.rec.label" => "接收者 ➡ 授予",
+    "relations.pur.label" => "目的 ➡ 动词",
+    "relations.det.label" => "出发地 ➡ 出发",
+    "relations.coc.label" => "区分比较",
+    "relations.pos.label" => "所有 ➡ 被所有",
+    "relations.coi.label" => "包含[全集] ➡ 被包含[子集]元素/集合 ➡ 个体元素",
+    "relations.lov.label" => "容器 ➡ 动词/容纳 ➡ 被容纳",
+    "relations.mot.label" => "表现 ➡ 有表现",
+    "relations.whp.label" => "整体 ➡ 局部",
+    "relations.def.label" => "特征限定",
+    "relations.ac.label" => "绝对从句",
+    "relations.avc.label" => "绝对语态从句",
+    "relations.qus.label" => "引号内 ➡ 引号",
+    "relations.qum.label" => "引号 ➡ 引号外",
+    "relations.enu.label" => "罗列 ➡ 破折号",
+    "relations.enm.label" => "破折号 ➡ 被罗列",
+];

+ 4 - 0
api-v13/resources/lang/zh-Hans/label.php

@@ -0,0 +1,4 @@
+<?php
+return [
+    'translation' => '译文',
+];

+ 7 - 0
api-v13/resources/lang/zh-Hans/labels.php

@@ -0,0 +1,7 @@
+<?php
+return [
+    'home' => '首页',
+    'translation' => '译文',
+    'original' => '原文',
+    'nissaya' => '缅文逐词'
+];

+ 19 - 0
api-v13/resources/lang/zh-Hans/pagination.php

@@ -0,0 +1,19 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Pagination Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are used by the paginator library to build
+    | the simple pagination links. You are free to change them to anything
+    | you want to customize your views to better match your application.
+    |
+    */
+
+    'previous' => '&laquo; Previous',
+    'next' => 'Next &raquo;',
+
+];

+ 22 - 0
api-v13/resources/lang/zh-Hans/passwords.php

@@ -0,0 +1,22 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Password Reset Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are the default lines which match reasons
+    | that are given by the password broker for a password update attempt
+    | has failed, such as for an invalid token or invalid new password.
+    |
+    */
+
+    'reset' => 'Your password has been reset!',
+    'sent' => 'We have emailed your password reset link!',
+    'throttled' => 'Please wait before retrying.',
+    'token' => 'This password reset token is invalid.',
+    'user' => "We can't find a user with that email address.",
+
+];

+ 22 - 0
api-v13/resources/lang/zh-Hans/site.php

@@ -0,0 +1,22 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | site Language Lines
+    |--------------------------------------------------------------------------
+    |
+    |
+    */
+
+    'logo' => '/logo.png',
+    'title' => '维基巴利',
+    'subhead' => 'wikipali',
+    'keywords' => ['巴利语','佛教'],
+    'description' => 'wikipali',
+    'copyright' => 'iapt 2022',
+    'author.name' => 'iapt',
+    'author.email' => 'visuddhindand@gmail.com',
+
+];

+ 162 - 0
api-v13/resources/lang/zh-Hans/validation.php

@@ -0,0 +1,162 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Validation Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines contain the default error messages used by
+    | the validator class. Some of these rules have multiple versions such
+    | as the size rules. Feel free to tweak each of these messages here.
+    |
+    */
+
+    'accepted' => 'The :attribute must be accepted.',
+    'accepted_if' => 'The :attribute must be accepted when :other is :value.',
+    'active_url' => 'The :attribute is not a valid URL.',
+    'after' => 'The :attribute must be a date after :date.',
+    'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
+    'alpha' => 'The :attribute must only contain letters.',
+    'alpha_dash' => 'The :attribute must only contain letters, numbers, dashes and underscores.',
+    'alpha_num' => 'The :attribute must only contain letters and numbers.',
+    'array' => 'The :attribute must be an array.',
+    'before' => 'The :attribute must be a date before :date.',
+    'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
+    'between' => [
+        'numeric' => 'The :attribute must be between :min and :max.',
+        'file' => 'The :attribute must be between :min and :max kilobytes.',
+        'string' => 'The :attribute must be between :min and :max characters.',
+        'array' => 'The :attribute must have between :min and :max items.',
+    ],
+    'boolean' => 'The :attribute field must be true or false.',
+    'confirmed' => 'The :attribute confirmation does not match.',
+    'current_password' => 'The password is incorrect.',
+    'date' => 'The :attribute is not a valid date.',
+    'date_equals' => 'The :attribute must be a date equal to :date.',
+    'date_format' => 'The :attribute does not match the format :format.',
+    'declined' => 'The :attribute must be declined.',
+    'declined_if' => 'The :attribute must be declined when :other is :value.',
+    'different' => 'The :attribute and :other must be different.',
+    'digits' => 'The :attribute must be :digits digits.',
+    'digits_between' => 'The :attribute must be between :min and :max digits.',
+    'dimensions' => 'The :attribute has invalid image dimensions.',
+    'distinct' => 'The :attribute field has a duplicate value.',
+    'email' => 'The :attribute must be a valid email address.',
+    'ends_with' => 'The :attribute must end with one of the following: :values.',
+    'enum' => 'The selected :attribute is invalid.',
+    'exists' => 'The selected :attribute is invalid.',
+    'file' => 'The :attribute must be a file.',
+    'filled' => 'The :attribute field must have a value.',
+    'gt' => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file' => 'The :attribute must be greater than :value kilobytes.',
+        'string' => 'The :attribute must be greater than :value characters.',
+        'array' => 'The :attribute must have more than :value items.',
+    ],
+    'gte' => [
+        'numeric' => 'The :attribute must be greater than or equal to :value.',
+        'file' => 'The :attribute must be greater than or equal to :value kilobytes.',
+        'string' => 'The :attribute must be greater than or equal to :value characters.',
+        'array' => 'The :attribute must have :value items or more.',
+    ],
+    'image' => 'The :attribute must be an image.',
+    'in' => 'The selected :attribute is invalid.',
+    'in_array' => 'The :attribute field does not exist in :other.',
+    'integer' => 'The :attribute must be an integer.',
+    'ip' => 'The :attribute must be a valid IP address.',
+    'ipv4' => 'The :attribute must be a valid IPv4 address.',
+    'ipv6' => 'The :attribute must be a valid IPv6 address.',
+    'mac_address' => 'The :attribute must be a valid MAC address.',
+    'json' => 'The :attribute must be a valid JSON string.',
+    'lt' => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file' => 'The :attribute must be less than :value kilobytes.',
+        'string' => 'The :attribute must be less than :value characters.',
+        'array' => 'The :attribute must have less than :value items.',
+    ],
+    'lte' => [
+        'numeric' => 'The :attribute must be less than or equal to :value.',
+        'file' => 'The :attribute must be less than or equal to :value kilobytes.',
+        'string' => 'The :attribute must be less than or equal to :value characters.',
+        'array' => 'The :attribute must not have more than :value items.',
+    ],
+    'max' => [
+        'numeric' => 'The :attribute must not be greater than :max.',
+        'file' => 'The :attribute must not be greater than :max kilobytes.',
+        'string' => 'The :attribute must not be greater than :max characters.',
+        'array' => 'The :attribute must not have more than :max items.',
+    ],
+    'mimes' => 'The :attribute must be a file of type: :values.',
+    'mimetypes' => 'The :attribute must be a file of type: :values.',
+    'min' => [
+        'numeric' => 'The :attribute must be at least :min.',
+        'file' => 'The :attribute must be at least :min kilobytes.',
+        'string' => 'The :attribute must be at least :min characters.',
+        'array' => 'The :attribute must have at least :min items.',
+    ],
+    'multiple_of' => 'The :attribute must be a multiple of :value.',
+    'not_in' => 'The selected :attribute is invalid.',
+    'not_regex' => 'The :attribute format is invalid.',
+    'numeric' => 'The :attribute must be a number.',
+    'password' => 'The password is incorrect.',
+    'present' => 'The :attribute field must be present.',
+    'prohibited' => 'The :attribute field is prohibited.',
+    'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',
+    'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',
+    'prohibits' => 'The :attribute field prohibits :other from being present.',
+    'regex' => 'The :attribute format is invalid.',
+    'required' => 'The :attribute field is required.',
+    'required_if' => 'The :attribute field is required when :other is :value.',
+    'required_unless' => 'The :attribute field is required unless :other is in :values.',
+    'required_with' => 'The :attribute field is required when :values is present.',
+    'required_with_all' => 'The :attribute field is required when :values are present.',
+    'required_without' => 'The :attribute field is required when :values is not present.',
+    'required_without_all' => 'The :attribute field is required when none of :values are present.',
+    'same' => 'The :attribute and :other must match.',
+    'size' => [
+        'numeric' => 'The :attribute must be :size.',
+        'file' => 'The :attribute must be :size kilobytes.',
+        'string' => 'The :attribute must be :size characters.',
+        'array' => 'The :attribute must contain :size items.',
+    ],
+    'starts_with' => 'The :attribute must start with one of the following: :values.',
+    'string' => 'The :attribute must be a string.',
+    'timezone' => 'The :attribute must be a valid timezone.',
+    'unique' => 'The :attribute has already been taken.',
+    'uploaded' => 'The :attribute failed to upload.',
+    'url' => 'The :attribute must be a valid URL.',
+    'uuid' => 'The :attribute must be a valid UUID.',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Custom Validation Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | Here you may specify custom validation messages for attributes using the
+    | convention "attribute.rule" to name the lines. This makes it quick to
+    | specify a specific custom language line for a given attribute rule.
+    |
+    */
+
+    'custom' => [
+        'attribute-name' => [
+            'rule-name' => 'custom-message',
+        ],
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Custom Validation Attributes
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are used to swap our attribute placeholder
+    | with something more reader friendly such as "E-Mail Address" instead
+    | of "email". This simply helps us make our message more expressive.
+    |
+    */
+
+    'attributes' => [],
+
+];

+ 20 - 0
api-v13/resources/lang/zh-Hant/auth.php

@@ -0,0 +1,20 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Authentication Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are used during authentication for various
+    | messages that we need to display to the user. You are free to modify
+    | these language lines according to your application's requirements.
+    |
+    */
+
+    'failed' => 'These credentials do not match our records.',
+    'password' => 'The provided password is incorrect.',
+    'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
+
+];

+ 5 - 0
api-v13/resources/lang/zh-Hant/buttons.php

@@ -0,0 +1,5 @@
+<?php
+return [
+    'more' => '更多',
+    'online-read' =>  '在线阅读',
+];

+ 5 - 0
api-v13/resources/lang/zh-Hant/labels.php

@@ -0,0 +1,5 @@
+<?php
+return [
+    'home' => '首页',
+    'translation' => '译文',
+];

+ 19 - 0
api-v13/resources/lang/zh-Hant/pagination.php

@@ -0,0 +1,19 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Pagination Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are used by the paginator library to build
+    | the simple pagination links. You are free to change them to anything
+    | you want to customize your views to better match your application.
+    |
+    */
+
+    'previous' => '&laquo; Previous',
+    'next' => 'Next &raquo;',
+
+];

+ 22 - 0
api-v13/resources/lang/zh-Hant/passwords.php

@@ -0,0 +1,22 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Password Reset Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are the default lines which match reasons
+    | that are given by the password broker for a password update attempt
+    | has failed, such as for an invalid token or invalid new password.
+    |
+    */
+
+    'reset' => 'Your password has been reset!',
+    'sent' => 'We have emailed your password reset link!',
+    'throttled' => 'Please wait before retrying.',
+    'token' => 'This password reset token is invalid.',
+    'user' => "We can't find a user with that email address.",
+
+];

+ 22 - 0
api-v13/resources/lang/zh-Hant/site.php

@@ -0,0 +1,22 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | site Language Lines
+    |--------------------------------------------------------------------------
+    |
+    |
+    */
+
+    'logo' => '/logo.png',
+    'title' => '维基巴利',
+    'subhead' => 'wikipali',
+    'keywords' => ['巴利语','佛教'],
+    'description' => 'wikipali',
+    'copyright' => 'iapt 2022',
+    'author.name' => 'iapt',
+    'author.email' => 'visuddhindand@gmail.com',
+
+];

+ 162 - 0
api-v13/resources/lang/zh-Hant/validation.php

@@ -0,0 +1,162 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Validation Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines contain the default error messages used by
+    | the validator class. Some of these rules have multiple versions such
+    | as the size rules. Feel free to tweak each of these messages here.
+    |
+    */
+
+    'accepted' => 'The :attribute must be accepted.',
+    'accepted_if' => 'The :attribute must be accepted when :other is :value.',
+    'active_url' => 'The :attribute is not a valid URL.',
+    'after' => 'The :attribute must be a date after :date.',
+    'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
+    'alpha' => 'The :attribute must only contain letters.',
+    'alpha_dash' => 'The :attribute must only contain letters, numbers, dashes and underscores.',
+    'alpha_num' => 'The :attribute must only contain letters and numbers.',
+    'array' => 'The :attribute must be an array.',
+    'before' => 'The :attribute must be a date before :date.',
+    'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
+    'between' => [
+        'numeric' => 'The :attribute must be between :min and :max.',
+        'file' => 'The :attribute must be between :min and :max kilobytes.',
+        'string' => 'The :attribute must be between :min and :max characters.',
+        'array' => 'The :attribute must have between :min and :max items.',
+    ],
+    'boolean' => 'The :attribute field must be true or false.',
+    'confirmed' => 'The :attribute confirmation does not match.',
+    'current_password' => 'The password is incorrect.',
+    'date' => 'The :attribute is not a valid date.',
+    'date_equals' => 'The :attribute must be a date equal to :date.',
+    'date_format' => 'The :attribute does not match the format :format.',
+    'declined' => 'The :attribute must be declined.',
+    'declined_if' => 'The :attribute must be declined when :other is :value.',
+    'different' => 'The :attribute and :other must be different.',
+    'digits' => 'The :attribute must be :digits digits.',
+    'digits_between' => 'The :attribute must be between :min and :max digits.',
+    'dimensions' => 'The :attribute has invalid image dimensions.',
+    'distinct' => 'The :attribute field has a duplicate value.',
+    'email' => 'The :attribute must be a valid email address.',
+    'ends_with' => 'The :attribute must end with one of the following: :values.',
+    'enum' => 'The selected :attribute is invalid.',
+    'exists' => 'The selected :attribute is invalid.',
+    'file' => 'The :attribute must be a file.',
+    'filled' => 'The :attribute field must have a value.',
+    'gt' => [
+        'numeric' => 'The :attribute must be greater than :value.',
+        'file' => 'The :attribute must be greater than :value kilobytes.',
+        'string' => 'The :attribute must be greater than :value characters.',
+        'array' => 'The :attribute must have more than :value items.',
+    ],
+    'gte' => [
+        'numeric' => 'The :attribute must be greater than or equal to :value.',
+        'file' => 'The :attribute must be greater than or equal to :value kilobytes.',
+        'string' => 'The :attribute must be greater than or equal to :value characters.',
+        'array' => 'The :attribute must have :value items or more.',
+    ],
+    'image' => 'The :attribute must be an image.',
+    'in' => 'The selected :attribute is invalid.',
+    'in_array' => 'The :attribute field does not exist in :other.',
+    'integer' => 'The :attribute must be an integer.',
+    'ip' => 'The :attribute must be a valid IP address.',
+    'ipv4' => 'The :attribute must be a valid IPv4 address.',
+    'ipv6' => 'The :attribute must be a valid IPv6 address.',
+    'mac_address' => 'The :attribute must be a valid MAC address.',
+    'json' => 'The :attribute must be a valid JSON string.',
+    'lt' => [
+        'numeric' => 'The :attribute must be less than :value.',
+        'file' => 'The :attribute must be less than :value kilobytes.',
+        'string' => 'The :attribute must be less than :value characters.',
+        'array' => 'The :attribute must have less than :value items.',
+    ],
+    'lte' => [
+        'numeric' => 'The :attribute must be less than or equal to :value.',
+        'file' => 'The :attribute must be less than or equal to :value kilobytes.',
+        'string' => 'The :attribute must be less than or equal to :value characters.',
+        'array' => 'The :attribute must not have more than :value items.',
+    ],
+    'max' => [
+        'numeric' => 'The :attribute must not be greater than :max.',
+        'file' => 'The :attribute must not be greater than :max kilobytes.',
+        'string' => 'The :attribute must not be greater than :max characters.',
+        'array' => 'The :attribute must not have more than :max items.',
+    ],
+    'mimes' => 'The :attribute must be a file of type: :values.',
+    'mimetypes' => 'The :attribute must be a file of type: :values.',
+    'min' => [
+        'numeric' => 'The :attribute must be at least :min.',
+        'file' => 'The :attribute must be at least :min kilobytes.',
+        'string' => 'The :attribute must be at least :min characters.',
+        'array' => 'The :attribute must have at least :min items.',
+    ],
+    'multiple_of' => 'The :attribute must be a multiple of :value.',
+    'not_in' => 'The selected :attribute is invalid.',
+    'not_regex' => 'The :attribute format is invalid.',
+    'numeric' => 'The :attribute must be a number.',
+    'password' => 'The password is incorrect.',
+    'present' => 'The :attribute field must be present.',
+    'prohibited' => 'The :attribute field is prohibited.',
+    'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',
+    'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',
+    'prohibits' => 'The :attribute field prohibits :other from being present.',
+    'regex' => 'The :attribute format is invalid.',
+    'required' => 'The :attribute field is required.',
+    'required_if' => 'The :attribute field is required when :other is :value.',
+    'required_unless' => 'The :attribute field is required unless :other is in :values.',
+    'required_with' => 'The :attribute field is required when :values is present.',
+    'required_with_all' => 'The :attribute field is required when :values are present.',
+    'required_without' => 'The :attribute field is required when :values is not present.',
+    'required_without_all' => 'The :attribute field is required when none of :values are present.',
+    'same' => 'The :attribute and :other must match.',
+    'size' => [
+        'numeric' => 'The :attribute must be :size.',
+        'file' => 'The :attribute must be :size kilobytes.',
+        'string' => 'The :attribute must be :size characters.',
+        'array' => 'The :attribute must contain :size items.',
+    ],
+    'starts_with' => 'The :attribute must start with one of the following: :values.',
+    'string' => 'The :attribute must be a string.',
+    'timezone' => 'The :attribute must be a valid timezone.',
+    'unique' => 'The :attribute has already been taken.',
+    'uploaded' => 'The :attribute failed to upload.',
+    'url' => 'The :attribute must be a valid URL.',
+    'uuid' => 'The :attribute must be a valid UUID.',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Custom Validation Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | Here you may specify custom validation messages for attributes using the
+    | convention "attribute.rule" to name the lines. This makes it quick to
+    | specify a specific custom language line for a given attribute rule.
+    |
+    */
+
+    'custom' => [
+        'attribute-name' => [
+            'rule-name' => 'custom-message',
+        ],
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Custom Validation Attributes
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are used to swap our attribute placeholder
+    | with something more reader friendly such as "E-Mail Address" instead
+    | of "email". This simply helps us make our message more expressive.
+    |
+    */
+
+    'attributes' => [],
+
+];

+ 6 - 0
api-v13/resources/mustache/article/html/footnote.html

@@ -0,0 +1,6 @@
+<h2 id="footnote">脚注</h2>
+<div class="footnote">
+[[#footnote]]
+<div class="item" id="footnote-[[sn]]"><a href="#note-[[sn]]" name="footnote-[[sn]]">[[sn]]</a>[[trigger]]:[[content]]</div>
+[[/footnote]]
+</div>

+ 9 - 0
api-v13/resources/mustache/article/html/glossary.html

@@ -0,0 +1,9 @@
+<h2>glossary</h2>
+<div class="glossary">
+[[#pali]]
+<div class="item pali"><span class="head">[[pali]]</span><span class="content">[[meaning]]</span></div>
+[[/pali]]
+[[#meaning]]
+<div class="item meaning"><span class="head">[[meaning]]</span><span  class="content">[[pali]]</span></div>
+[[/meaning]]
+</div>

+ 87 - 0
api-v13/resources/mustache/article/html/main.html

@@ -0,0 +1,87 @@
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <title>[[book_title]]</title>
+    <style>
+        p {
+            text-indent: 2em;
+            line-height: 1.7em;
+        }
+        .sentence>p{
+            display: inline;
+        }
+        a {
+            text-decoration: none;
+        }
+        .paragraph {
+            margin-bottom: 1em;
+        }
+
+        .row {
+            display: flex;
+        }
+        img{
+            max-width: 100%;
+        }
+        @media screen and (max-width: 960px){
+            .row {
+                flex-direction: column;
+            }
+        }
+        .col {
+            flex: 5;
+        }
+        code {
+            background-color: #ffe4c478;
+            padding: 2px 6px;
+            border-radius: 4px;
+        }
+        .origin {
+            font-family: times;
+        }
+
+        table {
+            border-spacing: 0;
+            border-collapse: collapse;
+            display: block;
+            width: -webkit-max-content;
+            width: max-content;
+            max-width: 100%;
+        }
+
+        td,
+        th {
+            padding: 0;
+        }
+
+        table th {
+            font-weight: 600;
+        }
+
+        table th,
+        table td {
+            padding: 6px 13px;
+            border: 1px solid black;
+        }
+
+        table tr {
+            background-color: #ffffff;
+            border-top: 1px solid hsl(210, 18%, 87%);
+        }
+
+        table img {
+            background-color: transparent;
+        }
+    </style>
+</head>
+<body>
+<h1>[[book_title]]</h1>
+
+<h2>目录</h2>
+<ul>
+    [[#sections]]
+    <li><a href="#[[filename]]">[[filename]]</a></li>
+    [[/sections]]
+</ul>
+

+ 5 - 0
api-v13/resources/mustache/article/html/section.html

@@ -0,0 +1,5 @@
+[[#articles]]
+<h[[level]]>[[title]]</h[[level]]>
+[[#subtitle]]<div class="subtitle">[[subtitle]]</div>[[/subtitle]]
+<div class="content">[[content]]</div>
+[[/articles]]

+ 3 - 0
api-v13/resources/mustache/article/md/footnote.md

@@ -0,0 +1,3 @@
+[[#footnote]]
+\[[[sn]]\]: [[content]]
+[[/footnote]]

+ 9 - 0
api-v13/resources/mustache/article/md/glossary.md

@@ -0,0 +1,9 @@
+# glossary
+<div class="glossary">
+[[#pali]]
+<div class="item pali"><span class="head">[[pali]]</span><span class="content">[[meaning]]</span></div>
+[[/pali]]
+[[#meaning]]
+<div class="item meaning"><span class="head">[[meaning]]</span><span  class="content">[[pali]]</span></div>
+[[/meaning]]
+</div>

+ 3 - 0
api-v13/resources/mustache/article/md/main.md

@@ -0,0 +1,3 @@
+# [[book_title]]
+
+

+ 8 - 0
api-v13/resources/mustache/article/md/section.md

@@ -0,0 +1,8 @@
+[[#articles]]
+<h[[level]]>[[title]]</h[[level]]>
+
+[[#subtitle]][[subtitle]][[/subtitle]]
+
+[[content]]
+
+[[/articles]]

+ 6 - 0
api-v13/resources/mustache/chapter/html/footnote.html

@@ -0,0 +1,6 @@
+<h2 id="footnote">脚注</h2>
+<div class="footnote">
+[[#footnote]]
+<div class="item" id="footnote-[[sn]]"><a href="#note-[[sn]]" name="footnote-[[sn]]">[[sn]]</a>[[trigger]]:[[content]]</div>
+[[/footnote]]
+</div>

+ 15 - 0
api-v13/resources/mustache/chapter/html/glossary.html

@@ -0,0 +1,15 @@
+<h2>glossary</h2>
+<div class="glossary">
+<h3>Sort by Pali</h3>
+<table>
+[[#pali]]
+<tr class="item pali"><td class="head">[[pali]]</td><td class="content">[[meaning]]</td></tr>
+[[/pali]]
+</table>
+<h3>Sort by Translation</h3>
+<table>
+[[#meaning]]
+<tr class="item meaning"><td class="head">[[meaning]]</td><td  class="content">[[pali]]</td></tr>
+[[/meaning]]
+</table>
+</div>

+ 51 - 0
api-v13/resources/mustache/chapter/html/main.html

@@ -0,0 +1,51 @@
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <title>[[book_title]]</title>
+    <style>
+        p {
+            text-indent: 2em;
+            line-height: 1.7em;
+        }
+        a {
+            text-decoration: none;
+        }
+        .paragraph {
+            margin-bottom: 1em;
+        }
+
+        .row {
+            display: flex;
+        }
+        @media screen and (max-width: 960px){
+            .row {
+                flex-direction: column;
+            }
+        }
+        .col {
+            flex: 5;
+        }
+        code {
+            background-color: #ffe4c478;
+            padding: 2px 6px;
+            border-radius: 4px;
+        }
+        .origin {
+            font-family: times;
+        }
+        .col p{
+            display:inline;
+        }
+    </style>
+</head>
+<body>
+<h1>[[book_title]]</h1>
+
+<h2>目录</h2>
+<ul>
+    [[#sections]]
+    <li><a href="#[[filename]]">[[filename]]</a></li>
+    [[/sections]]
+</ul>
+

+ 6 - 0
api-v13/resources/mustache/chapter/html/paragraph.html

@@ -0,0 +1,6 @@
+<div class="paragraph row [[layout]]">
+    [[#origin]]<div class="col origin">[[origin]]</div>[[/origin]]
+    [[#translations]]
+    <div class="col translation">[[content]]</div>
+    [[/translations]]
+</div>

+ 3 - 0
api-v13/resources/mustache/chapter/html/section.html

@@ -0,0 +1,3 @@
+<h2 id="[[title]]">[[title]]</h2>
+[[content]]
+

+ 6 - 0
api-v13/resources/mustache/chapter/html/sentence.html

@@ -0,0 +1,6 @@
+<div class="row [[layout]]">
+    <div class="col origin">[[origin]]</div>
+    [[#translations]]
+    <div class="col translation">[[content]]</div>
+    [[/translations]]
+</div>

+ 3 - 0
api-v13/resources/mustache/chapter/md/footnote.md

@@ -0,0 +1,3 @@
+[[#footnote]]
+\[[[sn]]\]: [[content]]
+[[/footnote]]

+ 15 - 0
api-v13/resources/mustache/chapter/md/glossary.md

@@ -0,0 +1,15 @@
+<h2>glossary</h2>
+<div class="glossary">
+<h3>Sort by Pali</h3>
+<table>
+[[#pali]]
+<tr class="item pali"><td class="head">[[pali]]</td><td class="content">[[meaning]]</td></tr>
+[[/pali]]
+</table>
+<h3>Sort by Translation</h3>
+<table>
+[[#meaning]]
+<tr class="item meaning"><td class="head">[[meaning]]</td><td  class="content">[[pali]]</td></tr>
+[[/meaning]]
+</table>
+</div>

+ 3 - 0
api-v13/resources/mustache/chapter/md/main.md

@@ -0,0 +1,3 @@
+# [[book_title]]
+
+

+ 8 - 0
api-v13/resources/mustache/chapter/md/paragraph.md

@@ -0,0 +1,8 @@
+[[#origin]]
+[[origin]]
+[[/origin]]
+[[#translations]]
+[[content]]
+[[/translations]]
+
+

+ 4 - 0
api-v13/resources/mustache/chapter/md/section.md

@@ -0,0 +1,4 @@
+## [[title]]
+
+[[content]]
+

+ 7 - 0
api-v13/resources/mustache/chapter/md/sentence.md

@@ -0,0 +1,7 @@
+[[origin]]
+
+[[#translations]]
+[[content]]
+
+[[/translations]]
+

+ 20 - 0
api-v13/resources/mustache/chapter/tex/main.tex

@@ -0,0 +1,20 @@
+% 导言区
+\documentclass[a4paper, 12pt, fontset=ubuntu]{article} % book, report, letter
+\usepackage{ctex} % Use chinese package
+
+\title{\heiti [[book_title]]}
+\author{\kaishu [[book_author]]}
+\date{\today}
+
+% 正文区
+
+\begin{document}
+    \maketitle % 头部信息在正文显示
+    \newpage
+    \tableofcontents % 显示索引列
+
+    [[#sections]]
+    \include{ [[filename]]}
+    [[/sections]]
+
+\end{document}

+ 6 - 0
api-v13/resources/mustache/chapter/tex/paragraph.tex

@@ -0,0 +1,6 @@
+\par [[origin]]
+
+[[#translations]]
+\par [[content]]
+[[/translations]]
+

+ 3 - 0
api-v13/resources/mustache/chapter/tex/section.tex

@@ -0,0 +1,3 @@
+\section{[[title]]}
+[[content]]
+

+ 5 - 0
api-v13/resources/mustache/chapter/tex/sentence.tex

@@ -0,0 +1,5 @@
+\newline [[origin]]</div>
+[[#translations]]
+\newline [[content]]
+[[/translations]]
+

+ 158 - 0
api-v13/resources/mustache/my_han_crop.tpl

@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <style type="text/css">
+            .img {
+                max-height: 27.5%;
+                /*max-width: 85%;*/
+                overflow-x: auto;
+                overflow-y: auto;
+                border-width: 1px 0 1px 0;
+                border-style: dashed;
+                border-color: transparent;
+                resize: vertical;
+                position: relative;
+            }
+
+            .img:hover {
+                border-color: black;
+            }
+            .dict {
+                width: 45vw;
+            }
+
+            .crop_a {
+                clip-path: inset(0 0 0 15%);
+                margin-left: -15%;
+            }
+            .crop_b {
+                clip-path: inset(0 15% 0 0);
+                margin-right: -15%;
+            }
+            .word {
+                width: 10vw;
+				display: contents;
+            }
+
+            .word-img img{
+                width: 35vw;
+                margin: -5px;
+            }
+			.result{
+				max-height: 200vw;
+			    overflow: scroll;
+			}
+            /* 默认滚动条样式 */
+            .img::-webkit-scrollbar {
+                width: 6px;
+                height: 6px;
+            }
+
+            .img::-webkit-scrollbar-track {
+                background: #f1f1f1;
+                border-radius: 6px;
+            }
+
+            .img:not(:hover)::-webkit-scrollbar-track {
+                background: transparent;
+            }
+
+            .img::-webkit-scrollbar-thumb {
+                background: #888;
+                border-radius: 6px;
+                transition: all 0.3s ease;
+            }
+
+            .img:not(:hover)::-webkit-scrollbar-thumb {
+                background: transparent;
+                border-radius: 6px;
+                transition: all 0.3s ease;
+            }
+
+            .img::-webkit-scrollbar-thumb:hover {
+                background: #555;
+            }
+            .result::-webkit-scrollbar {
+                width: 6px;
+                height: 6px;
+            }
+
+            .result::-webkit-scrollbar-track {
+                background: #f1f1f1;
+                border-radius: 6px;
+            }
+
+            .result:not(:hover)::-webkit-scrollbar-track {
+                background: transparent;
+            }
+
+            .result::-webkit-scrollbar-thumb {
+                background: #888;
+                border-radius: 6px;
+                transition: all 0.3s ease;
+            }
+
+            .result:not(:hover)::-webkit-scrollbar-thumb {
+                background: transparent;
+                border-radius: 6px;
+                transition: all 0.3s ease;
+            }
+
+            .result::-webkit-scrollbar-thumb:hover {
+                background: #555;
+            }
+            .copied {
+                color: #01955e;
+                display: none;
+                position: fixed;
+            }
+        </style>
+        <script>
+            function copy(text, index) {
+                navigator.clipboard.writeText(text).then(() => {
+                    show(index);
+                    setTimeout(() => {
+                        hide(index);
+                    }, 2000);
+                });
+            }
+            function show(index) {
+                let el = document.getElementById("copied-" + index);
+                el.style.display = "inline-block";
+            }
+            function hide(index) {
+                let el = document.getElementById("copied-" + index);
+                el.style.display = "none";
+            }
+        </script>
+    </head>
+    <body>
+        <h3>Page {{ page }}</h3>
+        <div style="display: flex">
+            <div>
+                {{#dict}}
+                <div class="img">
+                    <img class="dict crop_{{ index }}" src="{{ img }}" />
+                </div>
+                {{/dict}}
+            </div>
+            <div>
+                <table>
+                    {{#words}}
+                    <tr>
+                        <td class="word">{{ index }}</td>
+                        <td class="word">
+                            {{ word }}
+                            <button onclick="copy('{{ word }}',{{ index }})">复制</button>
+                            <span class="copied" id="copied-{{ index }}">已经复制</span>
+                        </td>
+                        <td class="word-img">
+                            <img src="img/{{ word }}.png" />
+                        </td>
+                    </tr>
+                    {{/words}}
+                </table>
+            </div>
+        </div>
+    </body>
+</html>

+ 11 - 0
api-v13/resources/mustache/nissaya_ending_card.tpl

@@ -0,0 +1,11 @@
+### {{ending}}-{{ending_tag}}
+
+**{{ending_meaning}}**
+
+{{ending_note}}
+
+|{{title_case}}|{{title_relation}}|{{title_local_link_to}}|{{title_content}}|{{title_local_ending}}|
+|-|-|-|-|-|
+{{#row}}
+|{{spell}}{{#case}}<a href='{{link}}' target='_blank'>{{label}}</a> {{/case}}|<a href='{{relation_link}}' target='_blank'>`{{relation}}`</a><br />{{local_relation}}|{{#to}}<a href='{{link}}' target='_blank'>`{{label}}`</a> {{/to}}|{{category_note}}|{{local_ending}}||
+{{/row}}

BIN
api-v13/resources/template/docx/paper.docx


+ 323 - 0
api-v13/resources/views/ananke.blade.php

@@ -0,0 +1,323 @@
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+
+    <title>Ananke: a Hugo Theme | Ananke</title>
+    <meta name="viewport" content="width=device-width,minimum-scale=1" />
+    <meta
+      name="description"
+      content="The last theme you'll ever need. Maybe."
+    />
+    <meta name="generator" content="Hugo 0.92.1" />
+
+    <meta name="robots" content="noindex, nofollow" />
+
+    <link rel="stylesheet" href="{{ URL::asset('assets/css/ananke/main.min.css') }}" />
+
+    <link
+      href="/index.xml"
+      rel="alternate"
+      type="application/rss+xml"
+      title="Ananke"
+    />
+    <link
+      href="/index.xml"
+      rel="feed"
+      type="application/rss+xml"
+      title="Ananke"
+    />
+
+    <meta property="og:title" content="Ananke: a Hugo Theme" />
+    <meta
+      property="og:description"
+      content="The last theme you'll ever need. Maybe."
+    />
+    <meta property="og:type" content="website" />
+    <meta
+      property="og:url"
+      content="https://gohugo-ananke-theme-demo.netlify.app/"
+    />
+    <meta property="og:site_name" content="Ananke" />
+
+    <meta itemprop="name" content="Ananke: a Hugo Theme" />
+    <meta
+      itemprop="description"
+      content="The last theme you'll ever need. Maybe."
+    />
+    <meta name="twitter:card" content="summary" />
+    <meta name="twitter:title" content="Ananke: a Hugo Theme" />
+    <meta
+      name="twitter:description"
+      content="The last theme you'll ever need. Maybe."
+    />
+  </head>
+
+  <body
+    class="ma0 avenir bg-near-white"
+  >
+    <header
+      class="cover bg-top"
+      style="background-image: url('{{ URL::asset('assets/images/hero.jpg') }}');"
+    >
+      <div class="bg-black-60">
+        <nav class="pv3 ph3 ph4-ns" role="navigation">
+          <div class="flex-l justify-between items-center center">
+            <a href="/" class="f3 fw2 hover-white no-underline white-90 dib">
+              wikipali
+            </a>
+            <div class="flex-l items-center">
+              <h4></h4>
+              <ul class="pl0 mr3">
+                @foreach ($nav as $item)
+                <li class="list f5 f4-ns fw4 dib pr3">
+                  <a
+                    class="hover-white no-underline white-90"
+                    href="/pcd/{{ $item['link'] }}"
+                    title="{{ $item['title'] }}"
+                  >
+                    {{ $item['title'] }}
+                  </a>
+                </li>
+                @endforeach
+              </ul>
+
+            </div>
+          </div>
+        </nav>
+
+        <div class="tc-l pv4 pv6-l ph3 ph4-ns">
+          <h1 class="f2 f-subheadline-l fw2 white-90 mb0 lh-title">
+            {{ $title }}
+          </h1>
+
+          <h2 class="fw1 f5 f3-l white-80 measure-wide-l center mt3">
+          {{ $subtitle }}
+          </h2>
+        </div>
+      </div>
+    </header>
+
+    <main class="pb7" role="main">
+      <article
+        class="cf ph3 ph5-l pv3 pv4-l f4 tc-l center measure-wide lh-copy mid-gray"
+      >
+        <p>
+          Welcome to my blog with some of my work in progress. I’ve been working
+          on this book idea. You can read some of the chapters below.
+        </p>
+      </article>
+
+      <div class="pa3 pa4-ns w-100 w-70-ns center">
+        <section class="w-100 mw8">
+          <div class="relative w-100 mb4">
+            <article class="bb b--black-10">
+              <div class="db pv4 ph3 ph0-l no-underline dark-gray">
+                <div class="flex flex-column flex-row-ns">
+                  <div class="pr3-ns mb4 mb0-ns w-100 w-40-ns">
+                    <a href="/post/chapter-6/" class="db grow">
+                      <img
+                        src="https://gohugo-ananke-theme-demo.netlify.app/images/esmeralda.jpg"
+                        class="img"
+                        alt="image from Chapter VI: Esmeralda"
+                      />
+                    </a>
+                  </div>
+
+                  <div class="blah w-100 w-60-ns pl3-ns">
+                    <h1 class="f3 fw1 athelas mt0 lh-title">
+                      <a href="/post/chapter-6/" class="color-inherit dim link">
+                        Chapter VI: Esmeralda
+                      </a>
+                    </h1>
+                    <div
+                      class="f6 f5-l lh-copy nested-copy-line-height nested-links"
+                    >
+                      We are delighted to be able to inform the reader, that
+                      during the whole of this scene, Gringoire and his piece
+                      had stood firm. His actors, spurred on by him, had not
+                      ceased to spout his comedy, and he had not ceased to
+                      listen to it. He had made up his mind about the tumult,
+                      and was determined to proceed to the end, not giving up
+                      the hope of a return of attention on the part of the
+                      public.
+                    </div>
+                    <a
+                      href="/post/chapter-6/"
+                      class="ba b--moon-gray bg-light-gray br2 color-inherit dib f7 hover-bg-moon-gray link mt2 ph2 pv1"
+                      >read more</a
+                    >
+                  </div>
+                </div>
+              </div>
+            </article>
+          </div>
+
+          <div class="relative w-100 mb4">
+            <article class="bb b--black-10">
+              <div class="db pv4 ph3 ph0-l no-underline dark-gray">
+                <div class="flex flex-column flex-row-ns">
+                  <div class="blah w-100">
+                    <h1 class="f3 fw1 athelas mt0 lh-title">
+                      <a href="/post/chapter-5/" class="color-inherit dim link">
+                        Chapter V: Quasimodo
+                      </a>
+                    </h1>
+                    <div
+                      class="f6 f5-l lh-copy nested-copy-line-height nested-links"
+                    >
+                      In the twinkling of an eye, all was ready to execute
+                      Coppenole’s idea. Bourgeois, scholars and law clerks all
+                      set to work. The little chapel situated opposite the
+                      marble table was selected for the scene of the grinning
+                      match. A pane broken in the pretty rose window above the
+                      door, left free a circle of stone through which it was
+                      agreed that the competitors should thrust their heads. In
+                      order to reach it, it was only necessary to mount upon a
+                      couple of hogsheads, which had been produced from I know
+                      not where, and perched one upon the other, after a
+                      fashion.
+                    </div>
+                    <a
+                      href="/post/chapter-5/"
+                      class="ba b--moon-gray bg-light-gray br2 color-inherit dib f7 hover-bg-moon-gray link mt2 ph2 pv1"
+                      >read more</a
+                    >
+                  </div>
+                </div>
+              </div>
+            </article>
+          </div>
+
+          <div class="relative w-100 mb4">
+            <article class="bb b--black-10">
+              <div class="db pv4 ph3 ph0-l no-underline dark-gray">
+                <div class="flex flex-column flex-row-ns">
+                  <div class="blah w-100">
+                    <h1 class="f3 fw1 athelas mt0 lh-title">
+                      <a href="/post/chapter-4/" class="color-inherit dim link">
+                        Chapter IV: Master Jacques Coppenole
+                      </a>
+                    </h1>
+                    <div
+                      class="f6 f5-l lh-copy nested-copy-line-height nested-links"
+                    >
+                      While the pensioner of Ghent and his eminence were
+                      exchanging very low bows and a few words in voices still
+                      lower, a man of lofty stature, with a large face and broad
+                      shoulders, presented himself, in order to enter abreast
+                      with Guillaume Rym; one would have pronounced him a
+                      bull-dog by the side of a fox. His felt doublet and
+                      leather jerkin made a spot on the velvet and silk which
+                      surrounded him.
+                    </div>
+                    <a
+                      href="/post/chapter-4/"
+                      class="ba b--moon-gray bg-light-gray br2 color-inherit dib f7 hover-bg-moon-gray link mt2 ph2 pv1"
+                      >read more</a
+                    >
+                  </div>
+                </div>
+              </div>
+            </article>
+          </div>
+        </section>
+
+        <section class="w-100">
+          <h1 class="f3">More</h1>
+
+          <h2 class="f5 fw4 mb4 dib mr3">
+            <a href="/post/chapter-3/" class="link black dim">
+              Chapter III: Monsieur the Cardinal
+            </a>
+          </h2>
+
+          <h2 class="f5 fw4 mb4 dib mr3">
+            <a href="/post/chapter-2/" class="link black dim">
+              Chapter II: Pierre Gringoire
+            </a>
+          </h2>
+
+          <h2 class="f5 fw4 mb4 dib mr3">
+            <a href="/post/chapter-1/" class="link black dim">
+              Chapter I: The Grand Hall
+            </a>
+          </h2>
+
+          <a
+            href="/post/"
+            class="link db f6 pa2 br3 bg-mid-gray white dim w4 tc"
+            >All Articles</a
+          >
+        </section>
+      </div>
+    </main>
+    <footer class="bg-black bottom-0 w-100 pa3" role="contentinfo">
+      <div class="flex justify-between">
+        <a
+          class="f4 fw4 hover-white no-underline white-70 dn dib-ns pv2 ph3"
+          href="https://gohugo-ananke-theme-demo.netlify.app"
+        >
+          © Ananke 2022
+        </a>
+        <div>
+          <div class="ananke-socials">
+            <a
+              href="https://twitter.com/GoHugoIO"
+              target="_blank"
+              class="twitter ananke-social-link link-transition stackoverflow link dib z-999 pt3 pt0-l mr1"
+              title="Twitter link"
+              rel="noopener"
+              aria-label="follow on Twitter——Opens in a new window"
+            >
+              <span class="icon"
+                ><svg
+                  style="enable-background: new 0 0 67 67"
+                  version="1.1"
+                  viewBox="0 0 67 67"
+                  xml:space="preserve"
+                  xmlns="http://www.w3.org/2000/svg"
+                  xmlns:xlink="http://www.w3.org/1999/xlink"
+                >
+                  <path
+                    d="M37.167,22.283c-2.619,0.953-4.274,3.411-4.086,6.101  l0.063,1.038l-1.048-0.127c-3.813-0.487-7.145-2.139-9.974-4.915l-1.383-1.377l-0.356,1.017c-0.754,2.267-0.272,4.661,1.299,6.271  c0.838,0.89,0.649,1.017-0.796,0.487c-0.503-0.169-0.943-0.296-0.985-0.233c-0.146,0.149,0.356,2.076,0.754,2.839  c0.545,1.06,1.655,2.097,2.871,2.712l1.027,0.487l-1.215,0.021c-1.173,0-1.215,0.021-1.089,0.467  c0.419,1.377,2.074,2.839,3.918,3.475l1.299,0.444l-1.131,0.678c-1.676,0.976-3.646,1.526-5.616,1.568  C19.775,43.256,19,43.341,19,43.405c0,0.211,2.557,1.397,4.044,1.864c4.463,1.377,9.765,0.783,13.746-1.568  c2.829-1.673,5.657-5,6.978-8.221c0.713-1.716,1.425-4.851,1.425-6.354c0-0.975,0.063-1.102,1.236-2.267  c0.692-0.678,1.341-1.419,1.467-1.631c0.21-0.403,0.188-0.403-0.88-0.043c-1.781,0.636-2.033,0.551-1.152-0.402  c0.649-0.678,1.425-1.907,1.425-2.267c0-0.063-0.314,0.042-0.671,0.233c-0.377,0.212-1.215,0.53-1.844,0.72l-1.131,0.361l-1.027-0.7  c-0.566-0.381-1.361-0.805-1.781-0.932C39.766,21.902,38.131,21.944,37.167,22.283z M33,64C16.432,64,3,50.569,3,34S16.432,4,33,4  s30,13.431,30,30S49.568,64,33,64z"
+                    style="fill-rule: evenodd; clip-rule: evenodd"
+                  ></path>
+                </svg>
+              </span>
+
+              <span class="new-window"
+                ><svg
+                  height="8px"
+                  style="enable-background: new 0 0 1000 1000"
+                  version="1.1"
+                  viewBox="0 0 1000 1000"
+                  xml:space="preserve"
+                  xmlns="http://www.w3.org/2000/svg"
+                  xmlns:xlink="http://www.w3.org/1999/xlink"
+                >
+                  <path
+                    d="M598 128h298v298h-86v-152l-418 418-60-60 418-418h-152v-86zM810 810v-298h86v298c0 46-40 86-86 86h-596c-48 0-86-40-86-86v-596c0-46 38-86 86-86h298v86h-298v596h596z"
+                    style="fill-rule: evenodd; clip-rule: evenodd"
+                  ></path>
+                </svg> </span
+            ></a>
+          </div>
+        </div>
+      </div>
+    </footer>
+
+    <iframe
+      frameborder="0"
+      scrolling="no"
+      style="background-color: transparent; border: 0px; display: none"
+    ></iframe>
+    <div
+      id="GOOGLE_INPUT_CHEXT_FLAG"
+      input=""
+      input_stat='{"tlang":true,"tsbc":true,"pun":true,"mk":true,"ss":true}'
+      style="display: none"
+    ></div>
+    <div id="monica-content-root" class="monica-widget"></div>
+  </body>
+</html>

+ 67 - 0
api-v13/resources/views/blog/category.blade.php

@@ -0,0 +1,67 @@
+{{-- resources/views/blog/category.blade.php --}}
+@extends('blog.layouts.app')
+
+@section('title', $user['nickName'] . ' · ' . collect($current)->pluck('label')->implode(' / '))
+
+@section('content')
+
+<header>
+    <h3 class="section-title">Categories</h3>
+    <div class="section-card">
+        <div class="section-details">
+            <h3 class="section-count">{{ $count }} 篇</h3>
+            <h1 class="section-term">
+                @foreach($current as $category)
+                / <a href="{{ route('blog.category', ['user' => $user['userName'], 'category1' => $category['id']]) }}">
+                    {{ $category['label'] }}
+                </a>
+                @endforeach
+            </h1>
+
+            {{-- 子分类标签 --}}
+            @if(!empty($tagOptions))
+            <section class="widget tagCloud">
+                <div class="tagCloud-tags">
+                    @foreach($tagOptions as $id => $tag)
+                        @if($tag['count'] < $count)
+                        <a href="{{ rtrim(url()->current(), '/') . '/' . $tag['tag']->name }}">
+                            {{ $tag['tag']->name }} ({{ $tag['count'] }})
+                        </a>
+                        @endif
+                    @endforeach
+                </div>
+            </section>
+            @endif
+        </div>
+    </div>
+</header>
+
+<section class="article-list--compact">
+    @forelse($posts as $post)
+    <article>
+        <a href="{{ route('library.tipitaka.read', ['id' => $post['uid']]) }}">
+            <div class="article-details">
+                <h2 class="article-title">{{ $post->title }}</h2>
+                <footer class="article-time">
+                    <time>{{ $post->formatted_updated_at }}</time>
+                </footer>
+            </div>
+
+            @if(!empty($post->cover))
+            <div class="article-image">
+                <img src="{{ $post->cover }}"
+                     width="120" height="120"
+                     alt="{{ $post->title }}"
+                     loading="lazy" />
+            </div>
+            @endif
+        </a>
+    </article>
+    @empty
+    <div class="not-found-card" style="padding: 20px;">
+        <p>此分类下暂无文章</p>
+    </div>
+    @endforelse
+</section>
+
+@endsection

+ 93 - 0
api-v13/resources/views/blog/index.blade.php

@@ -0,0 +1,93 @@
+{{-- resources/views/blog/index.blade.php --}}
+@extends('blog.layouts.app')
+
+@section('title', $user['nickName'])
+
+@section('content')
+<h2 class="section-title">推荐阅读</h2>
+<div class="subsection-list">
+    <div class="article-list--tile">
+        <article class="has-image"><a href="/categories/documentation/">
+                <div class="article-image"><img alt="Featured image of post Documentation" height="150" loading="lazy" src="/categories/documentation/hutomo-abrianto-l2jk-uxb1BY-unsplash_hu_543ba73fab19d0c8.jpg" srcset="/categories/documentation/hutomo-abrianto-l2jk-uxb1BY-unsplash_hu_543ba73fab19d0c8.jpg 1x, /categories/documentation/hutomo-abrianto-l2jk-uxb1BY-unsplash_hu_2b27d90ffb967881.jpg 2x" width="250"></div>
+                <div class="article-details" style="background:linear-gradient(0deg,#bc8d7280 0%,#d0cfcdC0 100%)">
+                    <h2 class="article-title">Documentation</h2>
+                </div>
+            </a></article>
+        <article><a href="/categories/syntax/">
+                <div class="article-details">
+                    <h2 class="article-title">Syntax</h2>
+                </div>
+            </a></article>
+        <article><a href="/categories/themes/">
+                <div class="article-details">
+                    <h2 class="article-title">Themes</h2>
+                </div>
+            </a></article>
+    </div>
+</div>
+<section class="article-list">
+    @forelse($posts as $post)
+    <article class="{{ !empty($post->cover) ? 'has-image' : '' }}">
+        <header class="article-header">
+
+            @if(!empty($post->cover))
+            <div class="article-image">
+                <a href="{{ route('library.tipitaka.read', ['id' => $post['uid']]) }}">
+                    <img src="{{ $post->cover }}"
+                        width="800" height="450"
+                        loading="lazy"
+                        alt="{{ $post->title }}" />
+                </a>
+            </div>
+            @endif
+
+            <div class="article-details">
+
+                @if(!empty($post->categories))
+                <header class="article-category">
+                    @foreach($post->categories as $category)
+                    <a href="{{ route('blog.category', ['user' => $user['userName'], 'category1' => $category['id']]) }}">
+                        {{ $category['label'] }}
+                    </a>
+                    @endforeach
+                </header>
+                @endif
+
+                <div class="article-title-wrapper">
+                    <h2 class="article-title">
+                        <a href="{{ route('library.tipitaka.read', ['id' => $post['uid']]) }}">
+                            {{ $post->title }}
+                        </a>
+                    </h2>
+                    @if(!empty($post->summary))
+                    <h3 class="article-subtitle">{{ $post->summary }}</h3>
+                    @endif
+                </div>
+
+                <footer class="article-time">
+                    <div>
+                        <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-calendar-time"
+                            width="24" height="24" viewBox="0 0 24 24" stroke-width="2"
+                            stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                            <path stroke="none" d="M0 0h24v24H0z"></path>
+                            <path d="M11.795 21h-6.795a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v4"></path>
+                            <circle cx="18" cy="18" r="4"></circle>
+                            <path d="M15 3v4"></path>
+                            <path d="M7 3v4"></path>
+                            <path d="M3 11h16"></path>
+                            <path d="M18 16.496v1.504l1 1"></path>
+                        </svg>
+                        <time>{{ $post->formatted_updated_at }}</time>
+                    </div>
+                </footer>
+
+            </div>
+        </header>
+    </article>
+    @empty
+    <div class="not-found-card">
+        <p>暂无文章</p>
+    </div>
+    @endforelse
+</section>
+@endsection

+ 318 - 0
api-v13/resources/views/blog/layouts/app.blade.php

@@ -0,0 +1,318 @@
+{{-- resources/views/layouts/blog.blade.php
+     Blog 栏目布局。继承 layouts/base。
+     使用 Stack 主题 CSS(静态文件,不走 Vite)。
+     三栏结构:左边栏(博主信息+导航)/ 正文 / 右边栏(搜索+分类+标签)。
+     右边栏数据($categories, $tags, $archives)由 View Composer 自动注入。
+--}}
+@extends('layouts.base')
+
+@push('styles')
+    <link rel="stylesheet"
+          href="{{ URL::asset('assets/css/blog/style.min.css') }}">
+    <link href="{{ URL::asset('assets/css/blog/css2') }}"
+          type="text/css" rel="stylesheet">
+@endpush
+
+@section('body-class', '')
+
+@section('page')
+
+<div class="container main-container flex on-phone--column extended">
+
+    {{-- ── 左边栏 ── --}}
+    <aside class="sidebar left-sidebar sticky">
+
+        <button class="hamburger hamburger--spin"
+                type="button"
+                id="toggle-menu"
+                aria-label="Toggle Menu">
+            <span class="hamburger-box">
+                <span class="hamburger-inner"></span>
+            </span>
+        </button>
+
+        <header>
+            <figure class="site-avatar">
+                <a href="{{ route('blog.index', ['user' => $user['userName']]) }}">
+                    <img src="{{ $user['avatar'] ?? '' }}"
+                         width="300" height="300"
+                         class="site-logo"
+                         loading="lazy"
+                         alt="Avatar" />
+                </a>
+                @if(!empty($user['level']))
+                <span class="emoji" style="font-size: 11px;">LV{{ $user['level'] }}</span>
+                @endif
+            </figure>
+
+            <div class="site-meta">
+                <h1 class="site-name">
+                    <a href="{{ route('blog.index', ['user' => $user['userName']]) }}">
+                        {{ $user['nickName'] }}
+                    </a>
+                </h1>
+                @if(!empty($user['description']))
+                <h2 class="site-description">{{ $user['description'] }}</h2>
+                @endif
+            </div>
+        </header>
+
+        {{-- 社交链接 --}}
+        @if(!empty($user['social']))
+        <ol class="menu-social">
+            @foreach($user['social'] as $social)
+            <li>
+                <a href="{{ $social['url'] }}"
+                   target="_blank"
+                   title="{{ $social['name'] }}"
+                   rel="me">
+                    <i class="ti ti-brand-{{ strtolower($social['name']) }}"></i>
+                </a>
+            </li>
+            @endforeach
+        </ol>
+        @endif
+
+        {{-- 主导航 --}}
+        <ol class="menu" id="main-menu">
+            <li class="{{ request()->routeIs('blog.index') ? 'current' : '' }}">
+                <a href="{{ route('blog.index', ['user' => $user['userName']]) }}">
+                    <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-home"
+                         width="24" height="24" viewBox="0 0 24 24" stroke-width="2"
+                         stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                        <path stroke="none" d="M0 0h24v24H0z"></path>
+                        <polyline points="5 12 3 12 12 3 21 12 19 12"></polyline>
+                        <path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7"></path>
+                        <path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6"></path>
+                    </svg>
+                    <span>Home</span>
+                </a>
+            </li>
+
+            <li>
+                <a href="#">
+                    <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user"
+                         width="24" height="24" viewBox="0 0 24 24" stroke-width="2"
+                         stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                        <path stroke="none" d="M0 0h24v24H0z"></path>
+                        <circle cx="12" cy="7" r="4"></circle>
+                        <path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"></path>
+                    </svg>
+                    <span>About</span>
+                </a>
+            </li>
+
+            <li class="{{ request()->routeIs('blog.archives*') ? 'current' : '' }}">
+                <a href="{{ route('blog.archives', ['user' => $user['userName']]) }}">
+                    <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-archive"
+                         width="24" height="24" viewBox="0 0 24 24" stroke-width="2"
+                         stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                        <path stroke="none" d="M0 0h24v24H0z"></path>
+                        <rect x="3" y="4" width="18" height="4" rx="2"></rect>
+                        <path d="M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-10"></path>
+                        <line x1="10" y1="12" x2="14" y2="12"></line>
+                    </svg>
+                    <span>Archives</span>
+                </a>
+            </li>
+
+            <li class="{{ request()->routeIs('blog.search') ? 'current' : '' }}">
+                <a href="{{ route('blog.search', ['user' => $user['userName']]) }}">
+                    <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-search"
+                         width="24" height="24" viewBox="0 0 24 24" stroke-width="2"
+                         stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                        <path stroke="none" d="M0 0h24v24H0z"></path>
+                        <circle cx="10" cy="10" r="7"></circle>
+                        <line x1="21" y1="21" x2="15" y2="15"></line>
+                    </svg>
+                    <span>Search</span>
+                </a>
+            </li>
+
+            {{-- 暗色模式切换 --}}
+            <li class="menu-bottom-section">
+                <ol class="menu">
+                    <li id="dark-mode-toggle">
+                        <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-toggle-left"
+                             width="24" height="24" viewBox="0 0 24 24" stroke-width="2"
+                             stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                            <path stroke="none" d="M0 0h24v24H0z"></path>
+                            <circle cx="8" cy="12" r="2"></circle>
+                            <rect x="2" y="6" width="20" height="12" rx="6"></rect>
+                        </svg>
+                        <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-toggle-right"
+                             width="24" height="24" viewBox="0 0 24 24" stroke-width="2"
+                             stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                            <path stroke="none" d="M0 0h24v24H0z"></path>
+                            <circle cx="16" cy="12" r="2"></circle>
+                            <rect x="2" y="6" width="20" height="12" rx="6"></rect>
+                        </svg>
+                        <span>Dark Mode</span>
+                    </li>
+                </ol>
+            </li>
+        </ol>
+
+    </aside>
+
+    {{-- ── 正文 ── --}}
+    <main class="main full-width">
+        <div>
+            @yield('content')
+        </div>
+
+        <footer class="site-footer">
+            {{-- 博主版权 --}}
+            <section class="copyright">
+                &copy; 2020 - {{ date('Y') }} {{ $user['nickName'] }}
+            </section>
+
+            {{-- 主题版权(原样保留设计者信息) --}}
+            <section class="powerby">
+                &copy; 2020 - 2026 Hugo Theme Stack &nbsp;
+                Theme <b><a href="https://github.com/CaiJimmy/hugo-theme-stack"
+                             target="_blank" rel="noopener" data-version="3.30.0">Stack</a></b>
+                designed by
+                <a href="https://jimmycai.com/" target="_blank" rel="noopener">Jimmy</a>
+            </section>
+        </footer>
+    </main>
+
+    {{-- ── 右边栏 ── --}}
+    <aside class="sidebar right-sidebar sticky">
+
+        {{-- 搜索 --}}
+        <form action="{{ route('blog.search', ['user' => $user['userName']]) }}"
+              method="GET"
+              class="search-form widget">
+            <p>
+                <label>Search</label>
+                <input name="q"
+                       placeholder="Type something..."
+                       value="{{ request('q') }}" />
+                <button type="submit" title="Search">
+                    <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-search"
+                         width="24" height="24" viewBox="0 0 24 24" stroke-width="2"
+                         stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                        <path stroke="none" d="M0 0h24v24H0z"></path>
+                        <circle cx="10" cy="10" r="7"></circle>
+                        <line x1="21" y1="21" x2="15" y2="15"></line>
+                    </svg>
+                </button>
+            </p>
+        </form>
+
+        {{-- Archives --}}
+        @if(!empty($archives))
+        <section class="widget archives">
+            <div class="widget-icon">
+                <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-infinity"
+                     width="24" height="24" viewBox="0 0 24 24" stroke-width="2"
+                     stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                    <path stroke="none" d="M0 0h24v24H0z"></path>
+                    <path d="M9.828 9.172a4 4 0 1 0 0 5.656 a10 10 0 0 0 2.172 -2.828a10 10 0 0 1 2.172 -2.828 a4 4 0 1 1 0 5.656a10 10 0 0 1 -2.172 -2.828a10 10 0 0 0 -2.172 -2.828"></path>
+                </svg>
+            </div>
+            <h2 class="widget-title section-title">Archives</h2>
+            <div class="widget-archive--list">
+                @foreach($archives as $archive)
+                <div class="archives-year">
+                    <a href="{{ route('blog.archives.year', ['user' => $user['userName'], 'year' => $archive['year']]) }}">
+                        <span class="year">{{ $archive['year'] }}</span>
+                        <span class="count">{{ $archive['count'] }}</span>
+                    </a>
+                </div>
+                @endforeach
+            </div>
+        </section>
+        @endif
+
+        {{-- Categories --}}
+        @if(!empty($categories))
+        <section class="widget tagCloud">
+            <div class="widget-icon">
+                <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hash"
+                     width="24" height="24" viewBox="0 0 24 24" stroke-width="2"
+                     stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                    <path stroke="none" d="M0 0h24v24H0z"></path>
+                    <line x1="5" y1="9" x2="19" y2="9"></line>
+                    <line x1="5" y1="15" x2="19" y2="15"></line>
+                    <line x1="11" y1="4" x2="7" y2="20"></line>
+                    <line x1="17" y1="4" x2="13" y2="20"></line>
+                </svg>
+            </div>
+            <h2 class="widget-title section-title">Categories</h2>
+            <div class="tagCloud-tags">
+                @foreach($categories as $category)
+                <a href="{{ route('blog.category', ['user' => $user['userName'], 'category1' => $category['id']]) }}">
+                    {{ $category['label'] }}
+                </a>
+                @endforeach
+            </div>
+        </section>
+        @endif
+
+        {{-- Tags --}}
+        @if(!empty($tags))
+        <section class="widget tagCloud">
+            <div class="widget-icon">
+                <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-tag"
+                     width="24" height="24" viewBox="0 0 24 24" stroke-width="2"
+                     stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                    <path stroke="none" d="M0 0h24v24H0z"></path>
+                    <path d="M11 3L20 12a1.5 1.5 0 0 1 0 2L14 20a1.5 1.5 0 0 1 -2 0L3 11v-4a4 4 0 0 1 4 -4h4"></path>
+                    <circle cx="9" cy="9" r="2"></circle>
+                </svg>
+            </div>
+            <h2 class="widget-title section-title">Tags</h2>
+            <div class="tagCloud-tags">
+                @foreach($tags as $tag)
+                <a href="{{ route('blog.tag', ['user' => $user['userName'], 'tag' => $tag['name']]) }}"
+                   class="font_size_1">
+                    {{ $tag['name'] }}
+                </a>
+                @endforeach
+            </div>
+        </section>
+        @endif
+
+    </aside>
+
+</div>
+
+@push('scripts')
+<script>
+    (function() {
+        const colorSchemeKey = "StackColorScheme";
+        if (!localStorage.getItem(colorSchemeKey)) {
+            localStorage.setItem(colorSchemeKey, "auto");
+        }
+    })();
+    (function() {
+        const colorSchemeKey = "StackColorScheme";
+        const colorSchemeItem = localStorage.getItem(colorSchemeKey);
+        const supportDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches === true;
+        if (colorSchemeItem == "dark" || (colorSchemeItem === "auto" && supportDarkMode)) {
+            document.documentElement.dataset.scheme = "dark";
+        } else {
+            document.documentElement.dataset.scheme = "light";
+        }
+    })();
+</script>
+<script src="{{ URL::asset('assets/js/blog/vibrant.min.js') }}"
+        integrity="sha256-awcR2jno4kI5X0zL8ex0vi2z+KMkF24hUW8WePSA9HM="
+        crossorigin="anonymous"></script>
+<script src="{{ URL::asset('assets/js/blog/main.1e9a3bafd846ced4c345d084b355fb8c7bae75701c338f8a1f8a82c780137826.js') }}"
+        type="text/javascript" defer></script>
+<script>
+    (function() {
+        const customFont = document.createElement("link");
+        customFont.href = "https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap";
+        customFont.type = "text/css";
+        customFont.rel = "stylesheet";
+        document.head.appendChild(customFont);
+    })();
+</script>
+@endpush
+
+@endsection

+ 74 - 0
api-v13/resources/views/book.blade.php

@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
+    <title>Universal Viewer</title>
+    <link
+        rel="stylesheet"
+        href="https://cdn.jsdelivr.net/npm/universalviewer@4.0.0/dist/uv.css" />
+    <script
+        type="application/javascript"
+        src="https://cdn.jsdelivr.net/npm/universalviewer@4.0.0/dist/umd/UV.js"></script>
+    <style>
+        #uv {
+            width: 100%;
+            height: 668px;
+        }
+
+        /* 自定义按钮样式 */
+        .custom-menu-button {
+            padding: 5px 10px;
+            margin: 0 5px;
+            cursor: pointer;
+        }
+    </style>
+</head>
+
+<body>
+    <div class="uv" id="uv"></div>
+    <div id="custom-menu">
+        <button id="get-page-id" onclick="getPageId()">获取当前页面 ID</button>
+    </div>
+    <script>
+        const data = {
+            manifest: "https://wellcomelibrary.org/iiif/b18035723/manifest",
+            embedded: true // needed for codesandbox frame
+        };
+
+        uv = UV.init("uv", data);
+
+        // 监听 Universal Viewer 初始化完成事件
+        uv.on('initialized', function() {
+            // 创建自定义按钮
+            var customButton = document.createElement('button');
+            customButton.textContent = '获取当前页面 ID';
+            customButton.className = 'custom-menu-button';
+
+            // 为自定义按钮添加点击事件监听器
+            customButton.addEventListener('click', function() {
+                // 获取当前页面的索引
+                var currentCanvas = uv.extension.getState().canvasIndex;
+                // 获取当前页面的 ID
+                var canvasId = uv.extension.getContent().canvases[currentCanvas].id;
+                // 弹出提示框显示当前页面 ID
+                alert('当前页面 ID: ' + canvasId);
+                console.info('当前页面 ID: ', canvasId)
+            });
+
+            // 获取 Universal Viewer 的菜单容器
+            var menu = document.querySelector('.options');
+            // 将自定义按钮添加到菜单容器中
+            menu.appendChild(customButton);
+        });
+
+        function getPageId() {
+            var canvas = uv.extension.helper.getCurrentCanvas();
+            console.log("当前页面 Canvas ID:", canvas.id);
+        }
+    </script>
+</body>
+
+</html>

+ 165 - 0
api-v13/resources/views/components/language-switcher.blade.php

@@ -0,0 +1,165 @@
+@php
+$currentLocale = app()->getLocale();
+$languages = config('mint.languages');
+$currentLanguage = $languages[$currentLocale] ?? 'en';
+@endphp
+
+<div class="language-switcher">
+    <a href="#" class="language-link">
+        <svg class="language-icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
+            <path d="M512 929.959184c-230.4 0-417.959184-187.559184-417.959184-417.959184s187.559184-417.959184 417.959184-417.959184 417.959184 187.559184 417.959184 417.959184-187.559184 417.959184-417.959184 417.959184z m0-794.122449c-207.412245 0-376.163265 168.75102-376.163265 376.163265s168.75102 376.163265 376.163265 376.163265 376.163265-168.75102 376.163265-376.163265-168.75102-376.163265-376.163265-376.163265z" p-id="4472"></path>
+            <path d="M512 929.959184c-11.493878 0-20.897959-9.404082-20.897959-20.89796V114.938776c0-11.493878 9.404082-20.897959 20.897959-20.89796s20.897959 9.404082 20.897959 20.89796v794.122448c0 11.493878-9.404082 20.897959-20.897959 20.89796z" p-id="4473"></path>
+            <path d="M909.061224 532.897959H114.938776c-11.493878 0-20.897959-9.404082-20.89796-20.897959s9.404082-20.897959 20.89796-20.897959h794.122448c11.493878 0 20.897959 9.404082 20.89796 20.897959s-9.404082 20.897959-20.89796 20.897959z" p-id="4474"></path>
+            <path d="M227.787755 809.795918c-5.22449 0-10.971429-2.089796-15.15102-6.269387C136.359184 725.159184 94.040816 621.191837 94.040816 512s42.318367-213.159184 118.595919-291.526531c7.836735-8.359184 21.420408-8.359184 29.779592-0.522449 8.359184 7.836735 8.359184 21.420408 0.522449 29.779592C173.97551 320.261224 135.836735 413.257143 135.836735 512s38.138776 191.738776 106.579592 262.269388c7.836735 8.359184 7.836735 21.420408-0.522449 29.779592-3.657143 3.657143-8.881633 5.746939-14.106123 5.746938z" p-id="4475"></path>
+            <path d="M504.163265 929.959184c-0.522449 0-0.522449 0 0 0-110.759184-2.089796-214.204082-47.020408-291.52653-126.432653-7.836735-8.359184-7.836735-20.897959 0-29.257143 39.183673-40.228571 84.636735-71.57551 135.836734-92.995919 5.22449-2.089796 10.971429-2.089796 16.195919 0s9.404082 6.269388 11.493877 11.493878c29.779592 76.8 78.889796 146.285714 141.583674 200.097959 6.791837 5.746939 8.881633 15.15102 5.746939 23.510204-3.134694 7.836735-10.971429 13.583673-19.330613 13.583674z m-246.595918-141.061225c53.289796 49.110204 118.073469 80.979592 188.604082 93.518368-42.318367-44.930612-76.277551-97.17551-100.832653-153.6-31.869388 15.673469-61.64898 35.526531-87.771429 60.081632zM356.310204 344.293878c-2.612245 0-5.746939-0.522449-8.359184-1.567347-51.2-21.942857-96.653061-53.289796-135.836734-92.995919-7.836735-8.359184-7.836735-20.897959 0-29.257143C289.959184 141.061224 392.881633 96.653061 503.640816 94.040816c8.881633-0.522449 16.718367 5.22449 19.853062 13.583674s0.522449 17.763265-5.746939 23.510204C454.530612 184.946939 405.420408 253.910204 375.640816 331.232653c-2.089796 5.22449-6.269388 9.404082-11.493877 11.493878-2.089796 1.044898-5.22449 1.567347-7.836735 1.567347zM257.567347 235.102041c26.644898 24.555102 55.902041 44.408163 87.771429 60.604081 24.555102-56.42449 59.036735-108.669388 100.832653-153.6-70.530612 12.016327-135.314286 43.885714-188.604082 92.995919zM796.212245 809.795918c-5.22449 0-10.44898-2.089796-14.628572-5.746938-8.359184-7.836735-8.359184-21.420408-0.522449-29.779592C850.02449 703.738776 888.163265 610.742857 888.163265 512s-38.138776-191.738776-106.579592-262.269388c-7.836735-8.359184-7.836735-21.420408 0.522449-29.779592 8.359184-7.836735 21.420408-7.836735 29.779592 0.522449C887.640816 298.840816 929.959184 402.808163 929.959184 512s-42.318367 213.159184-118.595919 291.526531c-4.179592 4.179592-9.404082 6.269388-15.15102 6.269387z" p-id="4476"></path>
+            <path d="M514.612245 929.959184c-8.881633 0-16.718367-5.22449-19.330612-13.583674-3.134694-8.359184-0.522449-17.240816 5.746938-22.987755 63.738776-54.334694 112.84898-124.342857 142.628572-202.187755 2.089796-5.22449 6.269388-9.404082 11.493877-11.493878 5.22449-2.089796 10.971429-2.089796 16.195919 0 52.767347 21.942857 100.310204 53.812245 140.538775 95.085715 7.836735 8.359184 7.836735 20.897959 0 29.257143-78.889796 80.457143-184.42449 124.865306-297.273469 125.910204z m159.869388-203.755102c-25.077551 57.991837-59.559184 111.281633-102.922449 157.257142 72.620408-11.493878 140.016327-43.885714 194.873469-94.563265-27.689796-25.6-58.514286-46.497959-91.95102-62.693877zM662.987755 346.383673c-2.612245 0-5.746939-0.522449-8.359184-1.567346-5.22449-2.089796-9.404082-6.269388-11.493877-11.493878-29.779592-77.844898-78.889796-147.853061-142.628572-202.187755-6.791837-5.746939-8.881633-15.15102-5.746938-22.987755 3.134694-8.359184 10.971429-13.583673 19.330612-13.583674 112.84898 0.522449 217.861224 45.453061 296.75102 126.432653 7.836735 8.359184 7.836735 20.897959 0 29.257143-40.228571 41.273469-87.24898 73.142857-140.538775 95.085715-1.567347 0.522449-4.702041 1.044898-7.314286 1.044897z m-91.428571-205.844897c42.840816 45.97551 77.844898 99.265306 102.922449 157.257142 33.436735-16.195918 64.783673-37.093878 91.95102-62.693877-54.857143-50.677551-122.253061-83.069388-194.873469-94.563265z" p-id="4477"></path>
+            <path d="M356.310204 721.502041c-2.612245 0-5.746939-0.522449-8.359184-1.567347-5.22449-2.089796-9.404082-6.269388-11.493877-11.493878-24.032653-62.693878-36.571429-128.522449-36.571429-195.918367s12.016327-133.22449 36.571429-195.918367c2.089796-5.22449 6.269388-9.404082 11.493877-11.493878s10.971429-2.089796 16.195919 0c47.020408 19.330612 96.653061 29.257143 147.853061 29.257143 49.632653 0 97.697959-9.404082 143.15102-28.212245 5.22449-2.089796 10.971429-2.089796 16.195919 0s9.404082 6.269388 11.493877 11.493878c23.510204 62.171429 35.526531 127.477551 35.526531 193.828571 0 66.873469-12.016327 132.179592-35.526531 193.828571-2.089796 5.22449-6.269388 9.404082-11.493877 11.493878-5.22449 2.089796-10.971429 2.089796-16.195919 0-45.453061-18.808163-93.518367-28.212245-143.15102-28.212245-51.2 0-100.832653 9.926531-147.330612 29.779592-2.612245 2.612245-5.746939 3.134694-8.359184 3.134694z m12.538776-370.416327c-17.763265 51.722449-26.644898 106.057143-26.644898 160.914286s8.881633 109.191837 26.644898 160.914286c45.97551-16.718367 94.040816-25.077551 143.15102-25.077551 47.542857 0 94.040816 7.836735 138.44898 23.510204 17.240816-51.2 26.122449-105.012245 26.122449-159.346939 0-54.857143-8.881633-108.146939-26.122449-159.346939-44.408163 15.673469-90.906122 23.510204-138.44898 23.510204-49.632653 0-97.697959-8.359184-143.15102-25.077551z" p-id="4478"></path>
+        </svg>
+        {{ $currentLanguage }}
+    </a>
+    <ul class="language-dropdown">
+        @foreach ($languages as $locale => $language)
+        <li>
+            <a href="{{ route(Route::currentRouteName(), array_merge(request()->route()->parameters(), ['lang' => $locale])) }}"
+                class="language-dropdown-item {{ $locale === $currentLocale ? 'active' : '' }}">
+                {{ $language }}
+            </a>
+        </li>
+        @endforeach
+    </ul>
+</div>
+
+<style>
+    .language-switcher {
+        position: relative;
+        display: inline-block;
+    }
+
+    .language-link {
+        display: flex;
+        align-items: center;
+        gap: 0.4rem;
+        color: white;
+        text-decoration: none;
+        font-size: 0.95rem;
+        font-weight: 500;
+        transition: opacity 0.2s;
+        white-space: nowrap;
+        padding: 0.25rem 0;
+    }
+
+    .language-link:hover {
+        opacity: 0.8;
+        color: white;
+    }
+
+    .language-icon {
+        width: 18px;
+        height: 18px;
+        fill: currentColor;
+    }
+
+    .language-dropdown {
+        position: absolute;
+        top: 100%;
+        right: 0;
+        /* 背景颜色和透明度 - 调整 rgba 的最后一个值(0.85)来改变透明度,范围 0-1 */
+        background: rgba(0, 0, 0, 0.85);
+        /* 毛玻璃效果 - 调整数值(10px)来改变模糊程度,数值越大越模糊 */
+        backdrop-filter: blur(10px);
+        /* Safari 兼容性 */
+        -webkit-backdrop-filter: blur(10px);
+        /* 边框(可选) - 添加微妙的边框增强层次感 */
+        border: 1px solid rgba(255, 255, 255, 0.1);
+        border-radius: 0.375rem;
+        /* 阴影效果 */
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+        list-style: none;
+        margin: 0.5rem 0 0 0;
+        padding: 0.5rem 0;
+        min-width: 150px;
+        opacity: 0;
+        visibility: hidden;
+        transform: translateY(-10px);
+        transition: all 0.2s ease;
+        z-index: 1000;
+    }
+
+    .language-switcher:hover .language-dropdown {
+        opacity: 1;
+        visibility: visible;
+        transform: translateY(0);
+    }
+
+    .language-dropdown li {
+        margin: 0;
+        padding: 0;
+    }
+
+    .language-dropdown-item {
+        display: block;
+        padding: 0.5rem 1rem;
+        /* 下拉菜单文字颜色 */
+        color: #fff;
+        text-decoration: none;
+        font-size: 0.9rem;
+        transition: background 0.2s;
+        white-space: nowrap;
+    }
+
+    .language-dropdown-item:hover {
+        /* 悬停背景 - 调整 rgba 的最后一个值(0.2)来改变悬停时的高亮程度 */
+        background: rgba(255, 255, 255, 0.2);
+        color: #fff;
+    }
+
+    .language-dropdown-item.active {
+        /* 当前选中项的背景 - 调整 rgba 的最后一个值(0.3)来改变高亮程度 */
+        background: rgba(255, 255, 255, 0.3);
+        font-weight: 600;
+        color: #fff;
+    }
+
+    /* 移动端样式 */
+    @media (max-width: 768px) {
+        .language-switcher {
+            width: 100%;
+        }
+
+        .language-link {
+            justify-content: flex-start;
+            padding: 0;
+        }
+
+        .language-dropdown {
+            position: static;
+            opacity: 1;
+            visibility: visible;
+            transform: none;
+            box-shadow: none;
+            /* 移动端菜单背景 - 完全透明,融入侧边栏 */
+            background: transparent;
+            backdrop-filter: none;
+            -webkit-backdrop-filter: none;
+            border: none;
+            margin-top: 0.5rem;
+            padding: 0;
+        }
+
+        .language-dropdown-item {
+            color: white;
+            padding: 0.75rem 1rem;
+            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+        }
+
+        .language-dropdown-item:hover {
+            background: rgba(255, 255, 255, 0.1);
+            color: white;
+        }
+
+        .language-dropdown-item.active {
+            background: rgba(255, 255, 255, 0.15);
+            color: white;
+        }
+    }
+</style>

+ 24 - 0
api-v13/resources/views/components/library/footer.blade.php

@@ -0,0 +1,24 @@
+{{-- resources/views/components/library/footer.blade.php
+     Library 栏目全站 footer。
+     当前为最简占位版本,后续按需扩展。
+--}}
+<footer class="footer footer-transparent d-print-none">
+    <div class="container-xl">
+        <div class="row text-center align-items-center">
+            <div class="col-12 col-lg-auto mt-3 mt-lg-0">
+                <ul class="list-inline list-inline-dots mb-0">
+                    <li class="list-inline-item">
+                        <a href="{{ route('library.home') }}" class="link-secondary">
+                            WikiPāli
+                        </a>
+                    </li>
+                    <li class="list-inline-item">
+                        <a href="{{ route('library.download') }}" class="link-secondary">
+                            下载
+                        </a>
+                    </li>
+                </ul>
+            </div>
+        </div>
+    </div>
+</footer>

+ 51 - 0
api-v13/resources/views/components/library/header.blade.php

@@ -0,0 +1,51 @@
+{{-- api-v12/resources/views/components/library/header.blade.php --}}
+<div class="anthology-breadcrumb-bar">
+    <div class="container-xl">
+        <div class="bc-inner">
+
+            <ol class="breadcrumb mb-0">
+                @yield('breadcrumb')
+            </ol>
+
+            <ul class="bc-nav">
+                <li><a href="{{ route('library.home') }}">首页</a></li>
+                <li><a href="{{ route('library.tipitaka.index') }}">三藏</a></li>
+                <li><a href="{{ route('library.wiki.home') }}">百科</a></li>
+                <li><a href="{{ route('library.anthology.index') }}">文集</a></li>
+                <li><a href="{{ route('library.download') }}">下载</a></li>
+                <li>
+                    <x-language-switcher />
+                </li>
+            </ul>
+
+            <button class="bc-hamburger" id="bcHamburger">
+                <i class="ti ti-menu-2"></i>
+            </button>
+
+        </div>
+    </div>
+</div>
+
+
+<div class="bc-mobile-overlay" id="bcOverlay"></div>
+
+<div class="bc-mobile-drawer" id="bcDrawer">
+    <div class="bc-mobile-drawer-header">
+        <span>导航</span>
+
+        <button class="bc-mobile-drawer-close" id="bcDrawerClose">
+            <i class="ti ti-x"></i>
+        </button>
+    </div>
+
+    <ul class="bc-mobile-nav">
+        <li><a href="{{ route('library.home') }}">首页</a></li>
+        <li><a href="{{ route('library.tipitaka.index') }}">三藏</a></li>
+        <li><a href="{{ route('library.wiki.home') }}">百科</a></li>
+        <li><a href="{{ route('library.anthology.index') }}">文集</a></li>
+        <li><a href="{{ route('library.download') }}">下载</a></li>
+        <li style="padding:1rem 0.25rem;">
+            <x-language-switcher />
+        </li>
+    </ul>
+</div>

+ 62 - 0
api-v13/resources/views/components/library/navbar.blade.php

@@ -0,0 +1,62 @@
+{{-- resources/views/components/library/navbar.blade.php
+     替换原 components/library/header.blade.php。
+     文件名从 header 改为 navbar,与规范目录对齐。
+     原文件内容不变,仅做变量名规范化(--wp-* token 替代硬编码色值已在 CSS 层处理)。
+--}}
+<div class="anthology-breadcrumb-bar">
+    <div class="container-xl">
+        <div class="bc-inner">
+
+            <ol class="breadcrumb mb-0">
+                @yield('breadcrumb')
+            </ol>
+
+            <ul class="bc-nav">
+                <li><a href="{{ route('library.home') }}"
+                       class="{{ request()->routeIs('library.home') ? 'active' : '' }}">首页</a></li>
+                <li><a href="{{ route('library.tipitaka.index') }}"
+                       class="{{ request()->routeIs('library.tipitaka.*') ? 'active' : '' }}">三藏</a></li>
+                <li><a href="{{ route('library.wiki.home') }}"
+                       class="{{ request()->routeIs('library.wiki.*') ? 'active' : '' }}">百科</a></li>
+                <li><a href="{{ route('library.anthology.index') }}"
+                       class="{{ request()->routeIs('library.anthology.*') ? 'active' : '' }}">文集</a></li>
+                <li><a href="{{ route('library.download') }}"
+                       class="{{ request()->routeIs('library.download') ? 'active' : '' }}">下载</a></li>
+                <li>
+                    <x-language-switcher />
+                </li>
+            </ul>
+
+            <button class="bc-hamburger" id="bcHamburger" aria-label="打开导航">
+                <i class="ti ti-menu-2"></i>
+            </button>
+
+        </div>
+    </div>
+</div>
+
+<div class="bc-mobile-overlay" id="bcOverlay"></div>
+
+<div class="bc-mobile-drawer" id="bcDrawer">
+    <div class="bc-mobile-drawer-header">
+        <span>导航</span>
+        <button class="bc-mobile-drawer-close" id="bcDrawerClose" aria-label="关闭">
+            <i class="ti ti-x"></i>
+        </button>
+    </div>
+    <ul class="bc-mobile-nav">
+        <li><a href="{{ route('library.home') }}"
+               class="{{ request()->routeIs('library.home') ? 'active' : '' }}">首页</a></li>
+        <li><a href="{{ route('library.tipitaka.index') }}"
+               class="{{ request()->routeIs('library.tipitaka.*') ? 'active' : '' }}">三藏</a></li>
+        <li><a href="{{ route('library.wiki.home') }}"
+               class="{{ request()->routeIs('library.wiki.*') ? 'active' : '' }}">百科</a></li>
+        <li><a href="{{ route('library.anthology.index') }}"
+               class="{{ request()->routeIs('library.anthology.*') ? 'active' : '' }}">文集</a></li>
+        <li><a href="{{ route('library.download') }}"
+               class="{{ request()->routeIs('library.download') ? 'active' : '' }}">下载</a></li>
+        <li style="padding: 1rem 0.25rem;">
+            <x-language-switcher />
+        </li>
+    </ul>
+</div>

+ 20 - 0
api-v13/resources/views/components/term-drawer.blade.php

@@ -0,0 +1,20 @@
+<div class="offcanvas offcanvas-bottom h-auto" tabindex="-1" id="termDrawer">
+
+    <div class="offcanvas-header">
+
+        <h5 class="offcanvas-title" id="termDrawerTitle">
+            术语解释
+        </h5>
+
+        <button
+            type="button"
+            class="btn-close"
+            data-bs-dismiss="offcanvas"></button>
+
+    </div>
+
+    <div class="offcanvas-body" id="termDrawerBody">
+        Loading...
+    </div>
+
+</div>

+ 40 - 0
api-v13/resources/views/components/ui/author-avatar.blade.php

@@ -0,0 +1,40 @@
+{{-- resources/views/components/ui/author-avatar.blade.php
+     作者头像组件。支持图片头像和文字缩写头像两种形式。
+     Props:
+       $avatar   — 头像图片 URL(为空时显示文字头像)
+       $color    — 文字头像背景色
+       $initials — 文字头像缩写
+       $name     — 作者名字(显示在头像右侧)
+       $size     — sm | md | lg
+       $sub      — 副文字(如文章数量),可选
+--}}
+@props([
+    'avatar'   => null,
+    'color'    => '#888888',
+    'initials' => '?',
+    'name'     => '',
+    'size'     => 'md',
+    'sub'      => null,
+])
+
+<div class="author-avatar author-avatar--{{ $size }}">
+    @if($avatar)
+        <img src="{{ $avatar }}"
+             alt="{{ $name }}"
+             class="author-avatar__img">
+    @else
+        <div class="author-avatar__initials"
+             style="background: {{ $color }}">
+            {{ $initials }}
+        </div>
+    @endif
+
+    @if($name)
+    <div class="author-avatar__info">
+        <span class="author-avatar__name">{{ $name }}</span>
+        @if($sub)
+        <span class="author-avatar__sub">{{ $sub }}</span>
+        @endif
+    </div>
+    @endif
+</div>

+ 46 - 0
api-v13/resources/views/components/ui/book-cover.blade.php

@@ -0,0 +1,46 @@
+{{-- resources/views/components/ui/book-cover.blade.php
+     书籍封面组件。支持图片封面和渐变+文字封面两种形式。
+     Props:
+       $image    — 封面图片 URL(为空时显示渐变+文字)
+       $gradient — 渐变 CSS 字符串,image 为空时使用
+       $title    — 书名(image 为空时显示在封面上)
+       $subtitle — 副标题(可选)
+       $size     — sm | md | lg(控制尺寸)
+       $style3d  — true | false(是否显示 3D 书脊效果,默认 true)
+       $alt      — img alt 文字(默认使用 $title)
+--}}
+@props([
+    'image'    => null,
+    'gradient' => 'linear-gradient(135deg, #2d2010 0%, #1a1208 100%)',
+    'title'    => '',
+    'subtitle' => '',
+    'size'     => 'md',
+    'style3d'  => true,
+    'alt'      => null,
+])
+
+@php
+$sizes = [
+    'sm' => 'book-cover--sm',
+    'md' => 'book-cover--md',
+    'lg' => 'book-cover--lg',
+];
+$sizeClass = $sizes[$size] ?? $sizes['md'];
+@endphp
+
+<div class="book-cover {{ $sizeClass }} {{ $style3d ? 'book-cover--3d' : '' }}"
+     style="{{ $image ? '' : 'background: ' . $gradient }}">
+
+    @if($image)
+        <img src="{{ $image }}" alt="{{ $alt ?? $title }}" class="book-cover__img">
+    @else
+        <div class="book-cover__text">
+            <div class="book-cover__title">{{ $title }}</div>
+            @if($subtitle)
+                <div class="book-cover__divider"></div>
+                <div class="book-cover__subtitle">{{ $subtitle }}</div>
+            @endif
+        </div>
+    @endif
+
+</div>

+ 21 - 0
api-v13/resources/views/components/ui/book-grid.blade.php

@@ -0,0 +1,21 @@
+{{-- resources/views/components/ui/book-grid.blade.php
+     书籍网格组件。替代原 components/book-list.blade.php。
+     去除内联 <style> 和 CDN 引入,样式由 modules/_tipitaka.css 提供。
+     Props:
+       $books      — 书籍数组
+       $emptyText  — 空状态文字(默认"暂无图书")
+--}}
+@props([
+    'books'     => [],
+    'emptyText' => '暂无图书',
+])
+
+@if(!empty($books) && count($books) > 0)
+<div class="book-grid">
+    @foreach($books as $book)
+        <x-ui.card-book :book="$book" />
+    @endforeach
+</div>
+@else
+<x-ui.empty-state :title="$emptyText" />
+@endif

+ 69 - 0
api-v13/resources/views/components/ui/card-anthology.blade.php

@@ -0,0 +1,69 @@
+{{-- resources/views/components/ui/card-anthology.blade.php
+     文集卡片组件。横向布局:封面左 + 内容右。
+     用于 anthology/index 列表。
+     Props:
+       $item — 文集数据数组,包含以下字段:
+               id, title, subtitle, description, cover_image, cover_gradient,
+               author{name, avatar, color, initials},
+               chapters[], children_number, updated_at
+       $href — 卡片链接
+--}}
+@props([
+    'item',
+    'href',
+])
+
+<a href="{{ $href }}" class="anthology-card">
+
+    {{-- 封面 --}}
+    <x-ui.book-cover
+        :image="$item['cover_image'] ?? null"
+        :gradient="$item['cover_gradient'] ?? ''"
+        :title="$item['title']"
+        :subtitle="$item['subtitle'] ?? ''"
+        size="md"
+        :style3d="false"
+    />
+
+    {{-- 内容 --}}
+    <div class="anthology-card__body">
+        <div class="anthology-card__title">{{ $item['title'] }}</div>
+
+        @if(!empty($item['description']))
+        <div class="anthology-card__desc">{{ $item['description'] }}</div>
+        @endif
+
+        <div class="anthology-card__author">
+            <x-ui.author-avatar
+                :avatar="$item['author']['avatar'] ?? null"
+                :color="$item['author']['color'] ?? '#888'"
+                :initials="$item['author']['initials'] ?? '?'"
+                :name="$item['author']['name']"
+                size="sm"
+            />
+        </div>
+
+        @if(!empty($item['chapters']))
+        <div class="anthology-card__tags">
+            @foreach(array_slice($item['chapters'], 0, 4) as $ch)
+            <span class="anthology-tag">{{ mb_strimwidth($ch, 0, 14, '…') }}</span>
+            @endforeach
+            @if($item['children_number'] > 4)
+            <span class="anthology-tag anthology-tag--more">+{{ $item['children_number'] - 4 }} 章</span>
+            @endif
+        </div>
+        @endif
+
+        <div class="anthology-card__meta">
+            <span class="anthology-meta-item">
+                <i class="ti ti-calendar"></i>
+                {{ $item['updated_at'] }}
+            </span>
+            <span class="anthology-meta-item">
+                <i class="ti ti-file-text"></i>
+                {{ $item['children_number'] }} 章节
+            </span>
+        </div>
+    </div>
+
+</a>

+ 52 - 0
api-v13/resources/views/components/ui/card-book.blade.php

@@ -0,0 +1,52 @@
+{{-- resources/views/components/ui/card-book.blade.php
+     书籍卡片组件。纵向布局:封面上 + 信息下。
+     用于 tipitaka 列表页。
+     Props:
+       $book — 书籍数据数组,包含:
+               id, title, author, cover(可选),
+               cover_gradient(可选), publisher(object, 可选),
+               language(可选), type(可选)
+--}}
+@props(['book'])
+
+<div class="card-book">
+    <a href="{{ route('library.tipitaka.show', $book['id']) }}" class="card-book__link">
+
+        {{-- 封面 --}}
+        <x-ui.book-cover
+            :image="$book['cover'] ?? null"
+            :gradient="$book['cover_gradient'] ?? 'linear-gradient(135deg, #2d2010 0%, #1a1208 100%)'"
+            :title="$book['title'] ?? ''"
+            :subtitle="'义注'"
+            size="md"
+            :style3d="false" />
+
+        {{-- 书籍信息 --}}
+        <div class="card-book__info">
+            <div class="card-book__title">{{ $book['title'] ?? '未知书籍' }}</div>
+
+            @if(!empty($book['author']))
+            <div class="card-book__author">{{ $book['author'] }}</div>
+            @endif
+
+            @if(isset($book['publisher']))
+            <div class="card-book__publisher">
+                <a href="{{ route('blog.index', ['user' => $book['publisher']->username]) }}"
+                    class="card-book__publisher-link">
+                    {{ $book['publisher']->nickname }}
+                </a>
+            </div>
+            @endif
+
+            <div class="card-book__badges">
+                @if(!empty($book['language']))
+                <span class="card-book__badge">{{ $book['language'] }}</span>
+                @endif
+                @if(!empty($book['type']))
+                <span class="card-book__badge">{{ $book['type'] }}</span>
+                @endif
+            </div>
+        </div>
+
+    </a>
+</div>

+ 35 - 0
api-v13/resources/views/components/ui/empty-state.blade.php

@@ -0,0 +1,35 @@
+{{-- resources/views/components/ui/empty-state.blade.php
+     通用空状态组件。
+     Props:
+       $title — 标题文字
+       $desc  — 描述文字(支持 HTML,可选)
+       $icon  — 自定义图标 slot(可选,默认搜索图标)
+--}}
+@props([
+    'title' => '未找到相关内容',
+    'desc'  => '',
+])
+
+<div class="wiki-empty-state">
+    <div class="wiki-empty-icon">
+        {{ $icon ?? '' }}
+        @unless($icon ?? false)
+        <svg width="32" height="32" viewBox="0 0 24 24" fill="none"
+             stroke="currentColor" stroke-width="1.5">
+            <circle cx="11" cy="11" r="8" />
+            <path d="M21 21l-4.35-4.35" stroke-linecap="round" />
+            <path d="M8 11h6M11 8v6" stroke-linecap="round" />
+        </svg>
+        @endunless
+    </div>
+
+    <div class="wiki-empty-title">{{ $title }}</div>
+
+    @if ($desc)
+    <div class="wiki-empty-desc">{!! $desc !!}</div>
+    @endif
+
+    @isset($slot)
+        {{ $slot }}
+    @endisset
+</div>

+ 54 - 0
api-v13/resources/views/components/ui/search-input.blade.php

@@ -0,0 +1,54 @@
+{{-- resources/views/components/ui/search-input.blade.php --}}
+@props([
+'action',
+'value' => '',
+'placeholder' => '搜索…',
+'buttonText' => '搜索',
+'size' => 'md',
+'hiddenFields' => [],
+'autofocus' => false,
+])
+
+<form action="{{ $action }}" method="GET" class="search-input-form position-relative">
+    <div class="input-group {{ $size === 'lg' ? 'input-group-lg' : '' }}">
+
+        {{-- 🔍 图标输入框 --}}
+        <div class="input-icon flex-grow-1">
+            <span class="input-icon-addon">
+                {{-- Tabler 搜索图标 --}}
+                <svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24"
+                    viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"
+                    fill="none" stroke-linecap="round" stroke-linejoin="round">
+                    <path stroke="none" d="M0 0h24v24H0z" fill="none" />
+                    <circle cx="10" cy="10" r="7" />
+                    <line x1="21" y1="21" x2="15" y2="15" />
+                </svg>
+            </span>
+
+            <input
+                type="text"
+                name="q"
+                class="form-control search-input"
+                value="{{ $value }}"
+                placeholder="{{ $placeholder }}"
+                autocomplete="off"
+                data-suggest-url="/api/v3/search-suggest"
+                {{ $autofocus ? 'autofocus' : '' }} />
+        </div>
+
+        {{-- 隐藏字段 --}}
+        @foreach ($hiddenFields as $name => $val)
+        @if ($val)
+        <input type="hidden" name="{{ $name }}" value="{{ $val }}">
+        @endif
+        @endforeach
+
+        {{-- 按钮 --}}
+        <button class="btn btn-primary" type="submit">
+            {{ $buttonText }}
+        </button>
+    </div>
+
+    {{-- 自动补全下拉 --}}
+    <div class="search-suggest-dropdown dropdown-menu w-100"></div>
+</form>

+ 95 - 0
api-v13/resources/views/components/wiki/entry-actions.blade.php

@@ -0,0 +1,95 @@
+{{-- resources/views/components/wiki/entry-actions.blade.php --}}
+@props(['editUrl', 'title' => ''])
+
+<div class="wiki-entry-actions">
+
+    {{-- 分享到微信 --}}
+    <button class="wiki-action-btn"
+        id="wikiShareWechat"
+        title="分享到微信"
+        data-title="{{ $title }}">
+        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"
+            fill="none" stroke="currentColor" stroke-width="1.5"
+            stroke-linecap="round" stroke-linejoin="round">
+            <path d="M9.5 9a3.5 3.5 0 0 1 5 0" />
+            <path d="M5.5 11.5C4 10 3 8.1 3 6c0-3.3 3.1-6 7-6s7 2.7 7 6c0 .6-.1 1.2-.3 1.7" />
+            <path d="M12 20c-4.4 0-8-2.9-8-6.5S7.6 7 12 7s8 2.9 8 6.5c0 1.4-.5 2.7-1.4 3.8l.4 2.7-2.6-1.1A9 9 0 0 1 12 20z" />
+        </svg>
+    </button>
+
+    {{-- 编辑 --}}
+    <a class="wiki-action-btn"
+        href="{{ $editUrl }}"
+        target="_blank"
+        rel="noopener"
+        title="编辑条目">
+        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"
+            fill="none" stroke="currentColor" stroke-width="1.5"
+            stroke-linecap="round" stroke-linejoin="round">
+            <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
+            <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
+        </svg>
+    </a>
+
+</div>
+
+{{-- 微信二维码弹窗 --}}
+<div class="wiki-wechat-modal" id="wikiWechatModal" style="display:none;">
+    <div class="wiki-wechat-modal-backdrop" id="wikiWechatBackdrop"></div>
+    <div class="wiki-wechat-modal-box">
+        <div class="wiki-wechat-modal-title">分享到微信</div>
+        <div class="wiki-wechat-modal-desc">使用微信扫描二维码</div>
+        <div id="wikiWechatQr" class="wiki-wechat-qr"></div>
+        <button class="wiki-wechat-modal-close" id="wikiWechatClose">关闭</button>
+    </div>
+</div>
+
+@push('scripts')
+<script>
+    (function() {
+        const btn = document.getElementById('wikiShareWechat');
+        const modal = document.getElementById('wikiWechatModal');
+        const backdrop = document.getElementById('wikiWechatBackdrop');
+        const closeBtn = document.getElementById('wikiWechatClose');
+        const qrEl = document.getElementById('wikiWechatQr');
+
+        if (!btn || !modal || !qrEl) return;
+
+        function openWechatShare() {
+            const url = encodeURIComponent(window.location.href);
+
+            // 使用更稳定的二维码服务
+            qrEl.innerHTML = `
+            <img
+                src="https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${url}"
+                width="180"
+                height="180"
+                alt="QR Code"
+            >
+        `;
+
+            modal.style.display = 'flex';
+        }
+
+        function closeWechatShare() {
+            modal.style.display = 'none';
+        }
+
+        btn.addEventListener('click', openWechatShare);
+
+        if (closeBtn) {
+            closeBtn.addEventListener('click', closeWechatShare);
+        }
+
+        if (backdrop) {
+            backdrop.addEventListener('click', closeWechatShare);
+        }
+
+        document.addEventListener('keydown', function(e) {
+            if (e.key === 'Escape') {
+                closeWechatShare();
+            }
+        });
+    })();
+</script>
+@endpush

+ 11 - 0
api-v13/resources/views/components/wiki/entry-header.blade.php

@@ -0,0 +1,11 @@
+{{-- resources/views/components/wiki/entry-header.blade.php --}}
+@props(['entry'])
+
+<div class="wiki-entry-header">
+
+    <x-wiki.quality-badge :quality="$entry['quality']" />
+
+    <h1 class="wiki-entry-title">{{ $entry['meaning'] }}</h1>
+    <div>{{ $entry['word'] }}</div>
+
+</div>

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.