Browse Source

Merge branch 'development' of github.com:iapt-platform/mint into development

Jeremy Zheng 4 days ago
parent
commit
3046aafbd3
44 changed files with 886 additions and 486 deletions
  1. 2 2
      api-v13/app/Http/Controllers/DhammaTermController.php
  2. 18 11
      api-v13/app/Http/Controllers/EmailCertificationController.php
  3. 14 6
      api-v13/app/Http/Controllers/ForgotPasswordController.php
  4. 27 18
      api-v13/app/Http/Controllers/InviteController.php
  5. 80 0
      api-v13/app/Http/Controllers/ProgressController.php
  6. 2 2
      api-v13/app/Http/Controllers/ProgressImgController.php
  7. 1 1
      api-v13/resources/css/base/reset.css
  8. 1 1
      api-v13/resources/css/base/typography.css
  9. 17 15
      api-v13/resources/css/base/variables.css
  10. 2 2
      api-v13/resources/css/components/badge.css
  11. 64 32
      api-v13/resources/css/components/card-book.css
  12. 4 4
      api-v13/resources/css/components/card.css
  13. 4 2
      api-v13/resources/css/components/pagination.css
  14. 1 1
      api-v13/resources/css/components/search-input.css
  15. 2 2
      api-v13/resources/css/components/search-results.css
  16. 1 1
      api-v13/resources/css/layout/drawer.css
  17. 1 1
      api-v13/resources/css/layout/footer.css
  18. 16 12
      api-v13/resources/css/layout/grid.css
  19. 0 0
      api-v13/resources/css/layout/hero.css
  20. 1 1
      api-v13/resources/css/layout/navbar.css
  21. 1 1
      api-v13/resources/css/layout/toolbar.css
  22. 18 18
      api-v13/resources/css/library.css
  23. 0 284
      api-v13/resources/css/modules/_anthology.css
  24. 533 0
      api-v13/resources/css/modules/anthology.css
  25. 16 8
      api-v13/resources/css/modules/library-index.css
  26. 1 1
      api-v13/resources/css/modules/reader-content.css
  27. 1 1
      api-v13/resources/css/modules/reader.css
  28. 0 0
      api-v13/resources/css/modules/tipitaka.css
  29. 3 3
      api-v13/resources/css/modules/wiki.css
  30. 2 2
      api-v13/resources/css/reader.css
  31. 1 1
      api-v13/resources/js/modules/reader.js
  32. 1 1
      api-v13/resources/js/reader.js
  33. 4 4
      api-v13/resources/views/components/ui/book-grid.blade.php
  34. 1 1
      api-v13/resources/views/library/anthology/index.blade.php
  35. 10 14
      api-v13/resources/views/library/anthology/show.blade.php
  36. 0 28
      api-v13/resources/views/library/book/_toc.blade.php
  37. 28 0
      api-v13/resources/views/library/book/toc.blade.php
  38. 1 1
      api-v13/resources/views/library/index.blade.php
  39. 1 1
      api-v13/resources/views/library/tipitaka/category.blade.php
  40. 1 1
      api-v13/resources/views/library/tipitaka/show.blade.php
  41. 1 1
      api-v13/resources/views/library/wiki/home.blade.php
  42. 1 1
      api-v13/resources/views/library/wiki/layouts/app.blade.php
  43. 2 0
      api-v13/routes/api.php
  44. 1 0
      api-v13/vite.config.ts

+ 2 - 2
api-v13/app/Http/Controllers/DhammaTermController.php

@@ -150,7 +150,7 @@ class DhammaTermController extends Controller
                 break;
                 break;
             case 'hot-meaning':
             case 'hot-meaning':
                 $key = 'term/hot_meaning';
                 $key = 'term/hot_meaning';
-                $value = Cache::get($key, function () use ($request) {
+                $value = Cache::remember($key, config('mint.cache.expire'), function () use ($request) {
                     $hotMeaning = [];
                     $hotMeaning = [];
                     $words = DhammaTerm::select('word')
                     $words = DhammaTerm::select('word')
                         ->where('language', $request->input("language"))
                         ->where('language', $request->input("language"))
@@ -176,7 +176,7 @@ class DhammaTermController extends Controller
                     }
                     }
                     Cache::put($key, $hotMeaning, 3600);
                     Cache::put($key, $hotMeaning, 3600);
                     return $hotMeaning;
                     return $hotMeaning;
-                }, config('mint.cache.expire'));
+                });
                 return $this->ok(["rows" => $value, "count" => count($value)]);
                 return $this->ok(["rows" => $value, "count" => count($value)]);
                 break;
                 break;
             default:
             default:

+ 18 - 11
api-v13/app/Http/Controllers/EmailCertificationController.php

@@ -3,13 +3,15 @@
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
-use App\Models\Invite;
-use App\Http\Resources\InviteResource;
+use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Str;
 use Illuminate\Support\Str;
-use App\Mail\EmailCertif;
 use Illuminate\Support\Facades\Mail;
 use Illuminate\Support\Facades\Mail;
 use Illuminate\Support\Facades\Cache;
 use Illuminate\Support\Facades\Cache;
+
+use App\Models\Invite;
 use App\Models\UserInfo;
 use App\Models\UserInfo;
+use App\Http\Resources\InviteResource;
+use App\Mail\EmailCertif;
 
 
 class EmailCertificationController extends Controller
 class EmailCertificationController extends Controller
 {
 {
@@ -46,14 +48,19 @@ class EmailCertificationController extends Controller
         $invite->status = 'invited';
         $invite->status = 'invited';
         $invite->save();
         $invite->save();
 
 
-        Mail::to($request->input('email'))
-            ->send(new EmailCertif(
-                $invite->id,
-                $request->input('subject', 'sign up wikipali'),
-                $request->input('lang'),
-            ));
-        if (Mail::failures()) {
-            return $this->error('send email fail', '', 200);
+        try {
+            Mail::to($request->input('email'))
+                ->send(new EmailCertif(
+                    $invite->id,
+                    $request->input('subject', 'sign up wikipali'),
+                    $request->input('lang'),
+                ));
+        } catch (\Exception $e) {
+            Log::error('send email fail', [
+                'message' => $e->getMessage(),
+                'trace'   => $e->getTraceAsString(),
+            ]);
+            return $this->error('send email fail', $e->getMessage(), 200);
         }
         }
 
 
         return $this->ok(new InviteResource($invite));
         return $this->ok(new InviteResource($invite));

+ 14 - 6
api-v13/app/Http/Controllers/ForgotPasswordController.php

@@ -2,10 +2,13 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
-use App\Models\UserInfo;
+use Illuminate\Support\Facades\Mail;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
 use Illuminate\Support\Str;
 use Illuminate\Support\Str;
-use Mail;
+use Illuminate\Support\Facades\Log;
+
+
+use App\Models\UserInfo;
 use App\Mail\ForgotPassword;
 use App\Mail\ForgotPassword;
 
 
 class ForgotPasswordController extends Controller
 class ForgotPasswordController extends Controller
@@ -40,12 +43,17 @@ class ForgotPasswordController extends Controller
             return $this->error('fail on update reset_password_token', 500, 500);
             return $this->error('fail on update reset_password_token', 500, 500);
         }
         }
 
 
-        Mail::to($request->input('email'))
-            ->send(new ForgotPassword($resetToken, $request->input('lang'), $request->input('dashboard')));
-        if (Mail::failures()) {
+        try {
+            Mail::to($request->input('email'))
+                ->send(new ForgotPassword($resetToken, $request->input('lang'), $request->input('dashboard')));
+        } catch (\Exception $e) {
+            Log::error('send forgot password email fail', [
+                'message' => $e->getMessage(),
+                'trace'   => $e->getTraceAsString(),
+            ]);
             return $this->error('send email fail', [], 200);
             return $this->error('send email fail', [], 200);
         }
         }
-        return $this->ok('');
+        return $this->ok('successful');
     }
     }
 
 
     /**
     /**

+ 27 - 18
api-v13/app/Http/Controllers/InviteController.php

@@ -2,16 +2,20 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use Illuminate\Support\Facades\Mail;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Str;
+use Illuminate\Http\Request;
+
 use App\Models\Invite;
 use App\Models\Invite;
 use App\Models\UserInfo;
 use App\Models\UserInfo;
-use Illuminate\Http\Request;
 use App\Services\AuthService;
 use App\Services\AuthService;
 use App\Http\Api\UserApi;
 use App\Http\Api\UserApi;
 use App\Http\Api\StudioApi;
 use App\Http\Api\StudioApi;
 use App\Http\Resources\InviteResource;
 use App\Http\Resources\InviteResource;
-use Illuminate\Support\Str;
 use App\Mail\InviteMail;
 use App\Mail\InviteMail;
-use Illuminate\Support\Facades\Mail;
+
+
 
 
 class InviteController extends Controller
 class InviteController extends Controller
 {
 {
@@ -103,23 +107,28 @@ class InviteController extends Controller
         }
         }
 
 
         $uuid = Str::uuid();
         $uuid = Str::uuid();
-        Mail::to($request->input('email'))
-            ->send(new InviteMail(
-                $uuid,
-                $request->input('subject', 'sign up wikipali'),
-                $request->input('lang'),
-                $request->input('dashboard')
-            ));
-        if (Mail::failures()) {
+        try {
+            Mail::to($request->input('email'))
+                ->send(new InviteMail(
+                    $uuid,
+                    $request->input('subject', 'sign up wikipali'),
+                    $request->input('lang'),
+                    $request->input('dashboard')
+                ));
+        } catch (\Exception $e) {
+            Log::error('send invite email fail', [
+                'message' => $e->getMessage(),
+                'trace'   => $e->getTraceAsString(),
+            ]);
             return $this->error('send email fail', '', 200);
             return $this->error('send email fail', '', 200);
-        } else {
-            $invite = new Invite;
-            $invite->id = $uuid;
-            $invite->email = $request->input('email');
-            $invite->user_uid = $sender;
-            $invite->status = 'invited';
-            $invite->save();
         }
         }
+
+        $invite = new Invite;
+        $invite->id = $uuid;
+        $invite->email = $request->input('email');
+        $invite->user_uid = $sender;
+        $invite->status = 'invited';
+        $invite->save();
         return $this->ok(new InviteResource($invite));
         return $this->ok(new InviteResource($invite));
     }
     }
 
 

+ 80 - 0
api-v13/app/Http/Controllers/ProgressController.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use App\Models\ProgressChapter;
+
+class ProgressController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        //
+        switch ($request->input('view')) {
+            case 'channel':
+                $table = ProgressChapter::whereIn('channel_id', explode('_', $request->get('channels', '')));
+                break;
+            default:
+                return $this->error('invalid view', 400, 400);
+                break;
+        }
+
+        if ($request->has('lang')) {
+            $table = $table->where('lang', $request->get('lang'));
+        }
+        $count = $table->count();
+
+        $table = $table->orderBy(
+            $request->input('order', 'completed_at'),
+            $request->input('dir', 'desc')
+        );
+
+        $table = $table->skip($request->input("offset", 0))
+            ->take($request->input('limit', 10));
+
+        $result = $table->get();
+
+        return $this->ok(
+            [
+                "rows" => $result->toArray(),
+                "total" => $count,
+            ]
+        );
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     */
+    public function show(string $id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     */
+    public function update(Request $request, string $id)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     */
+    public function destroy(string $id)
+    {
+        //
+    }
+}

+ 2 - 2
api-v13/app/Http/Controllers/ProgressImgController.php

@@ -40,7 +40,7 @@ class ProgressImgController extends Controller
         //
         //
         return response()->stream(function () use ($id) {
         return response()->stream(function () use ($id) {
             $key = str_replace('-', '/', $id);
             $key = str_replace('-', '/', $id);
-            $svg = Cache::get('svg/' . $key, function () use ($key) {
+            $svg = Cache::remember('svg/' . $key, config('mint.cache.expire'), function () use ($key) {
                 $viewHeight = 60;
                 $viewHeight = 60;
                 $svg = "<svg xmlns='http://www.w3.org/2000/svg'  fill='currentColor' viewBox='0 0 300 60'>";
                 $svg = "<svg xmlns='http://www.w3.org/2000/svg'  fill='currentColor' viewBox='0 0 300 60'>";
                 $data = Cache::get($key);
                 $data = Cache::get($key);
@@ -55,7 +55,7 @@ class ProgressImgController extends Controller
                     $svg .= '<polyline points="0,0 1,0" /></svg>';
                     $svg .= '<polyline points="0,0 1,0" /></svg>';
                 }
                 }
                 return $svg;
                 return $svg;
-            }, config('mint.cache.expire'));
+            });
             echo $svg;
             echo $svg;
         }, 200, ['Content-Type' => 'image/svg+xml']);
         }, 200, ['Content-Type' => 'image/svg+xml']);
         /*
         /*

+ 1 - 1
api-v13/resources/css/base/_reset.css → api-v13/resources/css/base/reset.css

@@ -1,4 +1,4 @@
-/* resources/css/base/_reset.css
+/* resources/css/base/reset.css
    最小化 reset,补充 Tabler 未覆盖的部分。
    最小化 reset,补充 Tabler 未覆盖的部分。
    不覆盖 Tabler 已处理好的规则。
    不覆盖 Tabler 已处理好的规则。
 */
 */

+ 1 - 1
api-v13/resources/css/base/_typography.css → api-v13/resources/css/base/typography.css

@@ -1,4 +1,4 @@
-/* resources/css/base/_typography.css
+/* resources/css/base/typography.css
    全站基础排版。无副作用,不覆盖组件样式。
    全站基础排版。无副作用,不覆盖组件样式。
    Noto Serif 字体已在 library.css 入口 @import,此处只声明使用规则。
    Noto Serif 字体已在 library.css 入口 @import,此处只声明使用规则。
 */
 */

+ 17 - 15
api-v13/resources/css/base/_variables.css → api-v13/resources/css/base/variables.css

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

+ 2 - 2
api-v13/resources/css/components/_badge.css → api-v13/resources/css/components/badge.css

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

+ 64 - 32
api-v13/resources/css/components/_card-book.css → api-v13/resources/css/components/card-book.css

@@ -1,6 +1,6 @@
-/* resources/css/components/_card-book.css
+/* resources/css/components/card-book.css
    书籍封面组件(.book-cover)+ 纵向书籍卡片(.card-book)。
    书籍封面组件(.book-cover)+ 纵向书籍卡片(.card-book)。
-   .book-cover 从 _anthology.css 提取,供 anthology / tipitaka 共用。
+   .book-cover 从 anthology.css 提取,供 anthology / tipitaka 共用。
 */
 */
 
 
 /* ══════════════════════════════════════════
 /* ══════════════════════════════════════════
@@ -17,24 +17,43 @@
 }
 }
 
 
 /* 尺寸 */
 /* 尺寸 */
-.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; }
+.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 书脊 */
 /* 3D 书脊 */
 .book-cover--3d {
 .book-cover--3d {
     box-shadow:
     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);
+        -4px 0 0 rgba(0, 0, 0, 0.3),
+        -6px 4px 14px rgba(0, 0, 0, 0.4),
+        4px 4px 18px rgba(0, 0, 0, 0.3);
 }
 }
 
 
 .book-cover--3d::before {
 .book-cover--3d::before {
     content: '';
     content: '';
     position: absolute;
     position: absolute;
-    left: 0; top: 0; bottom: 0;
+    left: 0;
+    top: 0;
+    bottom: 0;
     width: 13px;
     width: 13px;
-    background: linear-gradient(to right, rgba(0,0,0,.4), rgba(0,0,0,.1));
+    background: linear-gradient(
+        to right,
+        rgba(0, 0, 0, 0.4),
+        rgba(0, 0, 0, 0.1)
+    );
     border-radius: 3px 0 0 3px;
     border-radius: 3px 0 0 3px;
     z-index: 2;
     z-index: 2;
 }
 }
@@ -45,8 +64,11 @@
     position: absolute;
     position: absolute;
     inset: 0;
     inset: 0;
     background: repeating-linear-gradient(
     background: repeating-linear-gradient(
-        45deg, transparent, transparent 8px,
-        rgba(255,255,255,.015) 8px, rgba(255,255,255,.015) 9px
+        45deg,
+        transparent,
+        transparent 8px,
+        rgba(255, 255, 255, 0.015) 8px,
+        rgba(255, 255, 255, 0.015) 9px
     );
     );
     z-index: 1;
     z-index: 1;
 }
 }
@@ -65,7 +87,7 @@
     position: relative;
     position: relative;
     z-index: 3;
     z-index: 3;
     text-align: center;
     text-align: center;
-    padding: 0 .5rem;
+    padding: 0 0.5rem;
 }
 }
 
 
 .book-cover__title {
 .book-cover__title {
@@ -74,13 +96,13 @@
     font-weight: 600;
     font-weight: 600;
     color: #fff;
     color: #fff;
     line-height: 1.6;
     line-height: 1.6;
-    letter-spacing: .12em;
+    letter-spacing: 0.12em;
     word-break: break-all;
     word-break: break-all;
 }
 }
 
 
 .book-cover--sm .book-cover__title {
 .book-cover--sm .book-cover__title {
-    font-size: .6rem;
-    letter-spacing: .04em;
+    font-size: 0.6rem;
+    letter-spacing: 0.04em;
     line-height: 1.3;
     line-height: 1.3;
 }
 }
 
 
@@ -88,13 +110,13 @@
     width: 28px;
     width: 28px;
     height: 1px;
     height: 1px;
     background: var(--wp-brand);
     background: var(--wp-brand);
-    margin: .5rem auto;
+    margin: 0.5rem auto;
 }
 }
 
 
 .book-cover__subtitle {
 .book-cover__subtitle {
-    font-size: .65rem;
-    color: rgba(255,255,255,.45);
-    letter-spacing: .04em;
+    font-size: 0.65rem;
+    color: rgba(255, 255, 255, 0.45);
+    letter-spacing: 0.04em;
 }
 }
 
 
 /* ══════════════════════════════════════════
 /* ══════════════════════════════════════════
@@ -103,12 +125,14 @@
    ══════════════════════════════════════════ */
    ══════════════════════════════════════════ */
 
 
 .card-book {
 .card-book {
-    transition: transform 0.2s, box-shadow 0.2s;
+    transition:
+        transform 0.2s,
+        box-shadow 0.2s;
 }
 }
 
 
 .card-book:hover {
 .card-book:hover {
     transform: translateY(-2px);
     transform: translateY(-2px);
-    box-shadow: 0 8px 24px rgba(0,0,0,.1);
+    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
 }
 }
 
 
 .card-book__link {
 .card-book__link {
@@ -131,16 +155,20 @@
 }
 }
 
 
 @media (max-width: 768px) {
 @media (max-width: 768px) {
-    .card-book .book-cover--md { height: 150px; }
+    .card-book .book-cover--md {
+        height: 150px;
+    }
 }
 }
 
 
-.card-book__info { padding: .75rem 0 0; }
+.card-book__info {
+    padding: 0.75rem 0 0;
+}
 
 
 .card-book__title {
 .card-book__title {
     font-size: 0.9375rem;
     font-size: 0.9375rem;
     font-weight: 600;
     font-weight: 600;
     color: var(--tblr-body-color);
     color: var(--tblr-body-color);
-    margin-bottom: .25rem;
+    margin-bottom: 0.25rem;
     line-height: 1.4;
     line-height: 1.4;
     display: -webkit-box;
     display: -webkit-box;
     -webkit-line-clamp: 2;
     -webkit-line-clamp: 2;
@@ -148,13 +176,15 @@
     overflow: hidden;
     overflow: hidden;
 }
 }
 
 
-.card-book:hover .card-book__title { color: var(--tblr-primary); }
+.card-book:hover .card-book__title {
+    color: var(--tblr-primary);
+}
 
 
 .card-book__author,
 .card-book__author,
 .card-book__publisher {
 .card-book__publisher {
-    font-size: .8125rem;
+    font-size: 0.8125rem;
     color: var(--tblr-secondary);
     color: var(--tblr-secondary);
-    margin-bottom: .25rem;
+    margin-bottom: 0.25rem;
 }
 }
 
 
 .card-book__publisher-link {
 .card-book__publisher-link {
@@ -162,13 +192,15 @@
     text-decoration: none;
     text-decoration: none;
 }
 }
 
 
-.card-book__publisher-link:hover { text-decoration: underline; }
+.card-book__publisher-link:hover {
+    text-decoration: underline;
+}
 
 
 .card-book__badges {
 .card-book__badges {
     display: flex;
     display: flex;
     flex-wrap: wrap;
     flex-wrap: wrap;
     gap: 4px;
     gap: 4px;
-    margin-top: .375rem;
+    margin-top: 0.375rem;
 }
 }
 
 
 .card-book__badge {
 .card-book__badge {
@@ -177,7 +209,7 @@
     background: var(--tblr-bg-surface-secondary);
     background: var(--tblr-bg-surface-secondary);
     border: 1px solid var(--tblr-border-color);
     border: 1px solid var(--tblr-border-color);
     border-radius: 20px;
     border-radius: 20px;
-    font-size: .6875rem;
+    font-size: 0.6875rem;
     color: var(--tblr-secondary);
     color: var(--tblr-secondary);
 }
 }
 
 
@@ -194,6 +226,6 @@
 @media (max-width: 575px) {
 @media (max-width: 575px) {
     .book-grid {
     .book-grid {
         grid-template-columns: repeat(2, 1fr);
         grid-template-columns: repeat(2, 1fr);
-        gap: .875rem;
+        gap: 0.875rem;
     }
     }
 }
 }

+ 4 - 4
api-v13/resources/css/components/_card.css → api-v13/resources/css/components/card.css

@@ -1,7 +1,7 @@
-/* resources/css/components/_card.css
+/* resources/css/components/card.css
    全站通用卡片、侧边栏、列表组件。
    全站通用卡片、侧边栏、列表组件。
-   从 _wiki.css 提取,供 wiki / tipitaka / anthology / search 共用。
-   wiki 专属样式(质量徽章、条目头部、term popover 等)保留在 modules/_wiki.css。
+   从 wiki.css 提取,供 wiki / tipitaka / anthology / search 共用。
+   wiki 专属样式(质量徽章、条目头部、term popover 等)保留在 modules/wiki.css。
 */
 */
 
 
 /* ══════════════════════════════════════════
 /* ══════════════════════════════════════════
@@ -247,7 +247,7 @@
 }
 }
 
 
 /* ══════════════════════════════════════════
 /* ══════════════════════════════════════════
-   九、作者头像组件(从 _anthology.css 提取)
+   九、作者头像组件(从 anthology.css 提取)
    ══════════════════════════════════════════ */
    ══════════════════════════════════════════ */
 
 
 .author-avatar {
 .author-avatar {

+ 4 - 2
api-v13/resources/css/components/_pagination.css → api-v13/resources/css/components/pagination.css

@@ -1,4 +1,4 @@
-/* resources/css/components/_pagination.css
+/* resources/css/components/pagination.css
    全站通用分页组件。
    全站通用分页组件。
    来源:wiki.css / wiki-search.css 的 .wiki-pagination 段落(两处相同,已去重)。
    来源:wiki.css / wiki-search.css 的 .wiki-pagination 段落(两处相同,已去重)。
 */
 */
@@ -25,7 +25,9 @@
     color: var(--tblr-body-color);
     color: var(--tblr-body-color);
     text-decoration: none;
     text-decoration: none;
     background: var(--tblr-bg-surface);
     background: var(--tblr-bg-surface);
-    transition: background 0.12s, border-color 0.12s;
+    transition:
+        background 0.12s,
+        border-color 0.12s;
     user-select: none;
     user-select: none;
 }
 }
 
 

+ 1 - 1
api-v13/resources/css/components/_search-input.css → api-v13/resources/css/components/search-input.css

@@ -1,4 +1,4 @@
-/* resources/css/components/_search-input.css
+/* resources/css/components/search-input.css
    搜索输入框 + 提示下拉。
    搜索输入框 + 提示下拉。
    主要样式由 Tabler input-group 提供,此处补充 WikiPali 专属覆盖。
    主要样式由 Tabler input-group 提供,此处补充 WikiPali 专属覆盖。
 */
 */

+ 2 - 2
api-v13/resources/css/components/_search-results.css → api-v13/resources/css/components/search-results.css

@@ -1,4 +1,4 @@
-/* resources/css/components/_search-results.css
+/* resources/css/components/search-results.css
    统一搜索结果页组件样式。
    统一搜索结果页组件样式。
    来源:wiki-search.css(与 wiki.css 末尾重复内容已去重,以 wiki 栏目为准)。
    来源:wiki-search.css(与 wiki.css 末尾重复内容已去重,以 wiki 栏目为准)。
    适用于 /library/search?type= 所有类型。
    适用于 /library/search?type= 所有类型。
@@ -68,7 +68,7 @@
 }
 }
 
 
 .wiki-search-card-word {
 .wiki-search-card-word {
-    font-family: "Noto Serif", Georgia, serif;
+    font-family: 'Noto Serif', Georgia, serif;
     font-size: 0.875rem;
     font-size: 0.875rem;
     font-style: italic;
     font-style: italic;
     color: var(--tblr-secondary);
     color: var(--tblr-secondary);

+ 1 - 1
api-v13/resources/css/layout/_drawer.css → api-v13/resources/css/layout/drawer.css

@@ -1,4 +1,4 @@
-/* resources/css/layout/_drawer.css
+/* resources/css/layout/drawer.css
    右侧 mobile 导航抽屉(nav drawer)。
    右侧 mobile 导航抽屉(nav drawer)。
    来源:main.css 的 .bc-mobile-overlay / .bc-mobile-drawer 段落。
    来源:main.css 的 .bc-mobile-overlay / .bc-mobile-drawer 段落。
 
 

+ 1 - 1
api-v13/resources/css/layout/_footer.css → api-v13/resources/css/layout/footer.css

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

+ 16 - 12
api-v13/resources/css/layout/_grid.css → api-v13/resources/css/layout/grid.css

@@ -1,14 +1,14 @@
-/* resources/css/layout/_grid.css
+/* resources/css/layout/grid.css
    全站断点定义 + 通用栏容器。
    全站断点定义 + 通用栏容器。
-   包含从 _wiki.css 提取的三栏布局,供所有栏目复用。
+   包含从 wiki.css 提取的三栏布局,供所有栏目复用。
 */
 */
 
 
 /* ── 断点 token ── */
 /* ── 断点 token ── */
 :root {
 :root {
-    --bp-sm:  576px;
-    --bp-md:  768px;
-    --bp-lg:  992px;
-    --bp-xl:  1200px;
+    --bp-sm: 576px;
+    --bp-md: 768px;
+    --bp-lg: 992px;
+    --bp-xl: 1200px;
 }
 }
 
 
 /* ══════════════════════════════════════════
 /* ══════════════════════════════════════════
@@ -18,15 +18,19 @@
 .wiki-layout {
 .wiki-layout {
     display: grid;
     display: grid;
     grid-template-columns: 200px 1fr 200px;
     grid-template-columns: 200px 1fr 200px;
-    grid-template-areas: "left main right";
+    grid-template-areas: 'left main right';
     gap: 1.5rem;
     gap: 1.5rem;
     align-items: start;
     align-items: start;
     padding-top: 1.5rem;
     padding-top: 1.5rem;
     padding-bottom: 3rem;
     padding-bottom: 3rem;
 }
 }
 
 
-.wiki-sidebar-left  { grid-area: left; }
-.wiki-sidebar-right { grid-area: right; }
+.wiki-sidebar-left {
+    grid-area: left;
+}
+.wiki-sidebar-right {
+    grid-area: right;
+}
 
 
 .wiki-main {
 .wiki-main {
     grid-area: main;
     grid-area: main;
@@ -40,8 +44,8 @@
     .wiki-layout {
     .wiki-layout {
         grid-template-columns: 180px 1fr;
         grid-template-columns: 180px 1fr;
         grid-template-areas:
         grid-template-areas:
-            "left main"
-            "left right";
+            'left main'
+            'left right';
     }
     }
 
 
     .wiki-sidebar-right {
     .wiki-sidebar-right {
@@ -59,7 +63,7 @@
 @media (max-width: 768px) {
 @media (max-width: 768px) {
     .wiki-layout {
     .wiki-layout {
         grid-template-columns: 1fr;
         grid-template-columns: 1fr;
-        grid-template-areas: "main" "right" "left";
+        grid-template-areas: 'main' 'right' 'left';
     }
     }
 
 
     .wiki-sidebar-left,
     .wiki-sidebar-left,

+ 0 - 0
api-v13/resources/css/layout/_hero.css → api-v13/resources/css/layout/hero.css


+ 1 - 1
api-v13/resources/css/layout/_navbar.css → api-v13/resources/css/layout/navbar.css

@@ -1,4 +1,4 @@
-/* resources/css/layout/_navbar.css
+/* resources/css/layout/navbar.css
    顶部导航栏:面包屑 bar + desktop 导航链接 + mobile 汉堡按钮。
    顶部导航栏:面包屑 bar + desktop 导航链接 + mobile 汉堡按钮。
    来源:main.css 的 .anthology-breadcrumb-bar、.bc-nav、.bc-hamburger 段落。
    来源:main.css 的 .anthology-breadcrumb-bar、.bc-nav、.bc-hamburger 段落。
    以 wiki.css 中同名规则为准(main.css 无冲突,wiki.css 中无同名规则,直接迁移)。
    以 wiki.css 中同名规则为准(main.css 无冲突,wiki.css 中无同名规则,直接迁移)。

+ 1 - 1
api-v13/resources/css/layout/_toolbar.css → api-v13/resources/css/layout/toolbar.css

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

+ 18 - 18
api-v13/resources/css/library.css

@@ -5,29 +5,29 @@
 */
 */
 
 
 /* 1. Tabler 核心 */
 /* 1. Tabler 核心 */
-@import "@tabler/core/dist/css/tabler.min.css";
-@import "@tabler/icons-webfont/dist/tabler-icons.min.css";
+@import '@tabler/core/dist/css/tabler.min.css';
+@import '@tabler/icons-webfont/dist/tabler-icons.min.css';
 
 
 /* 2. 全站字体 */
 /* 2. 全站字体 */
-@import url("https://fonts.googleapis.com/css2?family=Noto+Serif:ital,wght@0,400;0,600;1,400&display=swap");
+@import url('https://fonts.googleapis.com/css2?family=Noto+Serif:ital,wght@0,400;0,600;1,400&display=swap');
 
 
 /* 3. 基础变量 */
 /* 3. 基础变量 */
-@import "./base/_variables.css";
-@import "./base/_reset.css";
-@import "./base/_typography.css";
+@import './base/variables.css';
+@import './base/reset.css';
+@import './base/typography.css';
 
 
 /* 4. 布局层 */
 /* 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";
+@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. 公共组件 */
 /* 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";
+@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';

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

@@ -1,284 +0,0 @@
-/* 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; }
-}

+ 533 - 0
api-v13/resources/css/modules/anthology.css

@@ -0,0 +1,533 @@
+/* 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 0.25s,
+        transform 0.25s;
+    margin-bottom: 1.1rem;
+    text-decoration: none;
+    color: inherit;
+}
+
+.anthology-card:hover {
+    box-shadow:
+        0 8px 28px rgba(200, 134, 10, 0.12),
+        0 2px 8px rgba(0, 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: 0.35rem;
+    line-height: 1.4;
+}
+
+.anthology-card:hover .anthology-card__title {
+    color: var(--wp-brand);
+}
+
+.anthology-card__desc {
+    font-size: 0.8rem;
+    color: var(--wp-ink-muted);
+    margin-bottom: 0.65rem;
+    line-height: 1.65;
+}
+
+.anthology-card__author {
+    margin-bottom: 0.7rem;
+}
+
+.anthology-card__tags {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 0.3rem;
+    margin-top: auto;
+}
+
+.anthology-tag {
+    font-size: 0.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: 0.85rem;
+    margin-top: 0.65rem;
+    padding-top: 0.65rem;
+    border-top: 1px solid var(--wp-border);
+}
+
+.anthology-meta-item {
+    font-size: 0.72rem;
+    color: var(--wp-ink-muted);
+    display: flex;
+    align-items: center;
+    gap: 0.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, 0.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 0.3rem;
+    letter-spacing: 0.08em;
+}
+
+.anthology-page-header p {
+    color: rgba(255, 255, 255, 0.45);
+    font-size: 0.85rem;
+    margin: 0;
+}
+
+.result-badge {
+    background: var(--wp-brand);
+    color: var(--wp-ink);
+    font-size: 0.75rem;
+    font-weight: 700;
+    padding: 2px 9px;
+    border-radius: 20px;
+    margin-left: 0.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: 0.8rem 1.2rem;
+    border-bottom: 1px solid var(--wp-border);
+    font-family: 'Noto Serif SC', 'Noto Serif', Georgia, serif;
+    font-size: 0.875rem;
+    font-weight: 600;
+    color: var(--wp-ink-soft);
+    letter-spacing: 0.04em;
+    display: flex;
+    align-items: center;
+    gap: 0.45rem;
+}
+
+.sb-head::before {
+    content: '';
+    display: block;
+    width: 3px;
+    height: 13px;
+    background: var(--wp-brand);
+    border-radius: 2px;
+}
+
+.smeta-row {
+    display: flex;
+    padding: 0.7rem 1.2rem;
+    border-bottom: 1px solid var(--wp-border);
+    font-size: 0.8rem;
+    align-items: flex-start;
+    gap: 0.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: 0.8rem;
+    padding: 1.1rem 1.2rem;
+}
+.author-block-name {
+    font-weight: 600;
+    font-size: 0.9rem;
+    color: var(--wp-ink);
+    margin-bottom: 0.18rem;
+}
+.author-block-stats {
+    font-size: 0.75rem;
+    color: var(--wp-ink-muted);
+}
+.author-bio {
+    font-size: 0.78rem;
+    color: var(--wp-ink-muted);
+    line-height: 1.65;
+    padding: 0.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: 0.7rem;
+    padding: 0.7rem 1.2rem;
+    border-bottom: 1px solid var(--wp-border);
+    text-decoration: none;
+    transition: background 0.15s;
+}
+.related-ul li:last-child a {
+    border-bottom: none;
+}
+.related-ul li a:hover {
+    background: var(--wp-surface-alt);
+}
+.related-t {
+    font-size: 0.8rem;
+    color: var(--wp-ink-soft);
+    font-weight: 500;
+    margin-bottom: 0.18rem;
+    line-height: 1.3;
+}
+.related-ul li a:hover .related-t {
+    color: var(--wp-brand);
+}
+.related-a {
+    font-size: 0.7rem;
+    color: var(--wp-ink-muted);
+}
+
+.author-ul {
+    list-style: none;
+    padding: 0.35rem 0;
+    margin: 0;
+}
+.author-ul li a {
+    display: flex;
+    align-items: center;
+    gap: 0.6rem;
+    padding: 0.45rem 1.15rem;
+    text-decoration: none;
+    transition: background 0.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: 0.85rem 1.4rem;
+    border-bottom: 1px solid var(--wp-border);
+    display: flex;
+    align-items: center;
+    gap: 0.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: 0.9rem;
+    font-weight: 600;
+    color: var(--wp-ink-soft);
+    letter-spacing: 0.04em;
+}
+.sec-count {
+    margin-left: auto;
+    font-size: 0.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: 0.855rem;
+    color: var(--wp-ink-soft);
+    line-height: 1.95;
+}
+.sec-body p {
+    margin-bottom: 0.8rem;
+}
+.sec-body p:last-child {
+    margin-bottom: 0;
+}
+
+.toc-ul {
+    list-style: none;
+    padding: 0.35rem 0;
+    margin: 0;
+}
+.toc-ul li a {
+    display: flex;
+    align-items: center;
+    padding: 0.65rem 1.4rem;
+    text-decoration: none;
+    border-bottom: 1px solid rgba(232, 221, 208, 0.5);
+    transition: background 0.15s;
+}
+.toc-ul li:last-child a {
+    border-bottom: none;
+}
+.toc-ul li a:hover {
+    background: var(--wp-surface-alt);
+}
+.toc-num {
+    font-size: 0.72rem;
+    color: var(--wp-ink-muted);
+    width: 26px;
+    flex-shrink: 0;
+}
+.toc-name {
+    font-size: 0.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: 0.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: 0.4rem;
+}
+.hero-subtitle {
+    font-size: 0.88rem;
+    color: rgba(255, 255, 255, 0.45);
+    font-style: italic;
+    letter-spacing: 0.04em;
+    margin-bottom: 1.1rem;
+}
+.hero-tags {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 0.35rem;
+    margin-bottom: 1.3rem;
+}
+.hero-tag {
+    font-size: 0.72rem;
+    padding: 2px 9px;
+    border-radius: 20px;
+    background: rgba(200, 134, 10, 0.2);
+    color: var(--wp-brand);
+    border: 1px solid rgba(200, 134, 10, 0.3);
+}
+.hero-info-row {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 1.4rem;
+    margin-bottom: 1.3rem;
+}
+.hi-item {
+    display: flex;
+    align-items: center;
+    gap: 0.45rem;
+}
+.hi-label {
+    font-size: 0.72rem;
+    color: rgba(255, 255, 255, 0.4);
+    letter-spacing: 0.04em;
+    display: block;
+}
+.hi-value {
+    font-size: 0.83rem;
+    color: rgba(255, 255, 255, 0.82);
+    display: block;
+}
+.hero-desc {
+    font-size: 0.85rem;
+    color: rgba(255, 255, 255, 0.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: 0.88rem;
+    padding: 0.55rem 1.6rem;
+    border-radius: 6px;
+    border: none;
+    cursor: pointer;
+    text-decoration: none;
+    display: inline-flex;
+    align-items: center;
+    gap: 0.45rem;
+    transition:
+        background 0.2s,
+        transform 0.15s;
+}
+.btn-read-primary:hover {
+    background: #dea020;
+    color: var(--wp-ink);
+    transform: translateY(-1px);
+}
+.btn-outline-hero {
+    background: transparent;
+    color: rgba(255, 255, 255, 0.7);
+    font-size: 0.85rem;
+    padding: 0.5rem 1.3rem;
+    border-radius: 6px;
+    border: 1px solid rgba(255, 255, 255, 0.2);
+    cursor: pointer;
+    text-decoration: none;
+    display: inline-flex;
+    align-items: center;
+    gap: 0.4rem;
+    transition: all 0.2s;
+    margin-left: 0.65rem;
+}
+.btn-outline-hero:hover {
+    border-color: rgba(255, 255, 255, 0.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;
+    }
+}

+ 16 - 8
api-v13/resources/css/modules/_library-index.css → api-v13/resources/css/modules/library-index.css

@@ -1,4 +1,4 @@
-/* resources/css/modules/_library-index.css
+/* resources/css/modules/library-index.css
    Library 门户首页专属样式。
    Library 门户首页专属样式。
 */
 */
 
 
@@ -110,8 +110,13 @@
 }
 }
 
 
 @keyframes lib-live-pulse {
 @keyframes lib-live-pulse {
-    0%, 100% { opacity: 1; }
-    50%       { opacity: 0.4; }
+    0%,
+    100% {
+        opacity: 1;
+    }
+    50% {
+        opacity: 0.4;
+    }
 }
 }
 
 
 /* ══════════════════════════════════════════
 /* ══════════════════════════════════════════
@@ -119,7 +124,7 @@
    ══════════════════════════════════════════ */
    ══════════════════════════════════════════ */
 
 
 .lib-recent {
 .lib-recent {
-    padding: 0;   /* 覆盖 wiki-card 默认 padding,由 item 自己管理 */
+    padding: 0; /* 覆盖 wiki-card 默认 padding,由 item 自己管理 */
 }
 }
 
 
 .lib-recent__item {
 .lib-recent__item {
@@ -232,14 +237,17 @@
     border-radius: var(--tblr-border-radius-lg);
     border-radius: var(--tblr-border-radius-lg);
     text-decoration: none;
     text-decoration: none;
     color: inherit;
     color: inherit;
-    transition: background 0.12s, transform 0.15s, box-shadow 0.15s;
+    transition:
+        background 0.12s,
+        transform 0.15s,
+        box-shadow 0.15s;
     height: 100%;
     height: 100%;
 }
 }
 
 
 .lib-nav-card:hover {
 .lib-nav-card:hover {
     background: var(--tblr-bg-surface-secondary);
     background: var(--tblr-bg-surface-secondary);
     transform: translateY(-2px);
     transform: translateY(-2px);
-    box-shadow: 0 4px 12px rgba(0,0,0,.06);
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
     color: inherit;
     color: inherit;
     text-decoration: none;
     text-decoration: none;
 }
 }
@@ -296,13 +304,13 @@
     }
     }
 
 
     .lib-nav-card__desc {
     .lib-nav-card__desc {
-        display: none;   /* 手机隐藏描述,只显示图标+名称 */
+        display: none; /* 手机隐藏描述,只显示图标+名称 */
     }
     }
 }
 }
 
 
 /* 三藏分类卡片:手机 2 列已由 Bootstrap col-6 处理 */
 /* 三藏分类卡片:手机 2 列已由 Bootstrap col-6 处理 */
 @media (max-width: 575px) {
 @media (max-width: 575px) {
     .lib-cat-card__more {
     .lib-cat-card__more {
-        display: none;  /* 手机隐藏"更多"链接,避免挤压 */
+        display: none; /* 手机隐藏"更多"链接,避免挤压 */
     }
     }
 }
 }

+ 1 - 1
api-v13/resources/css/modules/_reader-content.css → api-v13/resources/css/modules/reader-content.css

@@ -1,4 +1,4 @@
-/* resources/css/modules/_reader-content.css
+/* resources/css/modules/reader-content.css
    阅读器正文排版。所有规则限定在 article.reader-body 作用域内。
    阅读器正文排版。所有规则限定在 article.reader-body 作用域内。
    包含:基础排版、Tufte sidenote 集成、响应式断点对齐。
    包含:基础排版、Tufte sidenote 集成、响应式断点对齐。
 */
 */

+ 1 - 1
api-v13/resources/css/modules/_reader.css → api-v13/resources/css/modules/reader.css

@@ -1,4 +1,4 @@
-/* resources/css/modules/_reader.css
+/* resources/css/modules/reader.css
    全站阅读页专属样式。
    全站阅读页专属样式。
    来源:原 resources/css/reader.css(旧)+ reader 重构新增内容合并。
    来源:原 resources/css/reader.css(旧)+ reader 重构新增内容合并。
    以旧 reader.css 内容为准,新增部分追加在末尾。
    以旧 reader.css 内容为准,新增部分追加在末尾。

+ 0 - 0
api-v13/resources/css/modules/_tipitaka.css → api-v13/resources/css/modules/tipitaka.css


+ 3 - 3
api-v13/resources/css/modules/_wiki.css → api-v13/resources/css/modules/wiki.css

@@ -1,9 +1,9 @@
-/* resources/css/modules/_wiki.css
+/* resources/css/modules/wiki.css
    Wiki 栏目专属样式。
    Wiki 栏目专属样式。
    公共部分(布局、卡片、侧边栏、TOC、相关列表、meta表格、条目头部、精选网格)
    公共部分(布局、卡片、侧边栏、TOC、相关列表、meta表格、条目头部、精选网格)
    已提取至:
    已提取至:
-     - layout/_grid.css      → .wiki-layout 三栏布局
-     - components/_card.css  → .wiki-card / .wiki-sidebar-* / .wiki-cat-list /
+     - 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-toc-list / .wiki-related-list / .wiki-meta-table /
                                .wiki-entry-header / .wiki-featured-grid / .author-avatar
                                .wiki-entry-header / .wiki-featured-grid / .author-avatar
    本文件只保留 wiki 专属内容。
    本文件只保留 wiki 专属内容。

+ 2 - 2
api-v13/resources/css/reader.css

@@ -3,5 +3,5 @@
    只做 @import,不写任何样式规则。
    只做 @import,不写任何样式规则。
 */
 */
 
 
-@import './modules/_reader.css';
-@import './modules/_reader-content.css';
+@import './modules/reader.css';
+@import './modules/reader-content.css';

+ 1 - 1
api-v13/resources/js/modules/_reader.js → api-v13/resources/js/modules/reader.js

@@ -1,4 +1,4 @@
-// resources/js/modules/_reader.js
+// resources/js/modules/reader.js
 
 
 export function initReader() {
 export function initReader() {
     injectCommentaryMarkers();
     injectCommentaryMarkers();

+ 1 - 1
api-v13/resources/js/reader.js

@@ -1,4 +1,4 @@
-import { initReader } from './modules/_reader.js';
+import { initReader } from './modules/reader.js';
 
 
 document.addEventListener('DOMContentLoaded', () => {
 document.addEventListener('DOMContentLoaded', () => {
     initReader();
     initReader();

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

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

+ 1 - 1
api-v13/resources/views/library/anthology/index.blade.php

@@ -4,7 +4,7 @@
 @section('title', '文集 · 巴利书库')
 @section('title', '文集 · 巴利书库')
 
 
 @push('styles')
 @push('styles')
-@vite('resources/css/modules/_anthology.css')
+@vite('resources/css/modules/anthology.css')
 @endpush
 @endpush
 
 
 @section('breadcrumb')
 @section('breadcrumb')

+ 10 - 14
api-v13/resources/views/library/anthology/show.blade.php

@@ -4,7 +4,7 @@
 @section('title', $anthology['title'] . ' · 巴利书库')
 @section('title', $anthology['title'] . ' · 巴利书库')
 
 
 @push('styles')
 @push('styles')
-    @vite('resources/css/modules/_anthology.css')
+@vite('resources/css/modules/anthology.css')
 @endpush
 @endpush
 
 
 @section('breadcrumb')
 @section('breadcrumb')
@@ -29,8 +29,7 @@
                 :title="$anthology['title']"
                 :title="$anthology['title']"
                 :subtitle="$anthology['subtitle'] ?? ''"
                 :subtitle="$anthology['subtitle'] ?? ''"
                 size="lg"
                 size="lg"
-                :style3d="true"
-            />
+                :style3d="true" />
 
 
             {{-- 文集信息 --}}
             {{-- 文集信息 --}}
             <div class="hero-content">
             <div class="hero-content">
@@ -55,8 +54,7 @@
                             :color="$anthology['author']['color']"
                             :color="$anthology['author']['color']"
                             :initials="$anthology['author']['initials']"
                             :initials="$anthology['author']['initials']"
                             :name="$anthology['author']['name']"
                             :name="$anthology['author']['name']"
-                            size="sm"
-                        />
+                            size="sm" />
                         <div>
                         <div>
                             <span class="hi-label">作者</span>
                             <span class="hi-label">作者</span>
                             <span class="hi-value">{{ $anthology['author']['name'] }}</span>
                             <span class="hi-value">{{ $anthology['author']['name'] }}</span>
@@ -92,13 +90,13 @@
                             'anthology' => $anthology['id'],
                             'anthology' => $anthology['id'],
                             'article'   => $anthology['articles'][0]['id']
                             'article'   => $anthology['articles'][0]['id']
                         ]) }}"
                         ]) }}"
-                       class="btn-read-primary">
+                        class="btn-read-primary">
                         <i class="ti ti-book-2"></i>
                         <i class="ti ti-book-2"></i>
                         在线阅读
                         在线阅读
                     </a>
                     </a>
                     @endif
                     @endif
                     <a href="{{ config('mint.server.dashboard_base_path') }}/workspace/anthology/{{ $anthology['id'] }}"
                     <a href="{{ config('mint.server.dashboard_base_path') }}/workspace/anthology/{{ $anthology['id'] }}"
-                       class="btn-outline-hero">
+                        class="btn-outline-hero">
                         <i class="ti ti-pencil"></i>
                         <i class="ti ti-pencil"></i>
                         在编辑器中打开
                         在编辑器中打开
                     </a>
                     </a>
@@ -127,9 +125,9 @@
                     </div>
                     </div>
                     <div class="sec-body">
                     <div class="sec-body">
                         @foreach(explode("\n", $anthology['about']) as $para)
                         @foreach(explode("\n", $anthology['about']) as $para)
-                            @if(trim($para))
-                            <p>{{ trim($para) }}</p>
-                            @endif
+                        @if(trim($para))
+                        <p>{{ trim($para) }}</p>
+                        @endif
                         @endforeach
                         @endforeach
                     </div>
                     </div>
                 </div>
                 </div>
@@ -206,8 +204,7 @@
                             :avatar="$anthology['author']['avatar'] ?? null"
                             :avatar="$anthology['author']['avatar'] ?? null"
                             :color="$anthology['author']['color']"
                             :color="$anthology['author']['color']"
                             :initials="$anthology['author']['initials']"
                             :initials="$anthology['author']['initials']"
-                            size="lg"
-                        />
+                            size="lg" />
                         <div>
                         <div>
                             <div class="author-block-name">{{ $anthology['author']['name'] }}</div>
                             <div class="author-block-name">{{ $anthology['author']['name'] }}</div>
                             <div class="author-block-stats">
                             <div class="author-block-stats">
@@ -235,8 +232,7 @@
                                     :gradient="$rel['cover_gradient']"
                                     :gradient="$rel['cover_gradient']"
                                     :title="mb_substr($rel['title'], 0, 4)"
                                     :title="mb_substr($rel['title'], 0, 4)"
                                     size="sm"
                                     size="sm"
-                                    :style3d="false"
-                                />
+                                    :style3d="false" />
                                 <div>
                                 <div>
                                     <div class="related-t">{{ $rel['title'] }}</div>
                                     <div class="related-t">{{ $rel['title'] }}</div>
                                     <div class="related-a">{{ $rel['author_name'] }}</div>
                                     <div class="related-a">{{ $rel['author_name'] }}</div>

+ 0 - 28
api-v13/resources/views/library/book/_toc.blade.php

@@ -1,28 +0,0 @@
-{{-- resources/views/library/book/_toc.blade.php
-     TOC 列表局部视图,在 offcanvas 和侧边栏中复用。
-     变量:$toc array,$anthologyId(可选)
---}}
-@if(!empty($toc))
-<ul>
-    @foreach ($toc as $item)
-    <li class="toc_item toc-level-{{ $item['level'] }}
-               {{ ($item['active'] ?? false) ? 'toc-active' : ($item['disabled'] ? 'toc-disabled' : '') }}">
-        @if($item['active'] ?? false)
-            <span title="{{ $item['title'] }}">{{ $item['title'] }}</span>
-        @elseif(!$item['disabled'])
-            @if(isset($anthologyId))
-                <a href="{{ route('library.anthology.read', ['anthology' => $anthologyId, 'article' => $item['id'], 'channel' => request('channel')]) }}"
-                   title="{{ $item['title'] }}">{{ $item['title'] }}</a>
-            @else
-                <a href="{{ route('library.tipitaka.read', ['id' => $item['id'], 'channel' => request('channel')]) }}"
-                   title="{{ $item['title'] }}">{{ $item['title'] }}</a>
-            @endif
-        @else
-            <span title="{{ $item['title'] }}">{{ $item['title'] }}</span>
-        @endif
-    </li>
-    @endforeach
-</ul>
-@else
-<div class="alert alert-warning">此书没有目录</div>
-@endif

+ 28 - 0
api-v13/resources/views/library/book/toc.blade.php

@@ -0,0 +1,28 @@
+{{-- resources/views/library/book/toc.blade.php
+     TOC 列表局部视图,在 offcanvas 和侧边栏中复用。
+     变量:$toc array,$anthologyId(可选)
+--}}
+@if(!empty($toc))
+<ul>
+    @foreach ($toc as $item)
+    <li class="toc_item toc-level-{{ $item['level'] }}
+               {{ ($item['active'] ?? false) ? 'toc-active' : ($item['disabled'] ? 'toc-disabled' : '') }}">
+        @if($item['active'] ?? false)
+        <span title="{{ $item['title'] }}">{{ $item['title'] }}</span>
+        @elseif(!$item['disabled'])
+        @if(isset($anthologyId))
+        <a href="{{ route('library.anthology.read', ['anthology' => $anthologyId, 'article' => $item['id'], 'channel' => request('channel')]) }}"
+            title="{{ $item['title'] }}">{{ $item['title'] }}</a>
+        @else
+        <a href="{{ route('library.tipitaka.read', ['id' => $item['id'], 'channel' => request('channel')]) }}"
+            title="{{ $item['title'] }}">{{ $item['title'] }}</a>
+        @endif
+        @else
+        <span title="{{ $item['title'] }}">{{ $item['title'] }}</span>
+        @endif
+    </li>
+    @endforeach
+</ul>
+@else
+<div class="alert alert-warning">此书没有目录</div>
+@endif

+ 1 - 1
api-v13/resources/views/library/index.blade.php

@@ -7,7 +7,7 @@
 @section('title', 'WikiPāli · 巴利书库')
 @section('title', 'WikiPāli · 巴利书库')
 
 
 @push('styles')
 @push('styles')
-@vite('resources/css/modules/_library-index.css')
+@vite('resources/css/modules/library-index.css')
 @endpush
 @endpush
 
 
 {{-- Hero --}}
 {{-- Hero --}}

+ 1 - 1
api-v13/resources/views/library/tipitaka/category.blade.php

@@ -7,7 +7,7 @@
 @section('title', $currentCategory['name'] . ' · 巴利书库')
 @section('title', $currentCategory['name'] . ' · 巴利书库')
 
 
 @push('styles')
 @push('styles')
-@vite('resources/css/modules/_tipitaka.css')
+@vite('resources/css/modules/tipitaka.css')
 @endpush
 @endpush
 
 
 @section('breadcrumb')
 @section('breadcrumb')

+ 1 - 1
api-v13/resources/views/library/tipitaka/show.blade.php

@@ -4,7 +4,7 @@
 @section('title', $book['title'] . ' · 巴利书库')
 @section('title', $book['title'] . ' · 巴利书库')
 
 
 @push('styles')
 @push('styles')
-@vite('resources/css/modules/_tipitaka.css')
+@vite('resources/css/modules/tipitaka.css')
 @endpush
 @endpush
 
 
 @section('breadcrumb')
 @section('breadcrumb')

+ 1 - 1
api-v13/resources/views/library/wiki/home.blade.php

@@ -1,7 +1,7 @@
 {{-- resources/views/wiki/home.blade.php
 {{-- resources/views/wiki/home.blade.php
      Wiki 门户首页。
      Wiki 门户首页。
      布局:单栏居中,法轮图标 + 标题 + 搜索框 + 热门标签 + 语言选择。
      布局:单栏居中,法轮图标 + 标题 + 搜索框 + 热门标签 + 语言选择。
-     所有样式来自 modules/_wiki.css,无内联 <style>。
+     所有样式来自 modules/wiki.css,无内联 <style>。
 --}}
 --}}
 @extends('library.wiki.layouts.app')
 @extends('library.wiki.layouts.app')
 
 

+ 1 - 1
api-v13/resources/views/library/wiki/layouts/app.blade.php

@@ -6,7 +6,7 @@
 @extends('layouts.library')
 @extends('layouts.library')
 
 
 @push('styles')
 @push('styles')
-@vite('resources/css/modules/_wiki.css')
+@vite('resources/css/modules/wiki.css')
 @endpush
 @endpush
 
 
 @section('content')
 @section('content')

+ 2 - 0
api-v13/routes/api.php

@@ -126,6 +126,7 @@ use App\Http\Controllers\UpgradeController;
 use App\Http\Controllers\ChapterContentController;
 use App\Http\Controllers\ChapterContentController;
 use App\Http\Controllers\ParagraphContentController;
 use App\Http\Controllers\ParagraphContentController;
 use App\Http\Controllers\HeartbeatController;
 use App\Http\Controllers\HeartbeatController;
+use App\Http\Controllers\ProgressController;
 
 
 
 
 
 
@@ -326,4 +327,5 @@ Route::group([
     Route::apiResource('search', SearchPlusController::class);
     Route::apiResource('search', SearchPlusController::class);
     Route::apiResource('search-suggest', SearchSuggestController::class);
     Route::apiResource('search-suggest', SearchSuggestController::class);
     Route::apiResource('upgrade', UpgradeController::class);
     Route::apiResource('upgrade', UpgradeController::class);
+    Route::apiResource('progress', ProgressController::class);
 });
 });

+ 1 - 0
api-v13/vite.config.ts

@@ -10,6 +10,7 @@ export default defineConfig({
         laravel({
         laravel({
             input: [
             input: [
                 'resources/css/library.css', // library/* + blog 列表页
                 'resources/css/library.css', // library/* + blog 列表页
+		'resources/css/modules/library-index.css',
                 'resources/css/reader.css', // 全站阅读页(待建)
                 'resources/css/reader.css', // 全站阅读页(待建)
                 'resources/js/app.js',
                 'resources/js/app.js',
             ],
             ],