---
phase: 02-subtitle-overlay
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - plugins/claude-tts-companion/Sources/claude-tts-companion/SubtitleStyle.swift
  - plugins/claude-tts-companion/Sources/claude-tts-companion/SubtitlePanel.swift
autonomous: true
requirements: [SUB-01, SUB-02, SUB-06, SUB-07, SUB-08, SUB-09, SUB-10, SUB-11]

must_haves:
  truths:
    - "A floating NSPanel appears on the MacBook built-in display"
    - "Panel has dark 30% opacity background with 10px corner radius"
    - "Panel is positioned bottom center, 80px from bottom edge, 70% screen width"
    - "Panel is invisible to screen sharing"
    - "Panel does not steal focus and is click-through"
    - "Panel appears on all Spaces and fullscreen apps"
    - "Text wraps to 2 lines without shrinking"
  artifacts:
    - path: "plugins/claude-tts-companion/Sources/claude-tts-companion/SubtitleStyle.swift"
      provides: "Color, font, layout constants"
      contains: "enum SubtitleStyle"
    - path: "plugins/claude-tts-companion/Sources/claude-tts-companion/SubtitlePanel.swift"
      provides: "NSPanel subclass with all window behaviors"
      contains: "class SubtitlePanel"
  key_links:
    - from: "SubtitlePanel.swift"
      to: "SubtitleStyle.swift"
      via: "uses style constants for colors, fonts, layout"
      pattern: "SubtitleStyle\\."
---

<objective>
Create the subtitle overlay panel with all visual styling and window behavior properties.

Purpose: Establish the visible, non-intrusive floating panel that will display karaoke subtitles. This plan covers the panel shell, styling constants, and all macOS window behaviors (floating, click-through, screen-sharing invisible, all-Spaces).

Output: SubtitleStyle.swift (constants) + SubtitlePanel.swift (NSPanel subclass with NSTextField for text display)
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-subtitle-overlay/02-CONTEXT.md
@plugins/claude-tts-companion/Sources/claude-tts-companion/main.swift
@plugins/claude-tts-companion/Sources/claude-tts-companion/Config.swift
@plugins/claude-tts-companion/Package.swift
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create SubtitleStyle.swift with all visual constants</name>
  <files>plugins/claude-tts-companion/Sources/claude-tts-companion/SubtitleStyle.swift</files>
  <read_first>
    - plugins/claude-tts-companion/Sources/claude-tts-companion/Config.swift (pattern reference for enum style)
    - .planning/phases/02-subtitle-overlay/02-CONTEXT.md (authoritative values)
  </read_first>
  <action>
Create `SubtitleStyle.swift` with an `enum SubtitleStyle` containing all visual constants from CONTEXT.md decisions:

**Colors (NSColor):**

- `static let currentWordColor = NSColor(red: 1.0, green: 0.843, blue: 0.0, alpha: 1.0)` — Gold #FFD700
- `static let pastWordColor = NSColor(red: 0.627, green: 0.627, blue: 0.627, alpha: 1.0)` — Silver-grey #A0A0A0
- `static let futureWordColor = NSColor.white` — White #FFFFFF
- `static let backgroundColor = NSColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.3)` — Black 30% opacity

**Fonts (NSFont):**

- `static let currentWordFont = NSFont.systemFont(ofSize: 28, weight: .bold)` — Bold for current word
- `static let regularFont = NSFont.systemFont(ofSize: 28, weight: .regular)` — Regular for past/future
- Font size presets: `enum FontSize: CGFloat { case small = 22, medium = 28, large = 36 }`

**Layout:**

- `static let cornerRadius: CGFloat = 10`
- `static let horizontalPadding: CGFloat = 16`
- `static let verticalPadding: CGFloat = 12`
- `static let bottomOffset: CGFloat = 80` — 80px from bottom edge
- `static let widthRatio: CGFloat = 0.7` — 70% of screen width
- `static let maxLines = 2`
- `static let lingerDuration: TimeInterval = 2.0` — seconds after final word before clearing

Import only `AppKit`. Use SF Pro Display if available, fall back to system font:

```swift
static func font(size: CGFloat, weight: NSFont.Weight) -> NSFont {
    NSFont(name: "SF Pro Display", size: size).map { NSFontManager.shared.convert($0, toHaveTrait: weight == .bold ? .boldFontMask : .unboldFontMask) } ?? NSFont.systemFont(ofSize: size, weight: weight)
}
```

  </action>
  <verify>
    <automated>cd plugins/claude-tts-companion && swift build 2>&1 | tail -5</automated>
  </verify>
  <acceptance_criteria>
    - File exists at plugins/claude-tts-companion/Sources/claude-tts-companion/SubtitleStyle.swift
    - Contains `enum SubtitleStyle`
    - Contains `currentWordColor` with RGB values matching #FFD700 (red: 1.0, green: 0.843, blue: 0.0)
    - Contains `pastWordColor` with RGB values matching #A0A0A0 (0.627, 0.627, 0.627)
    - Contains `backgroundColor` with alpha: 0.3
    - Contains `cornerRadius: CGFloat = 10`
    - Contains `bottomOffset: CGFloat = 80`
    - Contains `widthRatio: CGFloat = 0.7`
    - Contains `maxLines = 2`
    - Contains `lingerDuration: TimeInterval = 2.0`
    - Contains `FontSize` enum with cases small=22, medium=28, large=36
    - `swift build` succeeds
  </acceptance_criteria>
  <done>SubtitleStyle.swift compiles with all color, font, and layout constants matching CONTEXT.md decisions exactly</done>
</task>

<task type="auto">
  <name>Task 2: Create SubtitlePanel.swift with NSPanel subclass and all window behaviors</name>
  <files>plugins/claude-tts-companion/Sources/claude-tts-companion/SubtitlePanel.swift</files>
  <read_first>
    - plugins/claude-tts-companion/Sources/claude-tts-companion/SubtitleStyle.swift (just created — Task 1)
    - plugins/claude-tts-companion/Sources/claude-tts-companion/main.swift (current entry point)
    - .planning/phases/02-subtitle-overlay/02-CONTEXT.md (window behavior specs)
  </read_first>
  <action>
Create `SubtitlePanel.swift` with `class SubtitlePanel: NSPanel` that sets up the floating overlay.

**NSPanel configuration (in init):**

```swift
// Panel style
super.init(
    contentRect: NSRect(...),  // computed from screen
    styleMask: [.borderless, .nonactivatingPanel],
    backing: .buffered,
    defer: false
)
```

**Window behavior properties:**

- `self.level = .floating` — always on top (SUB-01)
- `self.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]` — all Spaces + fullscreen (SUB-11)
- `self.sharingType = .none` — invisible to screen sharing/capture (SUB-08)
- `self.ignoresMouseEvents = true` — click-through (SUB-10)
- `self.isOpaque = false` — for transparent background
- `self.backgroundColor = .clear` — panel itself is transparent
- `self.hasShadow = false`

**Override focus prevention (SUB-09):**

```swift
override var canBecomeKey: Bool { false }
override var canBecomeMain: Bool { false }
```

**Content view setup:**

- Create a custom `NSView` subclass (or use `NSVisualEffectView` alternative: a plain NSView with `wantsLayer = true`)
- Set `layer!.backgroundColor = SubtitleStyle.backgroundColor.cgColor`
- Set `layer!.cornerRadius = SubtitleStyle.cornerRadius` (10px)
- Set `layer!.masksToBounds = true`

**Text field (NSTextField):**

- Create `private let textField = NSTextField(wrappingLabelField: "")`
- `textField.isEditable = false`
- `textField.isSelectable = false`
- `textField.isBezeled = false`
- `textField.drawsBackground = false`
- `textField.textColor = SubtitleStyle.futureWordColor`
- `textField.font = SubtitleStyle.regularFont`
- `textField.alignment = .center`
- `textField.maximumNumberOfLines = SubtitleStyle.maxLines` (2 lines, SUB-07)
- `textField.lineBreakMode = .byWordWrapping`
- `textField.cell?.wraps = true`
- `textField.cell?.isScrollable = false`
- Use Auto Layout to pin textField with SubtitleStyle padding (16px H, 12px V)

**Positioning (SUB-02 — MacBook built-in display):**
Create a `func positionOnScreen()` method:

- Get `NSScreen.main` (built-in display)
- Calculate panel width: `screen.frame.width * SubtitleStyle.widthRatio`
- Calculate panel height: enough for 2 lines at 28pt + padding (~88px, or calculate dynamically)
- Center horizontally: `x = screen.frame.origin.x + (screen.frame.width - panelWidth) / 2`
- Position from bottom: `y = screen.frame.origin.y + SubtitleStyle.bottomOffset`
- Call `self.setFrame(panelFrame, display: true)`

**Public API (stubs for Plan 02 to implement karaoke):**

- `func show(text: String)` — sets textField.stringValue, calls orderFront(nil), positions panel
- `func hide()` — calls orderOut(nil)
- `func updateAttributedText(_ text: NSAttributedString)` — sets textField.attributedStringValue (for karaoke highlighting in Plan 02)

Import `AppKit`.

Mark as `@MainActor` since all NSPanel operations must be on main thread.
</action>
<verify>
<automated>cd plugins/claude-tts-companion && swift build 2>&1 | tail -5</automated>
</verify>
<acceptance_criteria> - File exists at plugins/claude-tts-companion/Sources/claude-tts-companion/SubtitlePanel.swift - Contains `class SubtitlePanel: NSPanel` - Contains `level = .floating` - Contains `.canJoinAllSpaces` and `.fullScreenAuxiliary` in collectionBehavior - Contains `sharingType = .none` - Contains `ignoresMouseEvents = true` - Contains `canBecomeKey: Bool { false }` and `canBecomeMain: Bool { false }` - Contains `cornerRadius` set to `SubtitleStyle.cornerRadius` - Contains `maximumNumberOfLines` set to `SubtitleStyle.maxLines` - Contains `func show(text: String)` - Contains `func hide()` - Contains `func updateAttributedText` - `swift build` succeeds
</acceptance_criteria>
<done>SubtitlePanel.swift compiles as a floating, click-through, screen-sharing-invisible NSPanel positioned bottom-center on the main display with a 2-line word-wrapping text field</done>
</task>

</tasks>

<verification>
1. `cd plugins/claude-tts-companion && swift build` succeeds with zero errors
2. SubtitleStyle.swift contains all color/font/layout constants matching CONTEXT.md
3. SubtitlePanel.swift contains all NSPanel behavior flags (floating, click-through, sharing-invisible, all-Spaces)
4. No references to `canBecomeKey` returning `true` or `canBecomeMain` returning `true`
</verification>

<success_criteria>

- Both files compile without errors
- Panel has all 8 behavioral requirements configured (SUB-01, SUB-02, SUB-06, SUB-07, SUB-08, SUB-09, SUB-10, SUB-11)
- Style constants are centralized and match CONTEXT.md values exactly
- Public API surface ready for karaoke highlighting in Plan 02
  </success_criteria>

<output>
After completion, create `.planning/phases/02-subtitle-overlay/02-01-SUMMARY.md`
</output>
