<script setup lang="ts">
/**
 * TaskBoardView – Linear-style Kanban Board
 * Galaxy/Dashboard V2 styled edition.
 *
 * 5 columns: Offen, In Bearbeitung, Review, Blockiert, Erledigt
 * HTML5 Drag & Drop (no external lib)
 *
 * Agent-Workflow Features:
 *   - Agent tasks use the Lucide Bot badge
 *   - ExpectedFrom field shows who is expected to act next
 *   - Stale-task warning banner at top (InProgress > 2h)
 *   - Waiting section for Iris overview
 */
import { computed, onBeforeUnmount, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import {
  AlertTriangle,
  Bot,
  CalendarDays,
  Clock3,
  CornerDownRight,
  ExternalLink,
  Eye,
  Hourglass,
  Link2,
  ListChecks,
  MessageSquareText,
  Plus,
  Save,
  ServerCog,
  ShieldBan,
  X,
} from '@lucide/vue'
import { RouterLink, useRouter } from 'vue-router'
import { useOpenClawOverviewQuery } from '../api/openclawRuntime'
import { useTaskBoard } from '../api/taskBoard'
import AsyncStatePanel from '../components/mission-control/AsyncStatePanel.vue'
import { useTaskActivity, useTaskChildren } from '../api/tasks'
import { subscribeDomainEventState } from '../services/domainEvents'
import type { SseConnectionState } from '../services/sseHub'
import { useAuthStore } from '../stores/auth'
import {
  buildTaskAgentOptions,
  taskAgentLabel,
} from '../utils/taskAgentOptions'

type BoardTask = ReturnType<typeof flattenBoard>[number]

type TaskFormState = {
  title: string
  detail: string
  priority: string
  assignedTo: string
  state: string
  dueDate: string
}

const authStore = useAuthStore()
const router = useRouter()
const overviewQuery = useOpenClawOverviewQuery()
const taskBoard = useTaskBoard(50)
const board = taskBoard.board
const domainLiveState = ref<SseConnectionState>('connecting')
const showCreateModal = ref(false)
const showDetailPanel = ref(false)
const showIrisPanel = ref(false)
const dragOverColumn = ref<string | null>(null)
const selectedTaskId = ref<string | null>(null)
const detailSaving = ref(false)
const detailError = ref('')
const detailSuccess = ref('')
const selectedTaskQueryId = computed(() => selectedTaskId.value ?? '')
const quickPeekEnabled = computed(() => showDetailPanel.value && Boolean(selectedTaskId.value))
const quickPeekChildrenQuery = useTaskChildren(selectedTaskQueryId, quickPeekEnabled)
const quickPeekActivityQuery = useTaskActivity(selectedTaskQueryId, quickPeekEnabled)
const childTasks = computed(() => quickPeekChildrenQuery.data.value ?? [])
const taskActivity = computed(() => quickPeekActivityQuery.data.value ?? [])
const detailLoading = computed(() =>
  quickPeekChildrenQuery.isPending.value || quickPeekActivityQuery.isPending.value,
)

/* ── Create Task Form ───────────────────────────── */
const formTitle = ref('')
const formDetail = ref('')
const formPriority = ref('Medium')
const formAssignedTo = ref('bao')
const formSubmitting = ref(false)
const formError = ref('')

const detailForm = reactive<TaskFormState>({
  title: '',
  detail: '',
  priority: 'Medium',
  assignedTo: 'bao',
  state: 'Backlog',
  dueDate: '',
})

function resetForm() {
  formTitle.value = ''
  formDetail.value = ''
  formPriority.value = 'Medium'
  formAssignedTo.value = 'bao'
  formError.value = ''
}

async function handleCreateTask() {
  if (!formTitle.value.trim()) {
    formError.value = 'Titel ist erforderlich'
    return
  }
  formSubmitting.value = true
  formError.value = ''
  try {
    await taskBoard.createTask({
      title: formTitle.value.trim(),
      detail: formDetail.value.trim() || null,
      priority: formPriority.value,
      assignedTo: formAssignedTo.value,
    })
    showCreateModal.value = false
    resetForm()
  } catch (_err) {
    formError.value = 'Fehler beim Erstellen der Aufgabe'
  } finally {
    formSubmitting.value = false
  }
}

/* ── Drag & Drop ────────────────────────────────── */
const draggedTaskId = ref<string | null>(null)

function onDragStart(e: DragEvent, taskId: string) {
  if (!canChangeState.value) {
    e.preventDefault()
    return
  }
  if (!e.dataTransfer) return
  draggedTaskId.value = taskId
  e.dataTransfer.effectAllowed = 'move'
  e.dataTransfer.setData('text/plain', taskId)
  const el = e.currentTarget as HTMLElement | null
  el?.classList.add('dragging')
}

function onDragEnd(e: DragEvent) {
  draggedTaskId.value = null
  dragOverColumn.value = null
  const el = e.currentTarget as HTMLElement | null
  el?.classList.remove('dragging')
}

function onDragOver(e: DragEvent, column: string) {
  if (!canChangeState.value) return
  e.preventDefault()
  if (!e.dataTransfer) return
  e.dataTransfer.dropEffect = 'move'
  dragOverColumn.value = column
}

function onDragLeave(_e: DragEvent) {
  dragOverColumn.value = null
}

async function onDrop(e: DragEvent, targetState: string) {
  if (!canChangeState.value) return
  e.preventDefault()
  dragOverColumn.value = null
  const taskId = e.dataTransfer?.getData('text/plain')
  if (!taskId) return

  await taskBoard.moveTask(taskId, targetState)
}

/* ── Helpers ──────────────────────────────────────── */
function priorityLabel(p: string): string {
  const lower = p.toLowerCase()
  if (lower === 'high') return 'High'
  if (lower === 'low') return 'Low'
  return 'Med'
}

function priorityColor(p: string): string {
  const lower = p.toLowerCase()
  if (lower === 'high') return 'var(--st-block)'
  if (lower === 'low') return 'var(--a-cyan)'
  return 'var(--st-queue)'
}

function statusTone(state: string): string {
  switch (state.toLowerCase()) {
    case 'done': return 'is-done'
    case 'blocked': return 'is-blocked'
    case 'review': return 'is-review'
    case 'in progress': return 'is-progress'
    default: return 'is-backlog'
  }
}

function stateLabel(state: string): string {
  return state === 'Backlog' ? 'Offen' : state
}

function formatDate(date?: string | null, withTime = false): string {
  if (!date) return '—'
  return new Date(date).toLocaleString('de-DE', withTime
    ? { dateStyle: 'medium', timeStyle: 'short' }
    : { dateStyle: 'medium' })
}

function toDateInputValue(date?: string | null): string {
  if (!date) return ''
  return new Date(date).toISOString().slice(0, 10)
}

function flattenBoard() {
  return [
    ...board.value.offen,
    ...board.value.inProgress,
    ...board.value.review,
    ...board.value.blocked,
    ...board.value.done,
  ]
}

const allBoardTasks = computed(() => flattenBoard())
const taskAgentOptions = computed(() => buildTaskAgentOptions(
  overviewQuery.data.value?.agents.items ?? [],
  [
    formAssignedTo.value,
    detailForm.assignedTo,
    ...allBoardTasks.value.flatMap(task => [task.assignedTo, task.expectedFrom]),
  ],
))
const selectedTask = computed(() => allBoardTasks.value.find(task => task.id === selectedTaskId.value) ?? null)
const canSaveDetail = computed(() => detailForm.title.trim().length > 0 && !detailSaving.value)

/**
 * Policy: Iris und Bao dürfen Status ändern / verschieben.
 * Wenn der aktuelle Web-UI-User weder Iris noch Bao ist, werden
 * Drag & Drop und die Status-Dropdowns deaktiviert.
 */
const canChangeState = computed(() => authStore.isIris || authStore.isBao)

function hydrateDetailForm(task: BoardTask | null) {
  detailError.value = ''
  detailSuccess.value = ''
  if (!task) return
  detailForm.title = task.title
  detailForm.detail = task.detail ?? ''
  detailForm.priority = task.priority || 'Medium'
  detailForm.assignedTo = task.assignedTo || ''
  detailForm.state = task.state || 'Backlog'
  detailForm.dueDate = toDateInputValue(task.dueDate)
}

/* ── Iris Panel helpers ─────────────────────────── */
const activeAgentTasks = computed(() => [
  ...board.value.offen,
  ...board.value.inProgress,
  ...board.value.review,
  ...board.value.blocked,
].filter(task => task.isAgentTask))
const waitingForIrisTasks = computed(() => activeAgentTasks.value.filter(task =>
  task.expectedFrom?.toLowerCase() === 'iris',
))
const waitingForBaoTasks = computed(() => activeAgentTasks.value.filter(task =>
  task.expectedFrom?.toLowerCase() === 'bao',
))
const waitingForOthersTasks = computed(() => activeAgentTasks.value.filter(task => {
  const expected = task.expectedFrom?.toLowerCase()
  return Boolean(expected && expected !== 'iris' && expected !== 'bao')
}))
const staleTasks = computed(() => {
  const threshold = Date.now() - (2 * 60 * 60 * 1000)
  return activeAgentTasks.value.filter(task =>
    task.state.toLowerCase() === 'in progress'
      && new Date(task.updatedAt).getTime() < threshold,
  )
})
const staleCount = computed(() => staleTasks.value.length)
const waitingForIrisCount = computed(() => waitingForIrisTasks.value.length)
const waitingForBaoCount = computed(() => waitingForBaoTasks.value.length)
const liveModeLabel = computed(() => {
  if (domainLiveState.value === 'open') return 'Live'
  if (domainLiveState.value === 'connecting' || domainLiveState.value === 'reconnecting') return 'Verbinde…'
  if (domainLiveState.value === 'unsupported') return 'Polling-Fallback'
  return 'Offline'
})
const liveModeClass = computed(() => `live-pill-${domainLiveState.value === 'open' ? 'healthy' : domainLiveState.value === 'unsupported' ? 'polling' : 'degraded'}`)
const openClawActiveTasks = computed(() =>
  overviewQuery.data.value?.tasks.items
    .filter(item => item.status === 'queued' || item.status === 'running').length ?? 0,
)
const openClawActiveSessions = computed(() =>
  overviewQuery.data.value?.sessions.items
    .filter(item => item.status === 'running' || item.status === 'active').length ?? 0,
)
const openClawApprovals = computed(() =>
  overviewQuery.data.value?.approvals.items
    .filter(item => item.status === 'pending').length ?? 0,
)

function expectedFromLabel(expected: string | null | undefined): string {
  return taskAgentLabel(expected, taskAgentOptions.value)
}

function hoursSince(dateStr: string): number {
  const now = Date.now()
  const then = new Date(dateStr).getTime()
  return Math.round((now - then) / 3600000)
}

function relativeTime(date?: string | null): string {
  if (!date) return 'keine Updates'
  const diffMs = Date.now() - new Date(date).getTime()
  const mins = Math.max(0, Math.round(diffMs / 60000))
  if (mins < 1) return 'gerade eben'
  if (mins < 60) return `vor ${mins} min`
  const hours = Math.round(mins / 60)
  if (hours < 24) return `vor ${hours} h`
  const days = Math.round(hours / 24)
  return `vor ${days} d`
}

function childStatusSummary(taskId: string): string {
  const children = allBoardTasks.value.filter(task => task.parentTaskId === taskId)
  if (!children.length) return ''

  const counts = {
    inProgress: children.filter(task => task.state === 'In progress').length,
    review: children.filter(task => task.state === 'Review').length,
    blocked: children.filter(task => task.state === 'Blocked').length,
    done: children.filter(task => task.state === 'Done').length,
  }

  const parts = [] as string[]
  if (counts.inProgress) parts.push(`${counts.inProgress} in Arbeit`)
  if (counts.review) parts.push(`${counts.review} im Review`)
  if (counts.blocked) parts.push(`${counts.blocked} blockiert`)
  if (counts.done) parts.push(`${counts.done} erledigt`)
  return parts.length ? `Child-Tasks: ${parts.join(' · ')}` : `Child-Tasks: ${children.length}`
}

function activityHint(task: BoardTask): string {
  const childSummary = childStatusSummary(task.id)
  if (childSummary) return childSummary

  return task.lastActivityMessage?.trim()
    || (task.expectedFrom ? `Wartet auf ${expectedFromLabel(task.expectedFrom)}` : 'Noch kein relevanter Progress-Status')
}

function hasChildTasks(taskId: string): boolean {
  return allBoardTasks.value.some(task => task.parentTaskId === taskId)
}

function delegationBadge(task: BoardTask): string | null {
  if (task.childTaskCount && task.openChildTaskCount) return `${task.openChildTaskCount}/${task.childTaskCount} aktiv`
  if (task.childTaskCount) return `${task.childTaskCount} Child-Tasks`
  if (task.parentTaskId) return 'Child-Task'
  if (task.isAgentTask || task.hasVisibleDelegation) return 'delegiert'
  return null
}

function assigneeLabel(assignedTo: string | null | undefined): string {
  return expectedFromLabel(assignedTo)
}

function assigneeClass(assignedTo: string | null | undefined): string {
  if (!assignedTo) return ''
  const lower = assignedTo.toLowerCase()
  if (lower === 'iris') return 'assignee-iris'
  if (lower === 'bao') return 'assignee-bao'
  return 'assignee-agent'
}

/* ── Task Navigation ───────────────────────────── */
function navigateToTask(taskId: string) {
  router.push('/tasks/' + taskId)
}

function openQuickPeek(taskId: string) {
  selectedTaskId.value = taskId
  showDetailPanel.value = true
}

function handleCardClick(event: MouseEvent, taskId: string) {
  if (event.ctrlKey || event.metaKey || event.shiftKey) {
    event.preventDefault()
    openQuickPeek(taskId)
  } else {
    navigateToTask(taskId)
  }
}

function closeDetailPanel() {
  showDetailPanel.value = false
  selectedTaskId.value = null
  detailError.value = ''
  detailSuccess.value = ''
}

async function saveTaskDetail() {
  if (!selectedTask.value || !detailForm.title.trim()) return

  detailSaving.value = true
  detailError.value = ''
  detailSuccess.value = ''

  try {
    await taskBoard.updateTask(selectedTask.value.id, {
      title: detailForm.title.trim(),
      detail: detailForm.detail.trim() || null,
      priority: detailForm.priority,
      assignedTo: detailForm.assignedTo || null,
      dueDate: detailForm.dueDate || null,
    })

    // Nur Iris/Bao darf Status ändern
    if (canChangeState.value && detailForm.state !== selectedTask.value.state) {
      await taskBoard.moveTask(selectedTask.value.id, detailForm.state)
    } else if (detailForm.state !== selectedTask.value.state) {
      detailForm.state = selectedTask.value.state // revert state change in UI
    }

    detailSuccess.value = 'Änderungen gespeichert'
    await Promise.all([
      quickPeekChildrenQuery.refetch(),
      quickPeekActivityQuery.refetch(),
    ])
  } catch (_err) {
    detailError.value = 'Änderungen konnten nicht gespeichert werden'
  } finally {
    detailSaving.value = false
  }
}

function onGlobalKeydown(event: KeyboardEvent) {
  if (event.key === 'Escape' && showDetailPanel.value) {
    closeDetailPanel()
  }
}

watch(selectedTask, (task) => {
  hydrateDetailForm(task)
})

watch(showDetailPanel, (open) => {
  document.body.style.overflow = open ? 'hidden' : ''
})

/* ── Lifecycle ────────────────────────────────────── */
let unsubscribeDomainState: (() => void) | null = null

onMounted(() => {
  unsubscribeDomainState = subscribeDomainEventState(next => {
    domainLiveState.value = next
  })
  window.addEventListener('keydown', onGlobalKeydown)
})

onBeforeUnmount(() => {
  document.body.style.overflow = ''
})

onUnmounted(() => {
  unsubscribeDomainState?.()
  window.removeEventListener('keydown', onGlobalKeydown)
})
</script>

<template>
  <div class="board-wrap nexus-page nexus-page--workspace">
    <div class="board-header">
      <div>
        <h1><span class="grad-text">Aufgaben</span></h1>
        <p class="board-subtitle">Task Board — Übersicht aller Arbeitspakete</p>
        <div class="board-live-row">
          <span class="live-pill" :class="liveModeClass">{{ liveModeLabel }}</span>
          <span class="live-meta">Revision {{ taskBoard.revision.value }}</span>
        </div>
      </div>
      <div class="board-header-actions">
        <button
          v-if="staleCount > 0 || waitingForIrisCount > 0"
          type="button"
          class="iris-panel-btn"
          @click="showIrisPanel = !showIrisPanel"
        >
          <Eye :size="14" />
          Iris-Blick
          <span v-if="waitingForIrisCount > 0" class="panel-badge iris-badge">{{ waitingForIrisCount }}</span>
          <span v-if="staleCount > 0" class="panel-badge stale-badge">{{ staleCount }}</span>
        </button>
        <button type="button" class="create-btn" @click="showCreateModal = true">
          <Plus :size="16" />
          Neue Aufgabe
        </button>
      </div>
    </div>

    <section class="runtime-strip nexus-panel" aria-label="OpenClaw runtime summary">
      <div class="runtime-strip__identity">
        <ServerCog :size="17" aria-hidden="true" />
        <div>
          <strong>OpenClaw runtime</strong>
          <span>
            {{ overviewQuery.data.value?.connection.connected
              ? 'Connected control plane'
              : overviewQuery.data.value?.connection.state || 'Status pending' }}
          </span>
        </div>
      </div>
      <div class="runtime-strip__metrics">
        <span><strong>{{ openClawActiveTasks }}</strong> active tasks</span>
        <span><strong>{{ openClawActiveSessions }}</strong> sessions</span>
        <span><strong>{{ openClawApprovals }}</strong> approvals</span>
      </div>
      <RouterLink class="nexus-button" to="/runs">
        Inspect runtime
        <ExternalLink :size="13" aria-hidden="true" />
      </RouterLink>
    </section>

    <!-- Status-Change Permission Banner -->
    <div v-if="!canChangeState" class="permission-banner">
      <ShieldBan :size="14" />
      <span><strong>Nur-Lesen-Status.</strong> Du kannst Aufgaben inhaltlich bearbeiten (Titel, Beschreibung, Priorität, Zuständigkeit), aber das Verschieben/Status-Ändern ist <strong>nur Iris und Bao</strong> vorbehalten.</span>
    </div>

    <!-- Stale Warning Banner -->
    <div v-if="staleCount > 0" class="stale-banner">
      <AlertTriangle :size="14" />
      <span><strong>{{ staleCount }} Task(s)</strong> sind stale (In Bearbeitung &gt; 2h ohne Update).</span>
      <button type="button" class="stale-dismiss" @click="showIrisPanel = true">Ansehen</button>
    </div>

    <!-- Iris Overview Panel (collapsible) -->
    <div v-if="showIrisPanel" class="iris-panel">
      <div class="iris-panel-header">
        <h3><Bot :size="16" /> Iris — Worauf warte ich?</h3>
        <button type="button" class="modal-close" aria-label="Iris overview schließen" @click="showIrisPanel = false"><X :size="16" /></button>
      </div>
      <div class="iris-panel-grid">
        <section class="iris-section">
          <div class="iris-section-title">
            <span class="section-dot iris-dot"></span>
            Warte auf Iris <span class="section-count">{{ waitingForIrisCount }}</span>
          </div>
          <div v-if="waitingForIrisTasks.length === 0" class="iris-empty">Keine Tasks</div>
          <div v-for="t in waitingForIrisTasks" :key="t.id" class="iris-task-row progress-row">
            <div>
              <span class="iris-task-title">{{ t.title }}</span>
              <div class="iris-task-progress">{{ activityHint(t) }}</div>
            </div>
            <span class="iris-task-meta">{{ relativeTime(t.lastActivityAt ?? t.updatedAt) }}</span>
          </div>
        </section>

        <section class="iris-section">
          <div class="iris-section-title">
            <span class="section-dot bao-dot"></span>
            Warte auf Bao <span class="section-count">{{ waitingForBaoCount }}</span>
          </div>
          <div v-if="waitingForBaoTasks.length === 0" class="iris-empty">Keine Tasks</div>
          <div v-for="t in waitingForBaoTasks" :key="t.id" class="iris-task-row progress-row">
            <div>
              <span class="iris-task-title">{{ t.title }}</span>
              <div class="iris-task-progress">{{ activityHint(t) }}</div>
            </div>
            <span class="iris-task-meta">{{ relativeTime(t.lastActivityAt ?? t.updatedAt) }}</span>
          </div>
        </section>

        <section class="iris-section">
          <div class="iris-section-title">
            <span class="section-dot other-dot"></span>
            Warte auf andere <span class="section-count">{{ waitingForOthersTasks.length }}</span>
          </div>
          <div v-if="waitingForOthersTasks.length === 0" class="iris-empty">Keine Tasks</div>
          <div v-for="t in waitingForOthersTasks" :key="t.id" class="iris-task-row progress-row">
            <div>
              <span class="iris-task-title">{{ t.title }}</span>
              <div class="iris-task-progress">{{ activityHint(t) }}</div>
            </div>
            <span class="iris-task-meta">{{ expectedFromLabel(t.expectedFrom) }}</span>
          </div>
        </section>

        <section class="iris-section iris-section-stale">
          <div class="iris-section-title">
            <span class="section-dot stale-dot"></span>
            Stale Tasks <span class="section-count stale-count">{{ staleCount }}</span>
          </div>
          <div v-if="staleTasks.length === 0" class="iris-empty">Keine stale Tasks</div>
          <div v-for="t in staleTasks" :key="t.id" class="iris-task-row stale-row progress-row">
            <div>
              <span class="iris-task-title">{{ t.title }}</span>
              <div class="iris-task-progress">{{ activityHint(t) }}</div>
            </div>
            <span class="iris-task-meta stale-meta">{{ hoursSince(t.updatedAt) }}h offen</span>
          </div>
        </section>
      </div>
    </div>

    <AsyncStatePanel
      v-if="taskBoard.query.isLoading.value && !taskBoard.query.data.value"
      state="loading"
      title="Aufgaben werden geladen"
    />

    <AsyncStatePanel
      v-else-if="taskBoard.query.isError.value && !taskBoard.query.data.value"
      state="error"
      title="Task Board konnte nicht geladen werden"
      :problem="taskBoard.query.error.value"
      @action="taskBoard.query.refetch()"
    />

    <AsyncStatePanel
      v-if="taskBoard.query.isError.value && taskBoard.query.data.value"
      state="stale"
      title="Letzter bestätigter Board-Stand"
      :problem="taskBoard.query.error.value"
      message="Die sichtbaren Karten bleiben erhalten, während Nexus die Verbindung erneut prüft."
      action-label="Erneut synchronisieren"
      compact
      @action="taskBoard.query.refetch()"
    />

    <div v-if="taskBoard.query.data.value" class="board-columns">
      <div
        class="col"
        :class="{ 'drag-over': dragOverColumn === 'offen' }"
        @dragover="onDragOver($event, 'offen')"
        @dragleave="onDragLeave"
        @drop="onDrop($event, 'offen')"
      >
        <div class="col-header">
          <span class="col-icon" style="background: var(--st-queue); box-shadow: 0 0 0 2px color-mix(in srgb, var(--st-queue) 25%, transparent);"></span>
          <span class="col-name">Offen</span>
          <span class="col-count">{{ board.offen.length }}</span>
        </div>
        <div class="col-cards">
          <button
            v-for="task in board.offen"
            :key="task.id"
            type="button"
            class="card"
            :class="{ 'card-agent': task.isAgentTask }"
            draggable="true"
            @click="handleCardClick($event, task.id)"
            @dragstart="onDragStart($event, task.id)"
            @dragend="onDragEnd"
          >
            <div class="card-top">
              <span class="prio-badge" :style="{ color: priorityColor(task.priority), borderColor: priorityColor(task.priority) }">
                {{ priorityLabel(task.priority) }}
              </span>
              <span v-if="task.isAgentTask" class="agent-badge" title="Agent-Task"><Bot :size="12" aria-hidden="true" /></span>
              <span v-if="task.expectedFrom" class="expected-badge" :title="'Erwartet: ' + task.expectedFrom">
                <Hourglass :size="11" aria-hidden="true" /> {{ task.expectedFrom }}
              </span>
              <span v-if="delegationBadge(task)" class="expected-badge expected-badge--delegation" title="Sichtbare Delegation im Board">
                <CornerDownRight :size="11" aria-hidden="true" /> {{ delegationBadge(task) }}
              </span>
              <span
                v-if="task.assignedTo"
                class="assignee"
                :class="assigneeClass(task.assignedTo)"
              >
                {{ assigneeLabel(task.assignedTo) }}
              </span>
            </div>
            <div class="card-title">{{ task.title }}</div>
            <div v-if="task.detail" class="card-preview">{{ task.detail }}</div>
            <div v-if="task.isAgentTask || hasChildTasks(task.id)" class="card-progress-hint">{{ activityHint(task) }}</div>
            <div class="card-meta">Update {{ relativeTime(task.lastActivityAt ?? task.updatedAt) }}</div>
          </button>
          <div v-if="!board.offen.length" class="empty-col">Keine Aufgaben</div>
        </div>
      </div>

      <div
        class="col"
        :class="{ 'drag-over': dragOverColumn === 'inProgress' }"
        @dragover="onDragOver($event, 'inProgress')"
        @dragleave="onDragLeave"
        @drop="onDrop($event, 'inProgress')"
      >
        <div class="col-header">
          <span class="col-icon" style="background: var(--st-work); box-shadow: 0 0 0 2px color-mix(in srgb, var(--st-work) 25%, transparent);"></span>
          <span class="col-name">In Bearbeitung</span>
          <span class="col-count">{{ board.inProgress.length }}</span>
        </div>
        <div class="col-cards">
          <button
            v-for="task in board.inProgress"
            :key="task.id"
            type="button"
            class="card"
            :class="{ 'card-agent': task.isAgentTask }"
            draggable="true"
            @click="handleCardClick($event, task.id)"
            @dragstart="onDragStart($event, task.id)"
            @dragend="onDragEnd"
          >
            <div class="card-top">
              <span class="prio-badge" :style="{ color: priorityColor(task.priority), borderColor: priorityColor(task.priority) }">
                {{ priorityLabel(task.priority) }}
              </span>
              <span v-if="task.isAgentTask" class="agent-badge" title="Agent-Task"><Bot :size="12" aria-hidden="true" /></span>
              <span v-if="task.expectedFrom" class="expected-badge" :title="'Erwartet: ' + task.expectedFrom">
                <Hourglass :size="11" aria-hidden="true" /> {{ task.expectedFrom }}
              </span>
              <span v-if="delegationBadge(task)" class="expected-badge expected-badge--delegation" title="Sichtbare Delegation im Board">
                <CornerDownRight :size="11" aria-hidden="true" /> {{ delegationBadge(task) }}
              </span>
              <span
                v-if="task.assignedTo"
                class="assignee"
                :class="assigneeClass(task.assignedTo)"
              >
                {{ assigneeLabel(task.assignedTo) }}
              </span>
            </div>
            <div class="card-title">{{ task.title }}</div>
            <div v-if="task.detail" class="card-preview">{{ task.detail }}</div>
            <div v-if="task.isAgentTask || hasChildTasks(task.id)" class="card-progress-hint">{{ activityHint(task) }}</div>
            <div class="card-meta">Update {{ relativeTime(task.lastActivityAt ?? task.updatedAt) }}</div>
          </button>
          <div v-if="!board.inProgress.length" class="empty-col">Keine Aufgaben</div>
        </div>
      </div>

      <div
        class="col"
        :class="{ 'drag-over': dragOverColumn === 'review' }"
        @dragover="onDragOver($event, 'review')"
        @dragleave="onDragLeave"
        @drop="onDrop($event, 'review')"
      >
        <div class="col-header">
          <span class="col-icon" style="background: var(--st-review); box-shadow: 0 0 0 2px color-mix(in srgb, var(--st-review) 25%, transparent);"></span>
          <span class="col-name">Review</span>
          <span class="col-count">{{ board.review.length }}</span>
        </div>
        <div class="col-cards">
          <button
            v-for="task in board.review"
            :key="task.id"
            type="button"
            class="card"
            :class="{ 'card-agent': task.isAgentTask }"
            draggable="true"
            @click="handleCardClick($event, task.id)"
            @dragstart="onDragStart($event, task.id)"
            @dragend="onDragEnd"
          >
            <div class="card-top">
              <span class="prio-badge" :style="{ color: priorityColor(task.priority), borderColor: priorityColor(task.priority) }">
                {{ priorityLabel(task.priority) }}
              </span>
              <span v-if="task.isAgentTask" class="agent-badge" title="Agent-Task"><Bot :size="12" aria-hidden="true" /></span>
              <span v-if="task.expectedFrom" class="expected-badge" :title="'Erwartet: ' + task.expectedFrom">
                <Hourglass :size="11" aria-hidden="true" /> {{ task.expectedFrom }}
              </span>
              <span v-if="delegationBadge(task)" class="expected-badge expected-badge--delegation" title="Sichtbare Delegation im Board">
                <CornerDownRight :size="11" aria-hidden="true" /> {{ delegationBadge(task) }}
              </span>
              <span
                v-if="task.assignedTo"
                class="assignee"
                :class="assigneeClass(task.assignedTo)"
              >
                {{ assigneeLabel(task.assignedTo) }}
              </span>
            </div>
            <div class="card-title">{{ task.title }}</div>
            <div v-if="task.detail" class="card-preview">{{ task.detail }}</div>
            <div v-if="task.isAgentTask || hasChildTasks(task.id)" class="card-progress-hint">{{ activityHint(task) }}</div>
            <div class="card-meta">Update {{ relativeTime(task.lastActivityAt ?? task.updatedAt) }}</div>
          </button>
          <div v-if="!board.review.length" class="empty-col">Keine Aufgaben</div>
        </div>
      </div>

      <div
        class="col"
        :class="{ 'drag-over': dragOverColumn === 'done' }"
        @dragover="onDragOver($event, 'done')"
        @dragleave="onDragLeave"
        @drop="onDrop($event, 'done')"
      >
        <div class="col-header">
          <span class="col-icon" style="background: var(--st-work); box-shadow: 0 0 0 2px color-mix(in srgb, var(--st-work) 25%, transparent);"></span>
          <span class="col-name">Erledigt</span>
          <span class="col-count">{{ board.done.length }}</span>
        </div>
        <div class="col-cards">
          <button
            v-for="task in board.done"
            :key="task.id"
            type="button"
            class="card"
            draggable="true"
            @click="handleCardClick($event, task.id)"
            @dragstart="onDragStart($event, task.id)"
            @dragend="onDragEnd"
          >
            <div class="card-top">
              <span class="prio-badge" :style="{ color: priorityColor(task.priority), borderColor: priorityColor(task.priority) }">
                {{ priorityLabel(task.priority) }}
              </span>
              <span
                v-if="task.assignedTo"
                class="assignee"
                :class="assigneeClass(task.assignedTo)"
              >
                {{ assigneeLabel(task.assignedTo) }}
              </span>
            </div>
            <div class="card-title">{{ task.title }}</div>
            <div v-if="task.detail" class="card-preview">{{ task.detail }}</div>
            <div v-if="task.isAgentTask || hasChildTasks(task.id)" class="card-progress-hint">{{ activityHint(task) }}</div>
            <div class="card-meta">Update {{ relativeTime(task.lastActivityAt ?? task.updatedAt) }}</div>
          </button>
          <div v-if="!board.done.length" class="empty-col">Keine Aufgaben</div>
          <button
            v-if="taskBoard.hasMoreDone.value"
            type="button"
            class="btn-ghost done-more"
            :disabled="taskBoard.loadingMoreDone.value"
            @click="taskBoard.loadMoreDone()"
          >
            {{ taskBoard.loadingMoreDone.value ? 'Lade…' : 'Weitere erledigte laden' }}
          </button>
        </div>
      </div>

      <div
        class="col col-blocked"
        :class="{ 'drag-over': dragOverColumn === 'blocked' }"
        @dragover="onDragOver($event, 'blocked')"
        @dragleave="onDragLeave"
        @drop="onDrop($event, 'blocked')"
      >
        <div class="col-header">
          <span class="col-icon" style="background: var(--st-block); box-shadow: 0 0 0 2px color-mix(in srgb, var(--st-block) 25%, transparent);"></span>
          <span class="col-name">Blockiert</span>
          <span class="col-count">{{ board.blocked.length }}</span>
        </div>
        <div class="col-cards">
          <button
            v-for="task in board.blocked"
            :key="task.id"
            type="button"
            class="card card-blocked"
            :class="{ 'card-agent': task.isAgentTask }"
            draggable="true"
            @click="handleCardClick($event, task.id)"
            @dragstart="onDragStart($event, task.id)"
            @dragend="onDragEnd"
          >
            <div class="card-top">
              <span class="prio-badge" :style="{ color: priorityColor(task.priority), borderColor: priorityColor(task.priority) }">
                {{ priorityLabel(task.priority) }}
              </span>
              <span v-if="task.isAgentTask" class="agent-badge" title="Agent-Task"><Bot :size="12" aria-hidden="true" /></span>
              <span v-if="task.expectedFrom" class="expected-badge" :title="'Erwartet: ' + task.expectedFrom">
                <Hourglass :size="11" aria-hidden="true" /> {{ task.expectedFrom }}
              </span>
              <span v-if="delegationBadge(task)" class="expected-badge expected-badge--delegation" title="Sichtbare Delegation im Board">
                <CornerDownRight :size="11" aria-hidden="true" /> {{ delegationBadge(task) }}
              </span>
              <span
                v-if="task.assignedTo"
                class="assignee"
                :class="assigneeClass(task.assignedTo)"
              >
                {{ assigneeLabel(task.assignedTo) }}
              </span>
            </div>
            <div class="card-title">{{ task.title }}</div>
            <div v-if="task.detail" class="card-preview">{{ task.detail }}</div>
            <div v-if="task.isAgentTask || hasChildTasks(task.id)" class="card-progress-hint">{{ activityHint(task) }}</div>
            <div class="card-meta">Update {{ relativeTime(task.lastActivityAt ?? task.updatedAt) }}</div>
          </button>
          <div v-if="!board.blocked.length" class="empty-col">Keine Blockierer</div>
        </div>
      </div>
    </div>

    <Teleport to="body">
      <div v-if="showCreateModal" class="modal-overlay" @click.self="showCreateModal = false">
        <div class="modal-card">
          <div class="modal-header">
            <h2>Neue Aufgabe</h2>
            <button type="button" class="modal-close" aria-label="Dialog schließen" @click="showCreateModal = false"><X :size="16" /></button>
          </div>
          <form @submit.prevent="handleCreateTask" class="modal-form">
            <div class="field">
              <label for="task-title">Titel <span class="req">*</span></label>
              <input
                id="task-title"
                v-model="formTitle"
                type="text"
                class="field-input"
                placeholder="Aufgabe beschreiben…"
              />
            </div>
            <div class="field">
              <label for="task-detail">Beschreibung</label>
              <textarea
                id="task-detail"
                v-model="formDetail"
                class="field-input field-textarea"
                placeholder="Details zur Aufgabe…"
                rows="3"
              ></textarea>
            </div>
            <div class="field-row">
              <div class="field">
                <label for="task-priority">Priorität</label>
                <select id="task-priority" v-model="formPriority" class="field-input field-select">
                  <option value="High">High</option>
                  <option value="Medium">Medium</option>
                  <option value="Low">Low</option>
                </select>
              </div>
              <div class="field">
                <label for="task-assignee">Zugewiesen an</label>
                <select id="task-assignee" v-model="formAssignedTo" class="field-input field-select">
                  <option
                    v-for="option in taskAgentOptions.filter(entry => entry.id)"
                    :key="option.id"
                    :value="option.id"
                  >
                    {{ option.label }}
                  </option>
                </select>
              </div>
            </div>
            <p v-if="formError" class="form-error">{{ formError }}</p>
            <div class="modal-actions">
              <button type="button" class="btn-cancel" @click="showCreateModal = false">Abbrechen</button>
              <button type="submit" class="btn-submit" :disabled="formSubmitting">
                {{ formSubmitting ? 'Erstelle…' : 'Aufgabe erstellen' }}
              </button>
            </div>
          </form>
        </div>
      </div>
    </Teleport>

    <Teleport to="body">
      <div v-if="showDetailPanel && selectedTask" class="detail-overlay" @click.self="closeDetailPanel">
        <aside class="detail-panel">
          <div class="detail-topbar">
            <div class="detail-breadcrumb">Task Board / {{ stateLabel(selectedTask.state) }}</div>
            <button type="button" class="detail-close" @click="closeDetailPanel" aria-label="Detailansicht schließen">
              <X :size="18" />
            </button>
          </div>

          <div class="detail-content">
            <section class="detail-main">
              <div class="detail-title-block">
                <span class="detail-state-pill" :class="statusTone(selectedTask.state)">{{ stateLabel(selectedTask.state) }}</span>
                <input v-model="detailForm.title" class="detail-title-input" maxlength="240" aria-label="Titel der Aufgabe" />
                <div class="detail-meta-row">
                  <span>#{{ selectedTask.id.slice(0, 8) }}</span>
                  <span v-if="selectedTask.isAgentTask" class="meta-agent-tag"><Bot :size="12" aria-hidden="true" /> Agent-Task</span>
                  <span v-if="selectedTask.expectedFrom" class="meta-expected"><Hourglass :size="11" aria-hidden="true" /> Erwartet: {{ selectedTask.expectedFrom }}</span>
                  <span v-if="selectedTask.parentTaskId" class="meta-expected"><CornerDownRight :size="11" aria-hidden="true" /> Child-Task</span>
                  <span v-if="delegationBadge(selectedTask)" class="meta-expected"><CornerDownRight :size="11" aria-hidden="true" /> {{ delegationBadge(selectedTask) }}</span>
                  <span><Clock3 :size="13" /> Aktualisiert {{ formatDate(selectedTask.updatedAt, true) }}</span>
                  <span><CalendarDays :size="13" /> Erstellt {{ formatDate(selectedTask.createdAt) }}</span>
                  <span v-if="selectedTask.isAgentTask"><MessageSquareText :size="13" /> Letzter Status {{ relativeTime(selectedTask.lastActivityAt ?? selectedTask.updatedAt) }}</span>
                </div>
                <div v-if="selectedTask.isAgentTask || hasChildTasks(selectedTask.id)" class="detail-progress-banner">
                  <strong>Letzter Fortschritt:</strong> {{ activityHint(selectedTask) }}
                </div>
              </div>

              <div class="detail-section">
                <div class="detail-section-header">Beschreibung</div>
                <textarea
                  v-model="detailForm.detail"
                  class="detail-textarea"
                  aria-label="Beschreibung der Aufgabe"
                  rows="8"
                  placeholder="Mehr Kontext, Akzeptanzkriterien oder Notizen…"
                ></textarea>
              </div>

              <div class="detail-grid">
                <section class="detail-section">
                  <div class="detail-section-header"><ListChecks :size="14" /> Unteraufgaben</div>
                  <div v-if="detailLoading" class="detail-empty">Lade…</div>
                  <div v-else-if="childTasks.length" class="detail-stack">
                    <article v-for="child in childTasks" :key="child.id" class="mini-card">
                      <div class="mini-card-row">
                        <span class="mini-title">{{ child.title }}</span>
                        <span class="mini-state">{{ stateLabel(child.state) }}</span>
                      </div>
                      <p v-if="child.detail" class="mini-copy">{{ child.detail }}</p>
                    </article>
                  </div>
                  <div v-else class="detail-empty">Noch keine Unteraufgaben verknüpft.</div>
                </section>

                <section class="detail-section">
                  <div class="detail-section-header"><Link2 :size="14" /> Aktivität</div>
                  <div v-if="detailLoading" class="detail-empty">Lade…</div>
                  <div v-else-if="taskActivity.length" class="activity-list">
                    <article v-for="(entry, index) in taskActivity" :key="entry.id ?? index" class="activity-item">
                      <div class="activity-dot"></div>
                      <div>
                        <div class="activity-message">{{ entry.message ?? 'Aktivität' }}</div>
                        <div class="activity-time">{{ formatDate(entry.createdAt ?? null, true) }}</div>
                      </div>
                    </article>
                  </div>
                  <div v-else class="detail-empty">Noch keine Aktivität vorhanden.</div>
                </section>
              </div>
            </section>

            <aside class="detail-sidebar">
              <section class="sidebar-card">
                <div class="sidebar-heading">Eigenschaften</div>
                <label class="sidebar-field">
                  <span>Status <span v-if="!canChangeState" class="readonly-tag">(nur Iris/Bao)</span></span>
                  <select
                    v-model="detailForm.state"
                    class="field-input field-select slim"
                    :disabled="!canChangeState"
                    :title="!canChangeState ? 'Statusänderungen sind nur Iris und Bao vorbehalten' : ''"
                  >
                    <option value="Backlog">Offen</option>
                    <option value="In progress">In Bearbeitung</option>
                    <option value="Review">Review</option>
                    <option value="Blocked">Blockiert</option>
                    <option value="Done">Erledigt</option>
                  </select>
                </label>
                <label class="sidebar-field">
                  <span>Priorität</span>
                  <select v-model="detailForm.priority" class="field-input field-select slim">
                    <option value="High">High</option>
                    <option value="Medium">Medium</option>
                    <option value="Low">Low</option>
                  </select>
                </label>
                <label class="sidebar-field">
                  <span>Zuständig</span>
                  <select v-model="detailForm.assignedTo" class="field-input field-select slim">
                    <option v-for="option in taskAgentOptions" :key="option.id || 'unassigned'" :value="option.id">
                      {{ option.label }}
                    </option>
                  </select>
                </label>
                <label class="sidebar-field">
                  <span>Fällig am</span>
                  <input v-model="detailForm.dueDate" type="date" class="field-input slim" />
                </label>
              </section>

              <section class="sidebar-card subtle">
                <div class="sidebar-heading">Snapshot</div>
                <dl class="snapshot-list">
                  <div>
                    <dt>Quelle</dt>
                    <dd>{{ selectedTask.source || '—' }}</dd>
                  </div>
                  <div>
                    <dt>Priorität</dt>
                    <dd>{{ selectedTask.priority }}</dd>
                  </div>
                  <div>
                    <dt>Fällig</dt>
                    <dd>{{ formatDate(selectedTask.dueDate) }}</dd>
                  </div>
                  <div v-if="selectedTask.isAgentTask">
                    <dt>Agent-Task</dt>
                    <dd><Bot :size="12" aria-hidden="true" /> Ja</dd>
                  </div>
                  <div v-if="selectedTask.expectedFrom">
                    <dt>Erwartet von</dt>
                    <dd>{{ selectedTask.expectedFrom }}</dd>
                  </div>
                  <div v-if="selectedTask.parentTaskId">
                    <dt>Task-Typ</dt>
                    <dd>Sichtbare Child-Task</dd>
                  </div>
                </dl>
              </section>

              <p v-if="detailError" class="detail-flash error">{{ detailError }}</p>
              <p v-else-if="detailSuccess" class="detail-flash success">{{ detailSuccess }}</p>

              <div class="detail-actions">
                <button type="button" class="btn-cancel" @click="closeDetailPanel">Schließen</button>
                <button type="button" class="btn-ghost" @click="router.push('/tasks/' + selectedTask.id)">
                  <ExternalLink :size="13" /> Vollansicht öffnen
                </button>
                <button type="button" class="btn-submit" :disabled="!canSaveDetail || (detailForm.state !== selectedTask.state && !canChangeState)" @click="saveTaskDetail">
                  <Save :size="14" />
                  {{ detailSaving ? 'Speichert…' : 'Speichern' }}
                </button>
              </div>
            </aside>
          </div>
        </aside>
      </div>
    </Teleport>
  </div>
</template>

<style scoped>
.board-wrap {
  width: 100%;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  gap: 20px;
  animation: board-fade-in 0.35s ease-out;
}
@keyframes board-fade-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.board-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.board-header h1 { margin: 0; font-size: 22px; font-weight: 700; font-family: 'Space Grotesk', sans-serif; letter-spacing: -0.02em; }
.grad-text { background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
.board-subtitle { margin: 4px 0 0; font-size: 11px; color: var(--tx-3); font-family: 'Manrope', sans-serif; }
.board-header-actions { display: flex; align-items: center; gap: 8px; }
.runtime-strip {
  display: grid;
  grid-template-columns: minmax(190px, 1fr) auto auto;
  align-items: center;
  gap: 14px;
  padding: 12px 14px;
}
.runtime-strip__identity {
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--a-mid);
}
.runtime-strip__identity div {
  display: grid;
  gap: 2px;
}
.runtime-strip__identity strong {
  color: var(--tx);
}
.runtime-strip__identity span {
  color: var(--tx-3);
  font-family: var(--font-mono-v2);
  font-size: 11px;
}
.runtime-strip__metrics {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.runtime-strip__metrics span {
  padding: 5px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--tx-3);
  font-family: var(--font-mono-v2);
  font-size: 11px;
}
.runtime-strip__metrics strong {
  color: var(--tx);
}
.create-btn { display: flex; align-items: center; gap: 6px; padding: 8px 16px; border: none; border-radius: var(--r-sm, 10px); background: var(--grad); color: var(--tx); font-size: 12.5px; font-weight: 600; font-family: 'Manrope', sans-serif; cursor: pointer; transition: opacity .15s, transform .15s; flex-shrink: 0; box-shadow: var(--glow-purple); }
.create-btn:hover { opacity: .85; transform: translateY(-1px); }
.create-btn:active { transform: translateY(0); }
.iris-panel-btn { display: flex; align-items: center; gap: 6px; padding: 8px 14px; border: 1px solid var(--a-mid); border-radius: var(--r-sm, 10px); background: color-mix(in srgb, var(--a-mid) 10%, transparent); color: var(--a-mid); font-size: 12px; font-weight: 600; font-family: 'Manrope', sans-serif; cursor: pointer; transition: background .15s, border-color .15s; }
.iris-panel-btn:hover { background: color-mix(in srgb, var(--a-mid) 18%, transparent); }
.panel-badge { font-size: 9px; font-weight: 700; padding: 1px 6px; border-radius: 6px; }
.iris-badge { background: color-mix(in srgb, var(--a-purple) 25%, transparent); color: var(--a-purple); }
.stale-badge { background: color-mix(in srgb, var(--st-block) 25%, transparent); color: var(--st-block); }

/* Stale Banner */
.stale-banner { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: var(--r-sm, 10px); background: color-mix(in srgb, var(--st-block) 10%, transparent); border: 1px solid color-mix(in srgb, var(--st-block) 25%, transparent); color: var(--st-block); font-size: 12px; font-family: 'Manrope', sans-serif; }
.stale-dismiss { margin-left: auto; padding: 4px 12px; border: 1px solid color-mix(in srgb, var(--st-block) 30%, transparent); border-radius: 8px; background: transparent; color: var(--st-block); font-size: 11px; font-weight: 600; font-family: 'Manrope', sans-serif; cursor: pointer; }

/* Iris Panel */
.iris-panel { background: var(--glass); border: 1px solid var(--line-2); border-radius: var(--r, 14px); padding: 16px; backdrop-filter: blur(12px); }
.iris-panel-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; padding-bottom: 10px; border-bottom: 1px solid var(--line); }
.iris-panel-header h3 { margin: 0; font-size: 14px; font-weight: 700; color: var(--tx); display: flex; align-items: center; gap: 8px; font-family: 'Space Grotesk', sans-serif; }
.iris-loading { padding: 20px; text-align: center; color: var(--tx-3); font-size: 12px; }
.iris-panel-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.iris-section { background: color-mix(in srgb, var(--tx) 2%, transparent); border: 1px solid var(--line); border-radius: 12px; padding: 12px; }
.iris-section-title { display: flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--tx-2); margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--line); }
.section-dot { width: 8px; height: 8px; border-radius: 50%; }
.iris-dot { background: var(--a-purple); }
.bao-dot { background: var(--a-blue); }
.other-dot { background: var(--st-review); }
.stale-dot { background: var(--st-block); }
.section-count { margin-left: auto; font-family: 'JetBrains Mono', monospace; font-size: 10px; padding: 1px 6px; border-radius: 6px; background: var(--glass-2); color: var(--tx-2); }
.stale-count { background: color-mix(in srgb, var(--st-block) 15%, transparent); color: var(--st-block); }
.iris-empty { font-size: 11px; color: var(--tx-3); font-style: italic; padding: 8px; text-align: center; }
.iris-task-row { padding: 6px 8px; border-bottom: 1px solid var(--line); display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.iris-task-row:last-child { border-bottom: none; }
.progress-row { align-items: flex-start; }
.iris-task-title { font-size: 11.5px; font-weight: 500; color: var(--tx); display: block; }
.iris-task-progress { margin-top: 4px; font-size: 10px; color: var(--tx-3); line-height: 1.35; }
.iris-task-meta { font-size: 10px; color: var(--tx-3); white-space: nowrap; }
.stale-row { background: color-mix(in srgb, var(--st-block) 5%, transparent); border-radius: 4px; }
.stale-meta { color: var(--st-block); font-weight: 600; }
.iris-section-stale { border-color: color-mix(in srgb, var(--st-block) 25%, transparent); background: color-mix(in srgb, var(--st-block) 4%, transparent); }

.board-loading { display: flex; align-items: center; gap: 10px; padding: 40px; color: var(--tx-3); font-size: 13px; font-family: 'Manrope', sans-serif; }
.spinner { width: 20px; height: 20px; border: 2px solid var(--line-2); border-top-color: var(--a-mid); border-radius: 50%; animation: spin .6s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.board-columns { display: flex; gap: 14px; flex: 1; overflow-x: auto; padding-bottom: 20px; min-height: calc(100vh - 200px); scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--a-mid) 22%, transparent) transparent; }
.col { flex: 1; min-width: 240px; max-width: 320px; display: flex; flex-direction: column; background: var(--glass); border: 1px solid var(--line); border-radius: var(--r, 14px); padding: 12px; transition: border-color .2s, background .2s; backdrop-filter: blur(12px); }
.col.drag-over { border-color: var(--a-mid); background: linear-gradient(160deg, color-mix(in srgb, var(--a-mid) 10%, transparent), color-mix(in srgb, var(--space-3) 55%, transparent)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--a-mid) 15%, transparent); }

/* Permission Banner */
.permission-banner { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: var(--r-sm, 10px); background: color-mix(in srgb, var(--a-purple) 8%, transparent); border: 1px solid color-mix(in srgb, var(--a-purple) 20%, transparent); color: var(--a-purple); font-size: 12px; font-family: 'Manrope', sans-serif; }
.readonly-tag { font-weight: 400; color: var(--tx-3); font-size: 10px; text-transform: none; }
select:disabled { opacity: .45; cursor: not-allowed; }
.col-blocked { max-width: 240px; }
.col-header { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid var(--line); }
.col-icon { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.col-name { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--tx-2); font-family: 'Space Grotesk', sans-serif; }
.col-count { margin-left: auto; font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 700; font-variant-numeric: tabular-nums; padding: 2px 8px; border-radius: 10px; background: var(--glass-2); color: var(--tx-2); border: 1px solid var(--line); }
.col-cards { display: flex; flex-direction: column; gap: 8px; flex: 1; }
.card { padding: 10px 12px; border-radius: var(--r-sm, 10px); background: linear-gradient(160deg, color-mix(in srgb, var(--space-3) 45%, transparent), color-mix(in srgb, var(--space-3) 35%, transparent)); border: 1px solid var(--line); cursor: pointer; transition: transform .15s, box-shadow .2s, border-color .15s, opacity .15s; text-align: left; width: 100%; }
.card:hover { transform: scale(1.02); border-color: var(--line-2); box-shadow: 0 0 0 1px color-mix(in srgb, var(--a-mid) 10%, transparent), 0 8px 24px -6px color-mix(in srgb, var(--space-0) 40%, transparent); }
.card:active { cursor: grabbing; }
.card.dragging { opacity: .4; cursor: grabbing; }
.card-blocked { border-left: 3px solid var(--st-block); }
.card-agent { border-left: 2px solid color-mix(in srgb, var(--a-mid) 30%, transparent); }
.card-top { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; flex-wrap: wrap; }
.prio-badge { font-family: 'JetBrains Mono', monospace; font-size: 9px; font-weight: 700; padding: 1px 5px; border-radius: 4px; border: 1px solid; background: transparent; }
.agent-badge { font-size: 11px; line-height: 1; }
.expected-badge { font-family: 'JetBrains Mono', monospace; font-size: 8px; font-weight: 600; padding: 1px 5px; border-radius: 4px; background: color-mix(in srgb, var(--a-purple) 8%, transparent); color: var(--tx-2); border: 1px solid color-mix(in srgb, var(--a-purple) 15%, transparent); }
.assignee { font-family: 'Manrope', sans-serif; font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 4px; }
.assignee-iris { background: color-mix(in srgb, var(--a-purple) 12%, transparent); color: var(--a-purple); }
.assignee-bao { background: color-mix(in srgb, var(--a-blue) 12%, transparent); color: var(--a-blue); }
.assignee-agent { background: color-mix(in srgb, var(--st-work) 12%, transparent); color: var(--st-work); }
.card-title { font-size: 12.5px; font-weight: 600; color: var(--tx); line-height: 1.4; word-break: break-word; font-family: 'Manrope', sans-serif; }
.card-preview { margin-top: 6px; font-size: 11px; line-height: 1.45; color: var(--tx-2); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.card-progress-hint { margin-top: 7px; font-size: 10.5px; color: var(--tx-2); line-height: 1.4; padding: 6px 8px; border-radius: 8px; background: color-mix(in srgb, var(--a-mid) 7%, transparent); border: 1px solid color-mix(in srgb, var(--a-mid) 12%, transparent); }
.card-meta { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--tx-3); margin-top: 5px; font-variant-numeric: tabular-nums; }
.empty-col, .detail-empty { display: flex; align-items: center; justify-content: center; padding: 24px 12px; font-size: 11px; color: var(--tx-3); font-style: italic; font-family: 'Manrope', sans-serif; }
.done-more { width: 100%; justify-content: center; margin-top: 4px; }

/* Modal / Detail shared styles */
.modal-overlay, .detail-overlay { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; background: color-mix(in srgb, var(--space-0) 75%, transparent); backdrop-filter: blur(16px); }
.modal-card { width: 100%; max-width: 460px; background: linear-gradient(160deg, color-mix(in srgb, var(--space-3) 85%, transparent), color-mix(in srgb, var(--space-1) 85%, transparent)); border: 1px solid var(--line-2); border-radius: var(--r, 14px); padding: 24px; box-shadow: 0 0 0 1px color-mix(in srgb, var(--a-mid) 12%, transparent), 0 20px 60px -12px color-mix(in srgb, var(--space-0) 50%, transparent); backdrop-filter: blur(12px); }
.modal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; padding-bottom: 14px; border-bottom: 1px solid var(--line); }
.modal-header h2 { margin: 0; font-size: 16px; font-weight: 700; color: var(--tx); font-family: 'Space Grotesk', sans-serif; }
.modal-close, .detail-close { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border: none; background: transparent; color: var(--tx-3); cursor: pointer; border-radius: 6px; transition: background .15s, color .15s; }
.modal-close { font-size: 20px; }
.modal-close:hover, .detail-close:hover { background: color-mix(in srgb, var(--a-mid) 10%, transparent); color: var(--tx); }
.modal-form { display: flex; flex-direction: column; gap: 14px; }
.field { display: flex; flex-direction: column; gap: 5px; flex: 1; }
.field label, .sidebar-field span { font-size: 10.5px; font-weight: 600; color: var(--tx-2); text-transform: uppercase; letter-spacing: .04em; font-family: 'Manrope', sans-serif; }
.req { color: var(--st-block); }
.field-input { width: 100%; padding: 8px 12px; border: 1px solid var(--line); border-radius: var(--r-sm, 10px); background: color-mix(in srgb, var(--space-1) 50%, transparent); color: var(--tx); font-size: 13px; font-family: 'Manrope', sans-serif; outline: none; transition: border-color .15s, box-shadow .15s; box-sizing: border-box; }
.field-input:focus, .detail-title-input:focus, .detail-textarea:focus { border-color: var(--a-mid); box-shadow: 0 0 0 2px color-mix(in srgb, var(--a-mid) 15%, transparent); }
.field-textarea, .detail-textarea { resize: vertical; min-height: 60px; font-family: inherit; }
.field-select { cursor: pointer; }
.field-row { display: flex; gap: 12px; }
.form-error, .detail-flash.error { color: var(--st-block); font-size: 11px; margin: 0; font-family: 'Manrope', sans-serif; }
.detail-flash.success { color: var(--st-work); font-size: 11px; margin: 0; }
.modal-actions, .detail-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
.btn-cancel { padding: 7px 14px; border: 1px solid var(--line); border-radius: var(--r-sm, 10px); background: transparent; color: var(--tx-2); font-size: 12px; font-weight: 500; font-family: 'Manrope', sans-serif; cursor: pointer; transition: background .15s, color .15s; }
.btn-cancel:hover { background: color-mix(in srgb, var(--a-mid) 8%, transparent); color: var(--tx); }
.btn-ghost { padding: 7px 14px; border: 1px solid var(--line); border-radius: var(--r-sm, 10px); background: transparent; color: var(--tx-2); font-size: 12px; font-weight: 500; font-family: 'Manrope', sans-serif; cursor: pointer; transition: background .15s, color .15s; display: inline-flex; align-items: center; gap: 6px; }
.btn-ghost:hover { background: color-mix(in srgb, var(--a-mid) 8%, transparent); color: var(--tx); }
.btn-submit { padding: 7px 18px; border: none; border-radius: var(--r-sm, 10px); background: var(--grad); color: var(--tx); font-size: 12px; font-weight: 600; font-family: 'Manrope', sans-serif; cursor: pointer; transition: opacity .15s, transform .15s; box-shadow: var(--glow-purple); display: inline-flex; align-items: center; gap: 6px; }
.btn-submit:disabled { opacity: .5; cursor: not-allowed; box-shadow: none; }
.btn-submit:not(:disabled):hover { opacity: .85; transform: translateY(-1px); }

/* Detail Panel */
.detail-panel { width: min(1120px, calc(100vw - 48px)); height: min(88vh, 860px); display: flex; flex-direction: column; background: linear-gradient(180deg, color-mix(in srgb, var(--space-1) 97%, transparent), color-mix(in srgb, var(--space-1) 96%, transparent)); border: 1px solid color-mix(in srgb, var(--a-mid) 18%, transparent); border-radius: 22px; box-shadow: 0 28px 90px color-mix(in srgb, var(--space-0) 45%, transparent); overflow: hidden; }
.detail-topbar { display: flex; align-items: center; justify-content: space-between; padding: 18px 22px; border-bottom: 1px solid var(--line); }
.detail-breadcrumb { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--tx-3); }
.detail-content { display: grid; grid-template-columns: minmax(0, 1fr) 320px; min-height: 0; flex: 1; }
.detail-main { padding: 24px; overflow-y: auto; display: flex; flex-direction: column; gap: 20px; }
.detail-sidebar { padding: 24px; border-left: 1px solid var(--line); background: color-mix(in srgb, var(--tx) 2%, transparent); display: flex; flex-direction: column; gap: 14px; }
.detail-title-block { display: flex; flex-direction: column; gap: 12px; }
.detail-state-pill { width: fit-content; border-radius: 999px; padding: 5px 10px; font-size: 11px; font-weight: 700; letter-spacing: .03em; border: 1px solid transparent; }
.detail-state-pill.is-backlog { color: var(--st-queue); background: color-mix(in srgb, var(--st-queue) 12%, transparent); border-color: color-mix(in srgb, var(--st-queue) 25%, transparent); }
.detail-state-pill.is-progress { color: var(--st-work); background: color-mix(in srgb, var(--st-work) 12%, transparent); border-color: color-mix(in srgb, var(--st-work) 25%, transparent); }
.detail-state-pill.is-review { color: var(--st-review); background: color-mix(in srgb, var(--st-review) 12%, transparent); border-color: color-mix(in srgb, var(--st-review) 25%, transparent); }
.detail-state-pill.is-blocked { color: var(--st-block); background: color-mix(in srgb, var(--st-block) 12%, transparent); border-color: color-mix(in srgb, var(--st-block) 25%, transparent); }
.detail-state-pill.is-done { color: var(--st-work); background: color-mix(in srgb, var(--st-work) 12%, transparent); border-color: color-mix(in srgb, var(--st-work) 25%, transparent); }
.detail-title-input { background: transparent; border: 1px solid transparent; border-radius: 12px; padding: 0; color: var(--tx); font-family: 'Space Grotesk', sans-serif; font-size: 31px; font-weight: 700; letter-spacing: -0.03em; outline: none; }
.detail-meta-row { display: flex; flex-wrap: wrap; gap: 14px; color: var(--tx-3); font-size: 11.5px; }
.detail-meta-row span { display: inline-flex; align-items: center; gap: 5px; }
.meta-agent-tag { color: var(--a-purple); }
.meta-expected { color: var(--tx-2); }
.detail-progress-banner { padding: 10px 12px; border-radius: 12px; background: color-mix(in srgb, var(--a-mid) 8%, transparent); border: 1px solid color-mix(in srgb, var(--a-mid) 14%, transparent); color: var(--tx-2); font-size: 12px; }
.detail-section { background: color-mix(in srgb, var(--tx) 2%, transparent); border: 1px solid var(--line); border-radius: 16px; padding: 16px; }
.detail-section-header, .sidebar-heading { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; color: var(--tx-2); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; }
.detail-textarea { width: 100%; min-height: 180px; border-radius: 12px; border: 1px solid var(--line); background: color-mix(in srgb, var(--space-1) 55%, transparent); color: var(--tx); padding: 14px; font-size: 14px; line-height: 1.6; outline: none; box-sizing: border-box; }
.detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.detail-stack, .activity-list { display: flex; flex-direction: column; gap: 10px; }
.mini-card { border: 1px solid var(--line); border-radius: 12px; padding: 12px; background: color-mix(in srgb, var(--space-1) 45%, transparent); }
.mini-card-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.mini-title { font-size: 13px; font-weight: 600; color: var(--tx); }
.mini-state { font-size: 10px; color: var(--tx-3); text-transform: uppercase; letter-spacing: .05em; }
.mini-copy { margin: 8px 0 0; font-size: 11.5px; color: var(--tx-2); line-height: 1.5; }
.activity-item { display: grid; grid-template-columns: 10px minmax(0, 1fr); gap: 10px; align-items: start; }
.activity-dot { width: 8px; height: 8px; border-radius: 999px; margin-top: 5px; background: linear-gradient(135deg, var(--a-mid), var(--a-blue)); box-shadow: 0 0 0 4px color-mix(in srgb, var(--a-mid) 12%, transparent); }
.activity-message { color: var(--tx); font-size: 12.5px; line-height: 1.45; }
.activity-time { color: var(--tx-3); font-size: 10.5px; margin-top: 4px; }
.sidebar-card { border: 1px solid var(--line); border-radius: 16px; padding: 16px; background: color-mix(in srgb, var(--space-1) 45%, transparent); }
.sidebar-card.subtle { background: color-mix(in srgb, var(--tx) 2%, transparent); }
.sidebar-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.sidebar-field:last-child { margin-bottom: 0; }
.slim { min-height: 38px; }
.snapshot-list { display: flex; flex-direction: column; gap: 12px; margin: 0; }
.snapshot-list div { display: flex; justify-content: space-between; gap: 12px; }
.snapshot-list dt { color: var(--tx-3); font-size: 11px; }
.snapshot-list dd { margin: 0; color: var(--tx); font-size: 12px; text-align: right; }
.board-columns::-webkit-scrollbar { height: 9px; }
.board-columns::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--a-mid) 22%, transparent); border-radius: 9px; border: 2px solid transparent; background-clip: padding-box; }
.board-columns::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--a-mid) 40%, transparent); background-clip: padding-box; }
.board-columns::-webkit-scrollbar-track { background: transparent; }
@media (max-width: 1100px) { .detail-content { grid-template-columns: 1fr; } .detail-sidebar { border-left: none; border-top: 1px solid var(--line); } }
@media (max-width: 980px) { .runtime-strip { grid-template-columns: 1fr auto; } .runtime-strip__metrics { grid-column: 1 / -1; grid-row: 2; } }
@media (max-width: 860px) { .board-columns { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: thin; } .col { min-width: 260px; } .detail-panel { width: 100vw; height: 100vh; border-radius: 0; } .detail-grid { grid-template-columns: 1fr; } .detail-main, .detail-sidebar { padding: 18px; } .detail-title-input { font-size: 24px; } }
@media (max-width: 900px) { .iris-panel-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .iris-panel-grid { grid-template-columns: 1fr; } }
@media (max-width: 560px) { .runtime-strip { grid-template-columns: 1fr; } .runtime-strip__metrics { grid-column: 1; grid-row: auto; } .runtime-strip > .nexus-button { justify-self: stretch; } }
</style>
