docs initial

This commit is contained in:
Daniil
2026-04-06 01:44:58 +03:00
parent 2a344ad588
commit 694b8bc77c
84 changed files with 6922 additions and 298 deletions
@@ -0,0 +1,33 @@
# Codex Team Policy Fixes
Date: 2026-04-05
## Scope
- Fix the `.codex/memories` path convention so the shared rule and per-agent instructions use the same agent IDs.
- Tighten the team-first wording so non-trivial repo work consults the team by default.
- Remove role skill assignments that depend on unavailable review infrastructure.
## Approved Approach
Use a minimal patch:
- Standardize memory paths on the actual Codex agent IDs, which use underscores.
- Change the consultation policy from "before deep analysis" to "before any non-trivial repo task", while keeping a narrow exception for purely mechanical actions and explicit user opt-outs.
- Replace non-executable `requesting-code-review` entries with executable installed skills only.
## Intended Changes
### Memory paths
- Update `.codex/agent-team.md` to state that memories live under `.codex/memories/<agent_id>/`.
- Update every `.codex/agents/*.toml` file to reference underscore-based memory directories matching the agent names.
- Update `.codex/memories/README.md` examples to use `<agent_id>` wording.
### Team-first policy
- Update `AGENTS.md` and `.codex/agent-team.md` to require team consultation before any non-trivial repo task.
- Keep a narrow local-only exception for purely mechanical actions that cannot materially change behavior, architecture, or risk.
### Skill map
- Remove `requesting-code-review` from roles because the required `superpowers:code-reviewer` subagent is not available in this workspace.
- Keep the map limited to executable skills already installed in the current environment.
## Success Criteria
- Shared policy and per-agent instructions point to the same memory paths.
- The root guidance no longer leaves "deep analysis" as the main threshold for consulting the team.
- The skill map contains only practically usable role assignments for this environment.
@@ -0,0 +1,625 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Subtitle Preset Grid Redesign Demo</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Lobster&display=swap" rel="stylesheet">
<style>
/* Catppuccin Mocha - Matching the actual project colors */
:root {
/* Backgrounds */
--bg-canvas: #11111b;
--bg-default: #1e1e2e;
--bg-surface: #313244;
--bg-hover: #45475a;
--bg-default-invert: #eff1f5;
/* Text */
--text-primary: #cdd6f4;
--text-secondary: #bac2de;
--text-tertiary: #9399b2;
/* Borders */
--border-default: #45475a;
--border-subtle: #313244;
/* Purples (accent) */
--purple-400: #cba6f7;
--purple-500: #d9bcfa;
--purple-600: #e4cffc;
--purple-700: #eddfff;
--purple-300: #6a5a93;
--purple-200: #4b4168;
--purple-100: #362f4c;
--purple-50: #2b253b;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(17, 17, 27, 0.5);
--shadow-md: 0 4px 6px -1px rgba(17, 17, 27, 0.58), 0 24px 48px -12px rgba(17, 17, 27, 0.52);
--shadow-lg: 0 10px 15px -3px rgba(17, 17, 27, 0.6), 0 40px 80px -20px rgba(17, 17, 27, 0.7);
/* Accent glow */
--accent-shadow: rgba(203, 166, 247, 0.22);
--accent-shadow-hover: rgba(203, 166, 247, 0.3);
/* Preview background */
--preview-bg: #0c0a1a;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background: var(--bg-canvas);
color: var(--text-primary);
padding: 40px 20px;
min-height: 100vh;
}
body::before {
content: "";
position: fixed;
inset: 0;
background: radial-gradient(circle at 50% 0%, rgba(203, 166, 247, 0.08) 0%, transparent 55%);
pointer-events: none;
z-index: -1;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
font-size: 24px;
font-weight: 600;
margin-bottom: 32px;
color: var(--text-primary);
}
/* Controls */
.controls {
display: flex;
gap: 16px;
margin-bottom: 32px;
flex-wrap: wrap;
}
.control-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.control-group label {
font-size: 11px;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.8px;
font-weight: 500;
}
.aspect-buttons {
display: flex;
gap: 8px;
}
.aspect-btn {
padding: 8px 14px;
background: var(--bg-surface);
border: 1px solid var(--border-default);
color: var(--text-secondary);
border-radius: 8px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: all 0.15s ease;
}
.aspect-btn:hover {
background: var(--bg-hover);
border-color: var(--purple-400);
color: var(--text-primary);
}
.aspect-btn.active {
background: var(--purple-100);
border-color: var(--purple-400);
color: var(--purple-400);
}
/* Grid */
.preset-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 48px;
}
/* Preset Card */
.preset-card {
position: relative;
border-radius: 12px;
overflow: hidden;
background: var(--bg-default);
border: 1px solid var(--border-subtle);
cursor: pointer;
transition: all 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
display: flex;
flex-direction: column;
}
.preset-card:hover {
border-color: var(--purple-400);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.preset-card.selected {
border-color: var(--purple-400);
box-shadow:
0 0 0 1px var(--purple-400),
0 0 20px rgba(203, 166, 247, 0.25),
var(--shadow-lg);
}
.preset-card.selected::before {
content: "";
position: absolute;
inset: -1px;
border-radius: 12px;
padding: 1px;
background: linear-gradient(135deg, var(--purple-400), var(--purple-600));
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
/* Preview Area */
.preview-area {
position: relative;
aspect-ratio: 16 / 9;
background: var(--preview-bg);
overflow: hidden;
transition: aspect-ratio 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.preview-area.vertical {
aspect-ratio: 9 / 16;
}
.preview-area.square {
aspect-ratio: 1 / 1;
}
.preview-area.instagram {
aspect-ratio: 4 / 5;
}
.preview-text {
text-align: center;
padding: 16px;
font-size: 28px;
line-height: 1.4;
z-index: 1;
}
.preview-text .highlight {
font-weight: 700;
}
/* Card Footer */
.card-footer {
padding: 14px 16px;
background: linear-gradient(to top, var(--bg-surface), var(--bg-default));
border-top: 1px solid var(--border-subtle);
}
.preset-name {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 8px;
}
.system-badge {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 2px 8px;
background: var(--purple-100);
color: var(--purple-400);
border-radius: 4px;
}
.style-chars {
font-size: 12px;
color: var(--text-tertiary);
display: flex;
align-items: center;
gap: 8px;
}
.color-dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1);
}
/* Create New Card */
.create-card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
aspect-ratio: 16 / 9;
background: transparent;
border: 2px dashed var(--border-default);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
color: var(--text-tertiary);
min-height: 100%;
}
.create-card:hover {
border-color: var(--purple-400);
background: rgba(203, 166, 247, 0.05);
color: var(--text-secondary);
}
.create-card svg {
width: 32px;
height: 32px;
opacity: 0.6;
}
/* Skeleton Loading - Improved */
.skeleton-card {
border-radius: 12px;
overflow: hidden;
background: var(--bg-default);
border: 1px solid var(--border-subtle);
display: flex;
flex-direction: column;
}
.skeleton-preview {
aspect-ratio: 16 / 9;
background: var(--bg-surface);
position: relative;
overflow: hidden;
}
.skeleton-preview::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
90deg,
transparent 0%,
rgba(203, 166, 247, 0.08) 50%,
transparent 100%
);
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.skeleton-footer {
padding: 14px 16px;
background: linear-gradient(to top, var(--bg-surface), var(--bg-default));
border-top: 1px solid var(--border-subtle);
display: flex;
flex-direction: column;
gap: 10px;
}
.skeleton-line {
height: 14px;
background: var(--bg-hover);
border-radius: 4px;
width: 60%;
position: relative;
overflow: hidden;
}
.skeleton-line.short {
width: 40%;
height: 10px;
}
.skeleton-line::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
90deg,
transparent 0%,
rgba(203, 166, 247, 0.06) 50%,
transparent 100%
);
animation: shimmer 1.5s infinite;
}
/* Section Label */
.section-label {
font-size: 11px;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.8px;
margin-bottom: 12px;
font-weight: 500;
}
/* Selected indicator */
.selected-indicator {
position: absolute;
top: 8px;
right: 8px;
width: 20px;
height: 20px;
background: var(--purple-400);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(203, 166, 247, 0.4);
}
.selected-indicator svg {
width: 12px;
height: 12px;
color: var(--bg-canvas);
}
/* Responsive */
@media (max-width: 768px) {
.preset-grid {
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>Выбор пресета субтитров</h1>
<!-- Controls -->
<div class="controls">
<div class="control-group">
<label>Аспектное соотношение видео</label>
<div class="aspect-buttons">
<button class="aspect-btn active" data-ratio="16:9">16:9 (Широкое)</button>
<button class="aspect-btn" data-ratio="9:16">9:16 (Вертикальное)</button>
<button class="aspect-btn" data-ratio="1:1">1:1 (Квадрат)</button>
<button class="aspect-btn" data-ratio="4:5">4:5 (Instagram)</button>
</div>
</div>
</div>
<!-- Section: Ready Presets -->
<div class="section-label">Пример: готовые пресеты</div>
<div class="preset-grid" id="presetGrid">
<!-- Card 1: Классические -->
<div class="preset-card selected" data-preset="classic">
<div class="preview-area">
<div class="preview-text" style="font-family: 'Lobster', cursive; color: #ffffff;">
Пример <span class="highlight" style="color: #FFD700;">субтитров</span>
</div>
<div class="selected-indicator">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</div>
</div>
<div class="card-footer">
<div class="preset-name">
Классические
<span class="system-badge">Системный</span>
</div>
<div class="style-chars">
Lobster
<span style="color: var(--border-default);">·</span>
<span class="color-dot" style="background: #FFD700;"></span>
<span style="color: #FFD700;">Золотой</span>
</div>
</div>
</div>
<!-- Card 2: Неон -->
<div class="preset-card" data-preset="neon">
<div class="preview-area">
<div class="preview-text" style="font-family: 'Inter', sans-serif; font-weight: 700; color: #ffffff; text-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff;">
Пример <span class="highlight" style="color: #00ffff;">субтитров</span>
</div>
</div>
<div class="card-footer">
<div class="preset-name">
Неон
<span class="system-badge">Системный</span>
</div>
<div class="style-chars">
Inter Bold
<span style="color: var(--border-default);">·</span>
<span class="color-dot" style="background: #00ffff; box-shadow: 0 0 6px #00ffff;"></span>
<span style="color: #00ffff;">Неоновый</span>
</div>
</div>
</div>
<!-- Card 3: Минимализм -->
<div class="preset-card" data-preset="minimal">
<div class="preview-area">
<div class="preview-text" style="font-family: 'Inter', sans-serif; font-weight: 400; color: #e0e0e0; font-size: 24px;">
Пример <span class="highlight" style="color: #ffffff; font-weight: 500;">субтитров</span>
</div>
</div>
<div class="card-footer">
<div class="preset-name">
Минимализм
<span class="system-badge">Системный</span>
</div>
<div class="style-chars">
Inter Regular
<span style="color: var(--border-default);">·</span>
<span class="color-dot" style="background: #ffffff;"></span>
<span style="color: #ffffff;">Белый</span>
</div>
</div>
</div>
<!-- Card 4: Жирный -->
<div class="preset-card" data-preset="bold">
<div class="preview-area">
<div class="preview-text" style="font-family: 'Inter', sans-serif; font-weight: 900; color: #ffffff; -webkit-text-stroke: 2px #000000; font-size: 32px;">
Пример <span class="highlight" style="color: #ff006e;">субтитров</span>
</div>
</div>
<div class="card-footer">
<div class="preset-name">
Жирный
<span class="system-badge">Системный</span>
</div>
<div class="style-chars">
Inter Black
<span style="color: var(--border-default);">·</span>
<span class="color-dot" style="background: #ff006e;"></span>
<span style="color: #ff006e;">Розовый</span>
</div>
</div>
</div>
<!-- Create New Card -->
<div class="create-card">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
<span style="font-size: 14px; font-weight: 500;">Создать пресет</span>
</div>
</div>
<!-- Section: Loading State -->
<div class="section-label">Пример: состояние загрузки</div>
<div class="preset-grid">
<div class="skeleton-card">
<div class="skeleton-preview"></div>
<div class="skeleton-footer">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
</div>
<div class="skeleton-card">
<div class="skeleton-preview"></div>
<div class="skeleton-footer">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
</div>
<div class="skeleton-card">
<div class="skeleton-preview"></div>
<div class="skeleton-footer">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
</div>
<div class="skeleton-card">
<div class="skeleton-preview"></div>
<div class="skeleton-footer">
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
</div>
</div>
</div>
<script>
// Aspect ratio switching
const aspectButtons = document.querySelectorAll('.aspect-btn');
const previewAreas = document.querySelectorAll('.preview-area');
const skeletonPreviews = document.querySelectorAll('.skeleton-preview');
const createCard = document.querySelector('.create-card');
aspectButtons.forEach(btn => {
btn.addEventListener('click', () => {
// Update active button
aspectButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const ratio = btn.dataset.ratio;
// Update preview areas
previewAreas.forEach(preview => {
preview.classList.remove('vertical', 'square', 'instagram');
if (ratio === '9:16') preview.classList.add('vertical');
if (ratio === '1:1') preview.classList.add('square');
if (ratio === '4:5') preview.classList.add('instagram');
});
// Update skeleton previews
skeletonPreviews.forEach(preview => {
if (ratio === '9:16') preview.style.aspectRatio = '9 / 16';
else if (ratio === '1:1') preview.style.aspectRatio = '1 / 1';
else if (ratio === '4:5') preview.style.aspectRatio = '4 / 5';
else preview.style.aspectRatio = '16 / 9';
});
// Update create card
if (ratio === '9:16') createCard.style.aspectRatio = '9 / 16';
else if (ratio === '1:1') createCard.style.aspectRatio = '1 / 1';
else if (ratio === '4:5') createCard.style.aspectRatio = '4 / 5';
else createCard.style.aspectRatio = '16 / 9';
});
});
// Card selection
const presetCards = document.querySelectorAll('.preset-card');
presetCards.forEach(card => {
card.addEventListener('click', () => {
presetCards.forEach(c => {
c.classList.remove('selected');
const indicator = c.querySelector('.selected-indicator');
if (indicator) indicator.remove();
});
card.classList.add('selected');
// Add checkmark indicator
const preview = card.querySelector('.preview-area');
if (!preview.querySelector('.selected-indicator')) {
const indicator = document.createElement('div');
indicator.className = 'selected-indicator';
indicator.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
`;
preview.appendChild(indicator);
}
});
});
</script>
</body>
</html>
@@ -0,0 +1,212 @@
# Subtitle Preset Grid Redesign - Design Document
**Date:** 2026-04-06
**Scope:** Redesign preset preview cards in Caption Settings step to match uploaded video aspect ratio with modern visual refresh
---
## Overview
Redesign the subtitle preset selection grid to:
1. Display preset previews with the **same aspect ratio as the uploaded video**
2. Apply a **modern visual refresh** consistent with the app's design language
3. Show **style characteristics** (font, colors) as subtle hints
4. Maintain **responsive layout** across screen sizes
---
## Core Functionality
### Dynamic Aspect Ratio
**Data Flow:**
1. Fetch video metadata via `GET /api/media/mediafiles/{media_file_id}/` using `primaryFileId` from WizardContext
2. Extract `width` and `height` from `MediaFileRead` response
3. Calculate aspect ratio: `width / height`
4. Apply as CSS `aspect-ratio` to preset cards via inline style or CSS variable
5. Handle loading state while fetching metadata
6. Fallback to 16:9 if no video is uploaded or API error occurs
**Implementation Notes:**
- Store aspect ratio in WizardContext alongside other video metadata
- Update ratio when `primaryFileId` changes
- Cards use container queries for responsive sizing
---
## Visual Design (5 Pillars Applied)
### 1. Typography with Character
- Keep existing font system (consistent with app)
- Style name: `font-weight: 500`, `font-size: 14px`
- Characteristic labels: `font-size: 12px`, muted color (`--gray-10`)
### 2. Committed Color & Theme
- Uses **Catppuccin Mocha** palette matching the project:
- Canvas: `--bg-canvas: #11111b`
- Cards: `--bg-default: #1e1e2e`
- Surfaces: `--bg-surface: #313244`
- Borders: `--border-default: #45475a`, `--border-subtle: #313244`
- Text: `--text-primary: #cdd6f4`, `--text-secondary: #bac2de`, `--text-tertiary: #9399b2`
- Selected state: purple accent (`--purple-400: #cba6f7`) with glow shadow
- Card hover: border transitions to purple accent
- System badge: purple-100 background with purple-400 text
- Checkmark indicator on selected card (top-right corner)
### 3. Purposeful Motion
- Cards fade in with staggered animation (50ms delay per card)
- Smooth border-color transition on hover (150ms ease)
- Selection change: immediate border color change
- Loading skeleton: shimmer animation
### 4. Brave Spatial Composition
- CSS Grid with `auto-fill` and `minmax(200px, 1fr)`
- Consistent 16px gap between cards
- Cards maintain video aspect ratio without stretching
- Responsive: more columns on wide screens, fewer on narrow
### 5. Atmosphere & Depth
- Card background: subtle gradient overlay for depth
- Selected card: elevated with `box-shadow` + accent glow
- Dark preview background (`#0c0a1a`) preserved from existing StylePreview
- Rounded corners: `border-radius: 12px`
---
## Component Structure
### PresetCard
```
┌──────────────────────────────────────┐
│ │
│ [StylePreview Component] │ ← Dynamic aspect-ratio
│ "Пример субтитров" │ based on video
│ │
├──────────────────────────────────────┤
│ Style Name [Системный] │ ← Footer
│ Lobster · Yellow accent │ ← Characteristics (subtle)
└──────────────────────────────────────┘
```
**Props:**
- `preset: CaptionPresetRead`
- `isSelected: boolean`
- `aspectRatio: number` (width/height, e.g., 1.777 for 16:9)
- `onSelect: () => void`
- `onEdit: () => void`
- `onDelete: () => void`
### StylePreview Updates
**New Props:**
- `aspectRatio?: number` - overrides default 9/16
**Behavior:**
- Uses passed `aspectRatio` for container sizing
- Falls back to 9/16 if not provided
- Maintains all existing text styling logic
### PresetGrid Updates
**New Behavior:**
- Fetches video metadata via `useVideoMetadata()` hook
- Passes `aspectRatio` to all PresetCard children
- Shows skeleton loading state while fetching
- Responsive grid layout
---
## Style Characteristics Display
Each card footer shows:
- **Font family** (e.g., "Lobster", "Inter") - extracted from `preset.style_config.text.font_family`
- **Accent color** - small color dot + name if distinct from default
- Hidden on cards narrower than 180px (responsive)
**Format:**
```
{font_family} · {accent_color_name}
```
Example: `Lobster · Желтый` or `Inter · Неоновый`
---
## Loading State
**Skeleton Card:**
- Same aspect ratio as target (default 16:9 while loading)
- Shimmer animation on preview area
- Gray placeholder for text
- 4-6 skeleton cards shown while loading
---
## Responsive Behavior
| Screen Width | Grid Columns | Card Min Width |
|--------------|--------------|----------------|
| < 480px | 2 | 140px |
| 480-768px | 3 | 160px |
| 768-1200px | 4 | 180px |
| > 1200px | 5-6 | 200px |
---
## API Integration
### New Hook: `useVideoMetadata`
```typescript
function useVideoMetadata(fileId: string | null) {
return api.useQuery(
"get",
"/api/media/mediafiles/{media_file_id}/",
{ params: { path: { media_file_id: fileId ?? "" } } },
{ enabled: !!fileId }
)
}
```
### Aspect Ratio Calculation
```typescript
const aspectRatio = useMemo(() => {
if (!mediaFile?.width || !mediaFile?.height) return 16 / 9
return mediaFile.width / mediaFile.height
}, [mediaFile])
```
---
## Edge Cases
1. **No video uploaded:** Fall back to 16:9 aspect ratio
2. **Video metadata unavailable:** Show error toast, fall back to 16:9
3. **Very wide video (>21:9):** Cap max card width to prevent overflow
4. **Very tall video (9:16+):** Limit max height, allow scrolling if needed
5. **No presets:** Show empty state with "Создать пресет" card only
---
## Files Modified
1. `src/features/project/CaptionSettingsStep/PresetGrid.tsx` - Grid logic, aspect ratio distribution
2. `src/features/project/CaptionSettingsStep/PresetGrid.module.scss` - Grid styles, responsive layout
3. `src/features/project/CaptionSettingsStep/StylePreview.tsx` - Accept aspect ratio prop
4. `src/features/project/CaptionSettingsStep/StylePreview.module.scss` - Dynamic sizing
5. `src/features/project/CaptionSettingsStep/useVideoMetadata.ts` - New hook (or inline in PresetGrid)
---
## Acceptance Criteria
- [ ] Preset cards display with uploaded video's aspect ratio
- [ ] Grid is responsive and works on mobile/desktop
- [ ] Loading state shows skeleton cards
- [ ] Style characteristics (font, color) visible on cards
- [ ] Selected state clearly visible with accent border
- [ ] Hover effects smooth and purposeful
- [ ] Fallback to 16:9 when no video available
- [ ] All existing functionality preserved (select, edit, delete, create)