# CSS Patterns / CSS样式模式

Reference file for style-optimizer skill. Contains documented CSS optimization patterns extracted from V12 dashboard development experience.

Last updated: 2026-06-24

---

## Pattern 1: Responsive Breakpoints (XL/LG/MD/SM)

**Purpose**: Mobile-first responsive design with four standard breakpoints.

**CSS Variables**:
```css
:root {
  --breakpoint-xl: 1280px;
  --breakpoint-lg: 1024px;
  --breakpoint-md: 768px;
  --breakpoint-sm: 640px;
}
```

**Usage Pattern**:
```css
/* Mobile-first: Base styles for small screens */
.component {
  padding: 12px;
  font-size: 14px;
}

/* Medium devices (tablets) */
@media (min-width: 640px) {
  .component {
    padding: 16px;
    font-size: 15px;
  }
}

/* Large devices (laptops) */
@media (min-width: 768px) {
  .component {
    padding: 20px;
    font-size: 16px;
  }
}

/* Extra large devices (desktops) */
@media (min-width: 1024px) {
  .component {
    padding: 24px;
    font-size: 17px;
  }
}

/* Ultra-wide screens */
@media (min-width: 1280px) {
  .component {
    padding: 32px;
    font-size: 18px;
  }
}
```

**Key Principles**:
- Start with mobile styles (no media query)
- Layer complexity upward using `min-width`
- Use `rem` or `em` for typography, `px` for borders/spacing

---

## Pattern 2: Mini-Card Grid Layout

**Purpose**: Compact responsive grid for info cards with auto-fit layout.

**CSS Pattern**:
```css
.mini-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 12px;
}

.mini-card {
  background: #FFFFFF;
  border: 1px solid #E5E7EB;
  border-radius: 8px;
  padding: 12px 16px;
  transition: box-shadow 0.2s ease;
}

.mini-card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.mini-card-label {
  font-size: 12px;
  color: #6B7280;
  margin-bottom: 4px;
}

.mini-card-value {
  font-size: 18px;
  font-weight: 700;
  color: #111827;
}
```

**HTML Structure**:
```html
<div class="mini-card-grid">
  <div class="mini-card">
    <div class="mini-card-label">储备项目</div>
    <div class="mini-card-value">23</div>
  </div>
  <div class="mini-card">
    <div class="mini-card-label">评审项目</div>
    <div class="mini-card-value">9</div>
  </div>
</div>
```

**Responsive Behavior**:
- `minmax(130px, 1fr)`: Minimum 130px width, equally distributes remaining space
- `auto-fit`: Automatically adjusts column count based on available space
- Cards reflow seamlessly without explicit breakpoint queries

---

## Pattern 3: CSS Arrow Icons

**Purpose**: Replace emoji icons with pure CSS arrows for consistent cross-platform rendering.

**CSS Pattern**:
```css
.fold-trigger {
  position: relative;
  cursor: pointer;
  user-select: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.fold-trigger::after {
  content: '›';
  display: inline-block;
  font-size: 20px;
  font-weight: 700;
  color: #6B7280;
  transition: transform 0.2s ease;
  transform: rotate(0deg);
  line-height: 1;
}

.fold-trigger.expanded::after {
  transform: rotate(90deg);
}

/* Right-pointing arrow (default) */
.arrow-right::after {
  content: '›';
}

/* Down-pointing arrow */
.arrow-down::after {
  content: '⌄';
}

/* Left-pointing arrow */
.arrow-left::after {
  content: '‹';
}
```

**HTML Usage**:
```html
<button class="fold-trigger" onclick="toggleSection()">
  <span>Expand Section</span>
</button>

<!-- Expanded state -->
<button class="fold-trigger expanded" onclick="toggleSection()">
  <span>Collapse Section</span>
</button>
```

**JavaScript Toggle**:
```javascript
function toggleFold(triggerEl, targetEl) {
  var isExpanded = triggerEl.classList.toggle('expanded');
  targetEl.style.display = isExpanded ? 'block' : 'none';
}
```

**Advantages**:
- No emoji rendering differences across OS/browser
- Smooth CSS transitions (0.2s ease)
- Lightweight (no images/SVG)
- Accessible (screen readers read content text)

---

## Pattern 4: Drawer Panel Styles

**Purpose**: Slide-in right-side panel for detailed content with overlay mask.

**CSS Pattern**:
```css
/* Overlay mask */
.drawer-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.45);
  z-index: 200;
  display: none;
  pointer-events: auto;  /* Critical for mask click handling */
}

.drawer-overlay.active {
  display: block;
}

/* Drawer panel */
.drawer-panel {
  position: fixed;
  top: 0;
  right: 0;
  width: 520px;
  max-width: 90vw;
  height: 100%;
  background: #F8FAFC;
  box-shadow: -4px 0 20px rgba(0, 0, 0, 0.15);
  z-index: 201;
  overflow-y: auto;
  border-radius: 12px 0 0 12px;
  transform: translateX(0);
  transition: transform 0.3s ease;
}

.drawer-panel.hidden {
  transform: translateX(100%);
}

/* Drawer header */
.drawer-header {
  padding: 20px 24px;
  border-bottom: 1px solid #E5E7EB;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #FFFFFF;
}

.drawer-title {
  font-size: 18px;
  font-weight: 700;
  color: #111827;
}

.drawer-close {
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
  color: #6B7280;
  padding: 4px 8px;
}

/* Drawer body */
.drawer-body {
  padding: 24px;
}
```

**HTML Structure**:
```html
<div class="drawer-overlay" id="drawer-overlay"></div>
<div class="drawer-panel hidden" id="drawer-panel">
  <div class="drawer-header">
    <h3 class="drawer-title">Details</h3>
    <button class="drawer-close" onclick="closeDrawer()">×</button>
  </div>
  <div class="drawer-body">
    <!-- Content goes here -->
  </div>
</div>
```

**JavaScript Control**:
```javascript
function openDrawer() {
  document.getElementById('drawer-overlay').classList.add('active');
  document.getElementById('drawer-panel').classList.remove('hidden');
}

function closeDrawer() {
  document.getElementById('drawer-overlay').classList.remove('active');
  document.getElementById('drawer-panel').classList.add('hidden');
}

// Mask click closes drawer
document.getElementById('drawer-overlay').addEventListener('click', closeDrawer);
```

**Key Points**:
- `pointer-events: auto` on overlay ensures mask click always works
- `transform: translateX(100%)` for smooth slide animation
- `border-radius: 12px 0 0 12px` for modern aesthetic
- `overflow-y: auto` for scrollable content when needed

---

## Pattern 5: Chart Container Fixes

**Purpose**: Prevent ECharts overlap and ensure proper rendering.

**CSS Pattern**:
```css
.echarts-chart {
  width: 100%;
  height: 360px;
  min-height: 340px;
  position: relative;
  z-index: 1;
}

/* Chart container wrapper */
.chart-container {
  background: #FFFFFF;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.chart-title {
  font-size: 16px;
  font-weight: 600;
  color: #111827;
  margin-bottom: 16px;
}

.chart-summary {
  margin-top: 12px;
  padding: 12px;
  background: #F8FAFC;
  border-radius: 8px;
  display: flex;
  gap: 16px;
  font-size: 13px;
  color: #374151;
}
```

**HTML Structure**:
```html
<div class="chart-container" data-component="COMP-LINE-01">
  <h3 class="chart-title">投资趋势</h3>
  <div id="chart-line" class="echarts-chart"></div>
  <div class="chart-summary">
    <span>本月投资: 8.5亿</span>
    <span>环比: +12%</span>
    <span>同比: +23%</span>
  </div>
</div>
```

**JavaScript Initialization**:
```javascript
var chartDom = document.getElementById('chart-line');
var myChart = echarts.init(chartDom);
var option = { /* ECharts config */ };
myChart.setOption(option);

// Responsive resize
window.addEventListener('resize', function() {
  myChart.resize();
});
```

**Critical Properties**:
- `min-height`: Prevents zero-height overlap bugs
- `position: relative`: Establishes stacking context
- `z-index: 1`: Ensures proper layer ordering
- `width: 100%`: Full width for responsive behavior

---

## Pattern 6: Time Range Selector UI

**Purpose**: Dual year-month dropdowns for date range selection.

**CSS Pattern**:
```css
.time-range-selector {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
}

.time-range-selector select {
  padding: 6px 10px;
  border: 1px solid #E5E7EB;
  border-radius: 6px;
  background: #FFFFFF;
  font-size: 14px;
  color: #374151;
  cursor: pointer;
  transition: border-color 0.2s ease;
}

.time-range-selector select:hover {
  border-color: #2563EB;
}

.time-range-selector select:focus {
  outline: none;
  border-color: #2563EB;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}

.time-separator {
  color: #6B7280;
  font-size: 13px;
  user-select: none;
}

.data-date {
  font-size: 13px;
  color: #6B7280;
}
```

**HTML Structure**:
```html
<div class="time-range-selector" id="time-range">
  <select id="start-year" aria-label="起始年份">
    <option value="2023">2023</option>
    <option value="2024" selected>2024</option>
    <option value="2025">2025</option>
    <option value="2026">2026</option>
  </select>
  <select id="start-month" aria-label="起始月份">
    <option value="1">1月</option>
    <option value="2">2月</option>
    <option value="3">3月</option>
    <option value="4">4月</option>
    <option value="5">5月</option>
    <option value="6">6月</option>
    <option value="7">7月</option>
    <option value="8">8月</option>
    <option value="9">9月</option>
    <option value="10">10月</option>
    <option value="11">11月</option>
    <option value="12">12月</option>
  </select>
  <span class="time-separator">至</span>
  <select id="end-year" aria-label="截止年份">
    <option value="2024">2024</option>
    <option value="2025">2025</option>
    <option value="2026" selected>2026</option>
    <option value="present">至今</option>
  </select>
  <select id="end-month" aria-label="截止月份">
    <option value="1">1月</option>
    <option value="2">2月</option>
    <option value="3">3月</option>
    <option value="4">4月</option>
    <option value="5">5月</option>
    <option value="6" selected>6月</option>
  </select>
</div>
```

**JavaScript Helpers**:
```javascript
function buildTimeRangeText() {
  var sy = document.getElementById('start-year').value;
  var sm = document.getElementById('start-month').value;
  var ey = document.getElementById('end-year').value;
  var em = document.getElementById('end-month').value;

  var start = sy + '-' + sm;
  var end = ey === 'present' ? '至今' : (ey + '-' + em);
  return start + ' 至 ' + end;
}

function updateDataDateDisplay() {
  var displayEl = document.querySelector('.data-date');
  if (displayEl) {
    displayEl.textContent = '数据截止: ' + buildTimeRangeText();
  }
}

// Listeners
['start-year', 'start-month', 'end-year', 'end-month'].forEach(function(id) {
  var el = document.getElementById(id);
  if (el) {
    el.addEventListener('change', function() {
      updateDataDateDisplay();
      refreshAllData();
    });
  }
});
```

---

## Quick Reference Table

| Pattern | Key CSS Feature | Use Case |
|---------|-----------------|----------|
| Responsive Breakpoints | `@media min-width` | Mobile-first layouts |
| Mini-Card Grid | `grid-template-columns: auto-fit minmax` | Compact card layouts |
| CSS Arrow Icons | `::after` + `transform: rotate()` | Expandable sections |
| Drawer Panel | `transform: translateX()` | Slide-in detail panels |
| Chart Container | `min-height`, `position: relative` | ECharts fix |
| Time Range Selector | Flexbox + `select` styling | Date range input |