---
name: 極簡奢華風 UI 實作規範 (Minimal Luxury UI)
description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範，包含 CSS Tokens、常用組件樣式、動畫效果與互動模式，確保全站 15+ 模組的視覺一致性。
---

# 極簡奢華風 UI 實作規範 (Minimal Luxury UI)

> [!IMPORTANT]
> **開始寫任何頁面前，先對齊本規範。所有規範以此文件為最終依據。**
> 黃金範本頁面：Type A → `admin/machines/index.blade.php`｜Type B → `admin/products/index.blade.php`

---

## 1. 全局速查卡 (Quick Reference)

### 1.1 字型規則

| 使用場景 | Tailwind Class |
|---------|---------------|
| 頁面主標題 (H1)、大數字 | `font-display`（Outfit） |
| 所有內文、按鈕、標籤 | `font-sans`（Plus Jakarta Sans，預設不需加） |
| SN 碼、版本號、Barcode、時間戳 | `font-mono`（僅限純英數代碼） |

### 1.2 文字大小階層

| 元素 | 大小 | 字重 | 顏色 | 其他 |
|------|------|------|------|------|
| 頁面 H1 | `text-2xl sm:text-3xl` | `font-black` | `slate-800 dark:white` | `tracking-tight font-display` |
| 頁面副標題 | `text-sm` | `font-bold` | `slate-500 dark:slate-400` | `uppercase tracking-widest mt-1` |
| 表格標頭 (th) | `text-xs` | `font-bold` | `slate-500 dark:slate-400` | `uppercase tracking-[0.15em]` |
| 表格主要資料 | `text-base` | `font-extrabold` | `slate-800 dark:slate-100` | `tracking-tight` |
| 表格次要/描述 | `text-xs` | `font-bold` | `slate-500 dark:slate-400` | `tracking-wide` |
| 技術碼（SN/ID/條碼）| `text-xs` | `font-black` | `slate-400 dark:slate-500` | `font-mono uppercase tracking-widest` |
| 時間戳 | `text-xs` | `font-black` | `slate-400` | `font-mono tracking-widest` |
| 狀態 Badge | `text-[10px]` | `font-black` | 依狀態色系 | `uppercase tracking-widest` |
| Modal 標題 | `text-xl sm:text-2xl` | `font-black` | `slate-800 dark:white` | `font-display tracking-tight` |
| Mobile Card 標籤 | `text-[10px]` | `font-black` | `slate-400 dark:slate-500` | `uppercase tracking-widest mb-1` |
| Mobile Card 主標題 | `text-base` | `font-extrabold` | `slate-800 dark:slate-100` | `tracking-tight` |
| Mobile Card 副標（技術碼）| `text-xs` | `font-bold font-mono` | `slate-400 dark:slate-500` | `uppercase tracking-widest` |

> ❌ 嚴禁使用 `italic`｜❌ Mobile Card 標題禁用 `text-lg`

### 1.3 顏色速查

| 用途 | Class |
|------|-------|
| 品牌強調／按鈕 | `cyan-500` / `bg-cyan-500` |
| 成功／上線 | `emerald-500` / `bg-emerald-500/10` |
| 警告／等待 | `amber-500` / `bg-amber-500/10` |
| 錯誤／危險 | `rose-500` / `bg-rose-500/10` |
| 進階／系統 | `indigo-500` / `bg-indigo-500/10` |
| 停用／次要 | `slate-400` / `bg-slate-500/10` |

> Badge 背景：`bg-{color}/10 border border-{color}/20 text-{color}-500`

### 1.4 互動效果

| 元素 | Hover | Transition |
|------|-------|-----------|
| Primary 按鈕 | `hover:bg-cyan-600` + `active:scale-95` | `transition-all duration-300` |
| 表格列 (tr) | `hover:bg-slate-50/50 dark:hover:bg-white/[0.02]` | `transition-colors duration-200` |
| 表格操作按鈕 | `hover:bg-cyan-500/5 hover:text-cyan-500` | `transition-all duration-200` |
| 危險操作按鈕 | `hover:bg-rose-500/5 hover:text-rose-500` | `transition-all duration-200` |
| Mobile Card 操作 | `hover:bg-cyan-500 hover:text-white` | `transition-all duration-300` |

### 1.5 圓角規則

| 元素 | Class |
|------|-------|
| 主要內容卡片（桌面）| `rounded-3xl` |
| Mobile Card | `rounded-[2rem]`（比桌面更大）|
| 按鈕、輸入框 | `rounded-xl` |
| 狀態 Badge | `rounded-full` |
| 縮圖卡片、圖示框 | `rounded-2xl` |

### 1.6 進場動畫

| 場景 | 做法 |
|------|------|
| 頁面卡片、主內容區 | `animate-luxury-in` |
| 多卡片依序 | `style="animation-delay: 100ms"` 遞增 |
| Loading Overlay | `<x-luxury-spinner>`（禁止手寫）|
| Slide-over 面板 | `translate-x-full → translate-x-0` (x-transition) |

---

## 2. 強制使用的組件

> [!IMPORTANT]
> 以下 HTML 結構**嚴禁手寫**，一律使用組件：

| 禁止手寫 | 改用組件 |
|---------|---------|
| `<h1>` 標題 + 副標題 + Action 按鈕 | `<x-page-header>` |
| Tab 導覽列 | `<x-tab-nav>` + `<x-tab-nav-item>` |
| Loading Overlay | `<x-luxury-spinner>` |
| 搜尋欄 + 重置按鈕（Type A 純列表） | `<x-search-bar>` |
| 狀態標籤 | `<x-status-badge>` |
| 統計卡片 | `<x-stat-card>` |
| 空狀態提示 | `<x-empty-state>` |
| 刪除確認 | `<x-delete-confirm-modal>` |

### x-luxury-spinner 傳參規則

> [!CAUTION]
> - ✅ 正確：`show="isLoadingTable"`
> - ✅ 正確：`show="tabLoading === 'products'"`
> - ❌ 錯誤：`:show="\"tabLoading === 'products'\""` → **Blade ParseError！**
> - ❌ 錯誤：`:show="'isLoadingTable'"` → 語意不清

### x-status-badge 用法

```html
{{-- 靜態 --}}
<x-status-badge :status="$machine->is_online ? 'online' : 'offline'" />

{{-- Mobile Card 用小尺寸 --}}
<x-status-badge :status="$item->status" size="sm" />
```

內建狀態：`active`, `inactive`, `online`, `offline`, `enabled`, `disabled`, `pending`, `error`

### x-empty-state 用法

```html
{{-- 表格 @empty 中 --}}
<x-empty-state mode="table" :colspan="6" :message="__('No data found')" />

{{-- Card Grid @empty 中 --}}
<x-empty-state :message="__('No records found')" />
```

### x-stat-card 用法

```html
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
    <x-stat-card :label="__('Total')"  :value="$stats['total']"     />
    <x-stat-card :label="__('Online')" :value="$stats['online']"    color="cyan"   delay="100ms" />
    <x-stat-card :label="__('Error')"  :value="$stats['error']"     color="rose"   delay="200ms" />
    <x-stat-card :label="__('Idle')"   :value="$stats['idle']"      color="amber"  delay="300ms" />
</div>
```

---

## 3. 頁面佈局範本 (Page Templates)

### Type A — 純列表（黃金範本：machines/index.blade.php）

```html
@section('content')
<div class="space-y-4 pb-20" x-data="machineApp()">

    <x-page-header
        :title="__('Module Name')"
        :subtitle="__('Description')"
    >
        <button @click="openCreateModal()" class="btn-luxury-primary flex items-center gap-2">
            <svg class="w-4 h-4">...</svg>
            <span class="text-sm sm:text-base">{{ __('Add') }}</span>
        </button>
    </x-page-header>

    <div class="luxury-card rounded-3xl p-8 animate-luxury-in relative overflow-hidden">
        <x-luxury-spinner show="isLoadingTable" />
        <div id="ajax-content-container" :class="{ 'opacity-30 pointer-events-none': isLoadingTable }">

            <x-search-bar
                :action="route('admin.xxx.index')"
                :value="request('search')"
                :placeholder="__('Search...')"
            />

            {{-- 桌面表格 --}}
            <div class="hidden xl:block overflow-x-auto">
                <table class="w-full text-left border-separate border-spacing-y-0">
                    <thead>
                        <tr class="bg-slate-50/50 dark:bg-slate-900/10">
                            <th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
                                {{ __('Column') }}
                            </th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
                        @forelse($items as $item)
                        <tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200">
                            <td class="px-6 py-6">...</td>
                        </tr>
                        @empty
                        <x-empty-state mode="table" :colspan="5" :message="__('No records found')" />
                        @endforelse
                    </tbody>
                </table>
            </div>

            {{-- Mobile/Tablet Card Grid --}}
            <div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
                @foreach($items as $item)
                {{-- 見 Section 4：Mobile Card 黃金範本 --}}
                @endforeach
            </div>

            {{-- 分頁 --}}
            <div class="mt-8 border-t border-slate-100/50 dark:border-slate-800/50 pt-6">
                {{ $items->appends(request()->query())->links('vendor.pagination.luxury') }}
            </div>

        </div>
    </div>
</div>
@endsection
```
---

### Type B — Tab + 列表（黃金範本：products/index.blade.php）

```html
@section('content')
<div class="space-y-2 pb-20" x-data="{ activeTab: 'main', tabLoading: null }">

    <x-page-header :title="__('Module Name')" :subtitle="__('Description')">
        <template x-if="activeTab === 'main'">
            <a href="..." class="btn-luxury-primary flex items-center gap-2">...</a>
        </template>
    </x-page-header>

    <x-tab-nav>
        <x-tab-nav-item value="main"      :label="__('Main List')" />
        <x-tab-nav-item value="secondary" :label="__('Secondary')" />
    </x-tab-nav>

    <div x-show="activeTab === 'main'" class="luxury-card rounded-3xl p-6 animate-luxury-in relative min-h-[300px]" x-cloak>
        <x-luxury-spinner show="tabLoading === 'main'" z-index="z-20" />
        <div id="tab-main-container">
            @include('admin.xxx.partials.tab-main')
        </div>
    </div>

    <div x-show="activeTab === 'secondary'" class="luxury-card rounded-3xl p-6 animate-luxury-in relative min-h-[300px]" x-cloak>
        <x-luxury-spinner show="tabLoading === 'secondary'" z-index="z-20" />
        <div id="tab-secondary-container">
            @include('admin.xxx.partials.tab-secondary')
        </div>
    </div>

</div>
@endsection
```

> ⚠️ **Tab Partial 搜尋欄**：Tab 頁面的 `@submit.prevent` 需呼叫 `handleFilterSubmit('tab_name')`，而非 `fetchPage()`。因此 Tab Partial **不使用** `<x-search-bar>`，須自行手寫 `<form @submit.prevent="handleFilterSubmit('...')">` 結構。

---

## 4. Mobile Card 黃金範本

> [!IMPORTANT]
> 所有 Mobile Card 必須嚴格遵守以下三區結構。禁止自行發明新結構。

### 卡片容器
```html
<div class="luxury-card p-6 rounded-[2rem] border border-slate-100 dark:border-slate-800
            bg-white/50 dark:bg-slate-900/50 transition-all duration-300 group">
```

### 區域一：Card Header
```html
<div class="flex items-start justify-between gap-4 mb-6">

    <div class="flex items-center gap-4 min-w-0">
        {{-- 圖示 56x56，hover 變 cyan --}}
        <div class="w-14 h-14 rounded-2xl bg-slate-100 dark:bg-slate-800
                    flex items-center justify-center text-slate-400
                    border border-slate-200 dark:border-slate-700
                    group-hover:bg-cyan-500 group-hover:text-white
                    transition-all duration-300 overflow-hidden shadow-sm shrink-0">
            {{-- img or SVG icon --}}
        </div>

        <div class="min-w-0">
            {{-- 主標題：text-base font-extrabold，絕對不用 text-lg --}}
            <h3 class="text-base font-extrabold text-slate-800 dark:text-slate-100
                       truncate hover:text-cyan-600 dark:hover:text-cyan-400
                       transition-colors tracking-tight cursor-pointer">
                主要名稱
            </h3>
            {{-- 副標（SN/Barcode）--}}
            <p class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500
                      uppercase tracking-widest truncate">
                副標
            </p>
        </div>
    </div>

    {{-- 右側：強制用 x-status-badge，禁止用小圓點 --}}
    <x-status-badge :status="$item->status" size="sm" />

</div>
```

### 區域二：Info Grid
```html
<div class="grid grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">

    <div>
        <p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">
            {{ __('Label') }}
        </p>
        <p class="text-sm font-bold text-slate-700 dark:text-slate-300">值</p>
    </div>

    {{-- 跨欄（如時間戳）--}}
    <div class="col-span-2">
        <p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">
            {{ __('Last Updated') }}
        </p>
        <p class="text-sm font-bold text-slate-700 dark:text-slate-300 font-mono">
            {{ $item->updated_at->format('Y-m-d H:i:s') }}
        </p>
    </div>

</div>
```

### 區域三：Action Buttons
```html
<div class="flex items-center gap-3">
    {{-- 一般動作 --}}
    <button type="button" @click="..."
        class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl
               bg-slate-50 dark:bg-slate-800 text-slate-500 dark:text-slate-400
               font-bold text-xs hover:bg-cyan-500 hover:text-white
               transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50">
        <svg class="w-3.5 h-3.5">...</svg>
        {{ __('Edit') }}
    </button>

    {{-- 危險動作 --}}
    <button type="button" @click="..."
        class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl
               bg-slate-50 dark:bg-slate-800 text-slate-500 dark:text-slate-400
               font-bold text-xs hover:bg-rose-500 hover:text-white
               transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50">
        <svg class="w-3.5 h-3.5">...</svg>
        {{ __('Delete') }}
    </button>
</div>
```

---

## 5. RWD 斷點規則

| 元素 | 規則 |
|------|------|
| 桌面表格顯示 | `hidden xl:block` |
| Mobile Card 顯示 | `xl:hidden` |
| Card Grid 欄數 | `grid-cols-1 md:grid-cols-2 gap-6` |
| 切換點 | 統一 `xl`（1280px）**禁止用 `lg` 或 `2xl`** |
| Page Header 排列 | 全斷點橫向排列 (flex-row) | 組件已處理，標題與按鈕並列 |
| H1 字大小 | `text-2xl sm:text-3xl` | 組件已處理，帶有 truncate 避免擠壓 |
| Tab 列寬 | `w-full sm:w-fit`（組件已處理）|
| Tab 按鈕 | `flex-1 sm:flex-none`（組件已處理）|

### RWD 禁止事項

> [!CAUTION]
> - ❌ Mobile Card 標題用 `text-lg`（必須 `text-base font-extrabold`）
> - ❌ Mobile Card 右上角用小圓點代替 `x-status-badge`
> - ❌ 操作按鈕不加 `flex-1`
> - ❌ Mobile Card 圓角用 `rounded-3xl`（應為 `rounded-[2rem]`）
> - ❌ Info Grid 欄位標籤用 `text-xs`（應為 `text-[10px]`）
> - ❌ 桌面/手機切換用 `lg` 斷點

---

## 6. 表單元件

```html
{{-- 輸入框 --}}
<input type="text" class="luxury-input py-2.5 pl-11 pr-4 block w-full sm:w-72 text-sm">

{{-- 可搜尋下拉（選項 > 10 筆時）--}}
<x-searchable-select
    name="company_id"
    :options="$companies"
    :selected="request('company_id')"
    :placeholder="__('All Companies')"
/>

{{-- 下拉選單遮擋處理範例 --}}
<div class="relative focus-within:z-20">
    <label>...</label>
    <x-searchable-select ... />
</div>
```

---

## 7. 提示與告警

| 類型 | 組件 |
|------|------|
| 成功／錯誤 Toast | `<x-toast />` |
| 刪除確認 Modal | `<x-delete-confirm-modal />` |
| 頁面內警告（琥珀色）| `<div class="p-5 bg-amber-500/10 border border-amber-500/20 text-amber-600 rounded-2xl flex items-start gap-4 font-bold">` |

### 7.1 Modal 實作規範（強制）

> [!CAUTION]
> **嚴禁使用 Preline 的 `hs-overlay` 系統**來實作 Modal。
> 本專案全部 Modal 統一使用 **Alpine.js** (`x-show` + `x-transition`) 實作。
> 使用 `hs-overlay` class 會導致 Modal 出現但完全透明（`opacity: 0`），因為 Preline overlay 的動畫 class 在此專案中未被正確初始化。

**正確：Alpine.js Modal 標準結構（參考 `modal-replenishment-create.blade.php`）**

```html
{{-- 1. 觸發按鈕：用 $dispatch 發送 window 事件 --}}
<button type="button" @click="$dispatch('open-xxx-modal')">開啟</button>

{{-- 2. Modal 主體：放在 layout 底部，x-data 宣告 Alpine state --}}
<div x-data="{ showModal: false }" @open-xxx-modal.window="showModal = true" x-cloak>
    <div x-show="showModal" class="fixed inset-0 z-[110] overflow-y-auto" style="display: none;">
        <div class="flex items-center justify-center min-h-screen px-4 ...">

            {{-- Backdrop --}}
            <div x-show="showModal"
                x-transition:enter="ease-out duration-300"
                x-transition:enter-start="opacity-0"
                x-transition:enter-end="opacity-100"
                x-transition:leave="ease-in duration-200"
                x-transition:leave-start="opacity-100"
                x-transition:leave-end="opacity-0"
                class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm"
                @click="showModal = false"></div>

            {{-- Modal 內容 --}}
            <div x-show="showModal"
                x-transition:enter="ease-out duration-300"
                x-transition:enter-start="opacity-0 translate-y-4 sm:scale-95"
                x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
                x-transition:leave="ease-in duration-200"
                x-transition:leave-start="opacity-100 sm:scale-100"
                x-transition:leave-end="opacity-0 translate-y-4 sm:scale-95"
                class="relative inline-flex flex-col align-bottom bg-white dark:bg-slate-900
                       rounded-[2.5rem] text-left shadow-2xl transform
                       sm:my-8 sm:align-middle sm:max-w-lg w-full
                       border border-slate-100 dark:border-slate-800 z-10"
                @click.stop>

                {{-- Header --}}
                <div class="px-10 py-8 pb-4 flex items-center justify-between">
                    <h3 class="text-2xl font-black text-slate-800 dark:text-white font-display tracking-tight">
                        標題
                    </h3>
                    <button @click="showModal = false"
                        class="p-2.5 rounded-full bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-slate-600 transition-all border border-slate-100 dark:border-slate-700">
                        <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
                        </svg>
                    </button>
                </div>

                {{-- Body / Footer --}}
                <div class="px-10 py-6">...</div>
                <div class="px-10 py-6 border-t border-slate-100 dark:border-slate-800/50 flex justify-end gap-4">
                    <button @click="showModal = false" class="btn-luxury-ghost px-8">取消</button>
                    <button type="submit" class="btn-luxury-primary px-12">確認</button>
                </div>
            </div>
        </div>
    </div>
</div>
```

**跨元件開啟 Modal（dropdown 內的按鈕）：**
- 按鈕在 Alpine.js dropdown 內時，需先關閉 dropdown 再發送事件：
  ```html
  <button @click="open = false; $dispatch('open-xxx-modal')">切換帳號</button>
  ```
- Modal 透過 `@open-xxx-modal.window` 接收（`window` 層級才能跨 Alpine scope 傳遞）



## 8. AJAX fetchPage 標準實作

```javascript
async fetchPage(url) {
    if (!url || this.isLoadingTable) return;
    this.isLoadingTable = true;
    try {
        const res = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
        const html = await res.text();
        const doc = new DOMParser().parseFromString(html, 'text/html');
        const newContent = doc.querySelector('#ajax-content-container');
        if (newContent) {
            document.querySelector('#ajax-content-container').innerHTML = newContent.innerHTML;
            window.history.pushState({}, '', url);
            if (window.HSStaticMethods?.autoInit) window.HSStaticMethods.autoInit();
        }
    } catch (e) {
        console.error('AJAX Load Failed:', e);
    } finally {
        this.isLoadingTable = false;
    }
}
```

> **分頁**：`{{ $items->appends(request()->query())->links('vendor.pagination.luxury') }}`

---

## 9. 區塊圖示色彩（詳情／編輯頁）

| 區塊 | 色系 | Class |
|------|------|-------|
| 基本資訊 | 翠綠 Emerald | `bg-emerald-500/10 text-emerald-500` |
| 硬體／插槽 | 琥珀 Amber | `bg-amber-500/10 text-amber-500` |
| 系統／進階 | 靛藍 Indigo | `bg-indigo-500/10 text-indigo-500` |
| 危險／刪除 | 玫瑰 Rose | `bg-rose-500/10 text-rose-500` |
---

## 10. Z-Index 堆疊與遮擋處理 (Z-Index Stacking)

> [!IMPORTANT]
> 當頁面或 Modal 中存在多個垂直排列的 x-searchable-select 時，為防止選單開展時被下一個欄位遮擋，嚴禁使用固定的 z-index。

### 10.1 動態層級提升模式
必須在欄位容器上使用 focus-within:z-[index]，確保正在操作的欄位永遠處於堆疊最前方。

| 使用場景 | 推薦 Class |
|---------|------------|
| 一般表單頁面 | relative focus-within:z-[20] |
| 模態框 (Modal) 內 | relative focus-within:z-[60] |

### 10.2 代碼範例
```html
<div class="space-y-4">
    <!-- 型號選單 -->
    <div class="relative focus-within:z-[60]">
        <label>型號</label>
        <x-searchable-select ... />
    </div>

    <!-- 公司選單 -->
    <div class="relative focus-within:z-[60]">
        <label>公司</label>
        <x-searchable-select ... />
    </div>
</div>
```
