# 皮肤（主题）开发指南

## 目录格式
```
themes/{slug}/
  theme.json          # 元数据
  assets/             # 静态资源（CSS/图片/预览）
    style.css
    preview.svg       # 后台卡片预览（建议 16:10）
  views/              # Blade，命名空间 theme::
    layout.blade.php
    home.blade.php
    article.blade.php
    ...
```

### theme.json 示例
```json
{
  "name": "my-theme",
  "title": "我的皮肤",
  "version": "1.0.0",
  "description": "说明文字",
  "slots": "*"
}
```

### 必须声明 `slots`
皮肤应在 `theme.json` 声明支持的注入槽（后台「皮肤」卡片会校验）：

| 写法 | 含义 |
|---|---|
| `"slots": "*"` 或 `"all"` | 支持全部标准槽（推荐） |
| `"slots": ["head", "sidebar", "my_slot"]` | 显式列表；可混用 `"*"` 再加自定义槽 |
| 省略 `slots` | **不合规**：后台标「未声明」；启用时警告 |

校验还会扫 `views/**/*.blade.php` 里的 `@themeslot` / `ThemeSlot::render`（非 default 主题会把未覆盖的 default 视图算进有效槽）。声明了但视图未调用 →「声明与视图不一致」。

## 视图约定
- 使用 `@extends('theme::layout')`
- 控制器传：`$articles` / `$article` / `$categories` / `$settings` / `$seo`
- 静态资源 URL：`app(ThemeManager::class)->assetUrl('style.css')` → `/themes/{slug}/style.css`
- 文案：`__('frontend.nav.home')` 等，勿写死中英文

## 内容注入点（ThemeSlot）

主题应在固定位置调用 `@themeslot('槽名')`（或 `ThemeSlot::render('槽名')`）。  
插件与后台「注入 / 广告 / 统计」都会写入同一套槽。

### 同一位置可多条
`Hook::gather('theme.{slot}')` 会按注册顺序把**后台片段 + 各插件**拼成一段 HTML。  
同一槽塞 3 个插件广告 + 后台一段，都会出来（纵向叠放，由主题 CSS 决定外观）。

### 可控边界（换别人做的 theme）
| 可控 | 不可强控 |
|---|---|
| 标准槽名（API）：`head` / `sidebar` / … | 像素级宽高、是否做成卡片 |
| 主题是否调用了 `@themeslot`（合规皮肤应全挂） | 侧栏在左还是右、主栏实际像素 |
| 建议尺寸写在 `ThemeSlot::catalog()` / 后台 helper | 第三方主题漏掉某个槽 → 该注入静默不显示 |

结论：**槽名是硬约定，版式是软建议**。别人做 theme 只要挂齐标准槽，内容就能到「大概那个位置」；精确到「300×250 居中」做不到，也不该做进核心。合规检查可后续做：扫描 views 是否包含全部标准 `@themeslot`。

尺寸/位置说明见 `ThemeSlot::catalog()`（后台「注入」Tab 的 helper 同步显示）。

| 槽名 | 典型用途 | 建议尺寸（软） |
|---|---|---|
| `head` | 统计 / 额外 CSS·JS | 无版面 |
| `body_start` / `body_end` | body 首尾脚本 | 无版面 |
| `header_after` | 页头横幅、公告 | 通栏高约 60–120px |
| `nav_after` | 导航后临时入口 | 文字链 |
| `content_before` / `content_after` | 主栏前后 | 主栏宽约 640–760px |
| `article_top` / `article_bottom` | 文章广告 | 主栏宽 × 90–250px |
| `sidebar_before` / `sidebar` / `sidebar_after` | 侧栏广告 / 临时链接 | 侧栏约 280–320px，多条叠放 |
| `footer_before` / `footer_after` | 页脚附加 | 通栏 |

### 后台怎么填
`/admin` → **站点设置** → Tab「注入 / 广告 / 统计」：
1. **统计代码** → `head`
2. **侧栏广告** → `sidebar`
3. **临时链接 HTML** → `sidebar_after`（可写 `<a href="...">活动</a>`）
4. **文章顶/底广告** → `article_top` / `article_bottom`

也可继续用 **站点片段（Stylevars）** 做纯文本侧栏块；友情链接仍走「友情链接」资源。

### 插件怎么注入
```php
use App\Domain\Plugin\Hook;
use App\Domain\Theme\ThemeSlot;

Hook::listen(ThemeSlot::event(ThemeSlot::SIDEBAR), function (string $html): string {
    return $html.'<p><a href="/promo">活动页</a></p>';
});
```

### 主题里任意位置
在任意 Blade 加一行即可，例如正文中间：

```blade
@themeslot('article_top')
{{-- 或自定义新槽：插件 listen theme.my_slot，主题写 @themeslot('my_slot') --}}
```

新槽无需改核心：约定 `theme.{name}` 事件即可。

## 发布
```bash
php artisan themes:publish
# 或后台「皮肤」→ 启用 / 发布静态资源
```
资源复制到 `public/themes/{slug}/`。

## 回退
`ThemeManager` 先注册 `default` views，再 `prepend` 当前主题；缺视图自动回退 default。

## 切换
后台「皮肤」卡片启用，或改 Settings `general.active_theme`。
