<template>
  <div class="workspace">
    <div v-if="loadingJob" class="workspace__loading" aria-live="polite">
      <span class="spinner" aria-hidden="true" />
      <span>Loading job…</span>
    </div>

    <div v-else-if="!job" class="workspace__not-found" role="alert">
      <p>Job not found.</p>
    </div>

    <template v-else>
      <!-- Two-panel layout: job details | cover letter + resume optimizer -->
      <div class="workspace__panels">

        <!-- ── Left: Job details ──────────────────────────────────────── -->
        <aside class="workspace__job-panel">
          <div class="job-details">
            <!-- Badges -->
            <div class="job-details__badges">
              <span
                v-if="job.match_score !== null"
                class="score-badge"
                :class="[scoreBadgeClass, { 'score-badge--shimmer': shimmeringBadge }]"
              >
                {{ job.match_score }}%
              </span>
              <span v-if="job.is_remote" class="remote-badge">Remote</span>
            </div>

            <h1 class="job-details__title">{{ job.title }}</h1>
            <div class="job-details__company">
              {{ job.company }}
              <span v-if="job.location" aria-hidden="true"> · </span>
              <span v-if="job.location" class="job-details__location">{{ job.location }}</span>
            </div>
            <div v-if="job.salary" class="job-details__salary">{{ job.salary }}</div>

            <!-- Description -->
            <div class="job-details__desc" :class="{ 'job-details__desc--clamped': !descExpanded }">
              {{ job.description ?? 'No description available.' }}
            </div>
            <button
              v-if="(job.description?.length ?? 0) > 300"
              class="expand-btn"
              :aria-expanded="descExpanded"
              @click="descExpanded = !descExpanded"
            >
              {{ descExpanded ? 'Show less ▲' : 'Show more ▼' }}
            </button>

            <!-- Keyword gaps -->
            <div v-if="gaps.length > 0" class="job-details__gaps">
              <span class="gaps-label">Missing keywords:</span>
              <span v-for="kw in gaps.slice(0, 6)" :key="kw" class="gap-pill">{{ kw }}</span>
              <span v-if="gaps.length > 6" class="gaps-more">+{{ gaps.length - 6 }}</span>
            </div>

            <a v-if="job.url" :href="job.url" target="_blank" rel="noopener noreferrer" class="job-details__link">
              View listing ↗
            </a>
          </div>
        </aside>

        <!-- ── Right: Cover letter ────────────────────────────────────── -->
        <main class="workspace__cl-panel">
          <h2 class="cl-heading">Cover Letter</h2>

          <!-- State: none — no draft yet -->
          <template v-if="clState === 'none'">
            <div class="cl-empty">
              <p class="cl-empty__hint">No cover letter yet. Generate one with AI or paste your own.</p>
              <div class="cl-empty__actions">
                <button class="btn-generate" :disabled="generating" @click="generate()">
                  <span aria-hidden="true">✨</span> Generate with AI
                </button>
                <button class="btn-ghost" @click="clState = 'ready'; clText = ''">
                  Paste / write manually
                </button>
              </div>
            </div>
          </template>

          <!-- State: queued / running — generating -->
          <template v-else-if="clState === 'queued' || clState === 'running'">
            <div class="cl-generating" role="status" aria-live="polite">
              <span class="spinner spinner--lg" aria-hidden="true" />
              <p class="cl-generating__label">
                {{ clState === 'queued' ? 'Queued…' : (taskStage ?? 'Generating cover letter…') }}
              </p>
              <p class="cl-generating__hint">This usually takes 20–60 seconds</p>
            </div>
          </template>

          <!-- State: failed -->
          <template v-else-if="clState === 'failed'">
            <div class="cl-error" role="alert">
              <span aria-hidden="true">⚠️</span>
              <span class="cl-error__msg">Cover letter generation failed</span>
              <span v-if="taskError" class="cl-error__detail">{{ taskError }}</span>
              <div class="cl-error__actions">
                <button class="btn-generate" @click="generate()">Retry</button>
                <button class="btn-ghost" @click="clState = 'ready'; clText = ''">
                  Write manually instead
                </button>
              </div>
            </div>
          </template>

          <!-- State: ready — editor -->
          <template v-else-if="clState === 'ready'">
            <div class="cl-editor">
              <div class="cl-editor__toolbar">
                <span class="cl-editor__wordcount" aria-live="polite">
                  {{ wordCount }} words
                </span>
                <button
                  class="btn-ghost btn-ghost--sm"
                  :disabled="isSaved || saving"
                  @click="saveCoverLetter"
                >
                  {{ saving ? 'Saving…' : (isSaved ? '✓ Saved' : 'Save') }}
                </button>
              </div>
              <textarea
                ref="textareaEl"
                v-model="clText"
                class="cl-editor__textarea"
                aria-label="Cover letter text"
                placeholder="Your cover letter…"
                @input="isSaved = false; autoResize()"
              />
            </div>

            <!-- Download PDF -->
            <button class="btn-download" :disabled="!clText.trim() || downloadingPdf" @click="downloadPdf">
              <span aria-hidden="true">📄</span>
              {{ downloadingPdf ? 'Generating PDF…' : 'Download PDF' }}
            </button>
          </template>

          <!-- Regenerate button (when ready, offer to redo) -->
          <button
            v-if="clState === 'ready'"
            class="btn-ghost btn-ghost--sm cl-regen"
            @click="generate()"
          >
            ↺ Regenerate
          </button>

          <!-- ── ATS Resume Optimizer ──────────────────────────────── -->
          <ResumeOptimizerPanel :job-id="props.jobId" />

          <!-- ── Bottom action bar ──────────────────────────────────── -->
          <div class="workspace__actions">
            <button
              class="action-btn action-btn--apply"
              :disabled="actioning"
              @click="markApplied"
            >
              <span aria-hidden="true">🚀</span>
              {{ actioning === 'apply' ? 'Marking…' : 'Mark as Applied' }}
            </button>
            <button
              class="action-btn action-btn--reject"
              :disabled="!!actioning"
              @click="rejectListing"
            >
              <span aria-hidden="true">✗</span>
              {{ actioning === 'reject' ? 'Rejecting…' : 'Reject Listing' }}
            </button>
          </div>

        </main>
      </div>
    </template>

    <!-- Toast -->
    <Transition name="toast">
      <div v-if="toast" class="toast" role="status" aria-live="polite">{{ toast }}</div>
    </Transition>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import { useApiFetch } from '../composables/useApi'
import type { Job } from '../stores/review'
import ResumeOptimizerPanel from './ResumeOptimizerPanel.vue'

const props = defineProps<{ jobId: number }>()

const emit = defineEmits<{
  'job-removed': []
  'cover-letter-generated': []
}>()

// Perfect Match — intentionally matches score-badge--high boundary (≥70%); update together
const PERFECT_MATCH_THRESHOLD = 70
const shimmeringBadge = ref(false)

// ─── Job ──────────────────────────────────────────────────────────────────────

const job        = ref<Job | null>(null)
const loadingJob = ref(true)
const descExpanded = ref(false)

const gaps = computed<string[]>(() => {
  if (!job.value?.keyword_gaps) return []
  try   { return JSON.parse(job.value.keyword_gaps) as string[] }
  catch { return [] }
})

const scoreBadgeClass = computed(() => {
  const s = job.value?.match_score ?? 0
  if (s >= 70) return 'score-badge--high'
  if (s >= 50) return 'score-badge--mid-high'
  if (s >= 30) return 'score-badge--mid'
  return 'score-badge--low'
})

// ─── Cover letter state machine ───────────────────────────────────────────────
// none → queued → running → ready | failed

type ClState = 'none' | 'queued' | 'running' | 'ready' | 'failed'

const clState   = ref<ClState>('none')
const clText    = ref('')
const isSaved   = ref(true)
const saving    = ref(false)
const generating = ref(false)
const taskStage = ref<string | null>(null)
const taskError = ref<string | null>(null)

const wordCount = computed(() => {
  const words = clText.value.trim().split(/\s+/).filter(Boolean)
  return words.length
})

// ─── Polling ──────────────────────────────────────────────────────────────────

let pollTimer = 0

function startPolling() {
  stopPolling()
  pollTimer = window.setInterval(pollTaskStatus, 2000)
}

function stopPolling() {
  clearInterval(pollTimer)
}

async function pollTaskStatus() {
  const { data } = await useApiFetch<{
    status:  string
    stage:   string | null
    message: string | null
  }>(`/api/jobs/${props.jobId}/cover_letter/task`)
  if (!data) return

  taskStage.value = data.stage

  if (data.status === 'completed') {
    stopPolling()
    // Re-fetch the job to get the new cover letter text
    await fetchJob()
    clState.value = 'ready'
    generating.value = false
    emit('cover-letter-generated')
  } else if (data.status === 'failed') {
    stopPolling()
    clState.value  = 'failed'
    taskError.value = data.message ?? 'Unknown error'
    generating.value = false
  } else {
    clState.value = data.status === 'queued' ? 'queued' : 'running'
  }
}

// ─── Actions ──────────────────────────────────────────────────────────────────

async function generate() {
  if (generating.value) return
  generating.value = true
  clState.value    = 'queued'
  taskError.value  = null

  const { error } = await useApiFetch(`/api/jobs/${props.jobId}/cover_letter/generate`, { method: 'POST' })
  if (error) {
    clState.value    = 'failed'
    taskError.value  = error.kind === 'http' ? error.detail : 'Network error'
    generating.value = false
    return
  }
  startPolling()
}

async function saveCoverLetter() {
  saving.value = true
  const { error } = await useApiFetch(`/api/jobs/${props.jobId}/cover_letter`, {
    method:  'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body:    JSON.stringify({ text: clText.value }),
  })
  saving.value = false
  if (error) {
    showToast('Save failed — please try again')
    return
  }
  isSaved.value = true
}

// ─── PDF download ─────────────────────────────────────────────────────────────

const downloadingPdf = ref(false)

async function downloadPdf() {
  if (!job.value) return
  downloadingPdf.value = true
  try {
    const res = await fetch(`/api/jobs/${props.jobId}/cover_letter/pdf`)
    if (!res.ok) throw new Error(`HTTP ${res.status}`)
    const blob = await res.blob()
    const url  = URL.createObjectURL(blob)
    const a    = document.createElement('a')
    const company   = job.value.company.replace(/[^a-zA-Z0-9]/g, '') || 'Company'
    const dateStr   = new Date().toISOString().slice(0, 10)
    a.href     = url
    a.download = `CoverLetter_${company}_${dateStr}.pdf`
    a.click()
    URL.revokeObjectURL(url)
  } catch {
    showToast('PDF generation failed — save first and try again')
  } finally {
    downloadingPdf.value = false
  }
}

// ─── Mark applied / reject ────────────────────────────────────────────────────

const actioning = ref<'apply' | 'reject' | null>(null)

async function markApplied() {
  if (actioning.value) return
  actioning.value = 'apply'
  if (!isSaved.value) await saveCoverLetter()
  await useApiFetch(`/api/jobs/${props.jobId}/applied`, { method: 'POST' })
  actioning.value = null
  showToast('Marked as applied ✓')
  setTimeout(() => emit('job-removed'), 1200)
}

async function rejectListing() {
  if (actioning.value) return
  actioning.value = 'reject'
  await useApiFetch(`/api/jobs/${props.jobId}/reject`, { method: 'POST' })
  actioning.value = null
  showToast('Listing rejected')
  setTimeout(() => emit('job-removed'), 1000)
}

// ─── Toast ────────────────────────────────────────────────────────────────────

const toast = ref<string | null>(null)
let toastTimer = 0

function showToast(msg: string) {
  clearTimeout(toastTimer)
  toast.value  = msg
  toastTimer   = window.setTimeout(() => { toast.value = null }, 3500)
}

// ─── Auto-resize textarea ─────────────────────────────────────────────────────

const textareaEl = ref<HTMLTextAreaElement | null>(null)

function autoResize() {
  const el = textareaEl.value
  if (!el) return
  el.style.height = 'auto'
  el.style.height = `${el.scrollHeight}px`
}

watch(clText, () => nextTick(autoResize))

// ─── Data loading ─────────────────────────────────────────────────────────────

async function fetchJob() {
  const { data } = await useApiFetch<Job>(`/api/jobs/${props.jobId}`)
  if (data) {
    job.value = data
    if (data.cover_letter) {
      clText.value = data.cover_letter as string
      clState.value = 'ready'
      isSaved.value = true
    }
    if ((data.match_score ?? 0) >= PERFECT_MATCH_THRESHOLD) {
      shimmeringBadge.value = false
      nextTick(() => { shimmeringBadge.value = true })
      setTimeout(() => { shimmeringBadge.value = false }, 850)
    }
  }
}

onMounted(async () => {
  await fetchJob()
  loadingJob.value = false

  // Check if a generation task is already in flight
  if (clState.value === 'none') {
    const { data } = await useApiFetch<{ status: string; stage: string | null }>(`/api/jobs/${props.jobId}/cover_letter/task`)
    if (data && (data.status === 'queued' || data.status === 'running')) {
      clState.value    = data.status as ClState
      taskStage.value  = data.stage
      generating.value = true
      startPolling()
    }
  }

  await nextTick(autoResize)
})

onUnmounted(() => {
  stopPolling()
  clearTimeout(toastTimer)
})

// Extra type to allow cover_letter field on Job
declare module '../stores/review' {
  interface Job { cover_letter?: string | null }
}
</script>

<style scoped>
.workspace {
  max-width: 1200px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-6) var(--space-12);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.workspace__loading,
.workspace__not-found {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  padding: var(--space-16);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

/* ── Two-panel layout ────────────────────────────────────────────────── */

.workspace__panels {
  display: grid;
  grid-template-columns: 1fr 1.3fr;
  gap: var(--space-6);
  align-items: start;
}

/* ── Job details panel ───────────────────────────────────────────────── */

.workspace__job-panel {
  position: sticky;
  top: var(--space-4);
}

.job-details {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.job-details__badges { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.job-details__title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  color: var(--color-text);
  line-height: 1.25;
}

.job-details__company {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text-muted);
}

.job-details__location { font-weight: 400; }

.job-details__salary {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-success);
}

.job-details__desc {
  font-size: var(--text-sm);
  color: var(--color-text);
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.job-details__desc--clamped {
  display: -webkit-box;
  -webkit-line-clamp: 6;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.expand-btn {
  align-self: flex-start;
  background: transparent;
  border: none;
  color: var(--app-primary);
  font-size: var(--text-xs);
  cursor: pointer;
  padding: 0;
  font-weight: 600;
}

.job-details__gaps { display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: center; }
.gaps-label { font-size: var(--text-xs); color: var(--color-text-muted); font-weight: 600; }
.gap-pill {
  padding: 1px var(--space-2);
  border-radius: 999px;
  font-size: var(--text-xs);
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border-light);
  color: var(--color-text-muted);
}
.gaps-more { font-size: var(--text-xs); color: var(--color-text-muted); }

.job-details__link {
  font-size: var(--text-xs);
  color: var(--app-primary);
  font-weight: 600;
  text-decoration: none;
  align-self: flex-start;
  transition: opacity 150ms ease;
}
.job-details__link:hover { opacity: 0.7; }

/* ── Cover letter panel ──────────────────────────────────────────────── */

.workspace__cl-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.cl-heading {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  color: var(--color-text);
}

/* Empty state */
.cl-empty {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  text-align: center;
}

.cl-empty__hint { font-size: var(--text-sm); color: var(--color-text-muted); max-width: 36ch; }

.cl-empty__actions { display: flex; flex-direction: column; gap: var(--space-2); width: 100%; max-width: 260px; }

/* Generating state */
.cl-generating {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-lg);
  padding: var(--space-10);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  text-align: center;
}

.cl-generating__label {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-text);
}

.cl-generating__hint { font-size: var(--text-xs); color: var(--color-text-muted); }

/* Error state */
.cl-error {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-5);
  background: rgba(192, 57, 43, 0.06);
  border: 1px solid var(--color-error);
  border-radius: var(--radius-lg);
  color: var(--color-error);
  font-size: var(--text-sm);
  font-weight: 600;
}

.cl-error__msg    { font-weight: 700; }
.cl-error__detail { font-size: var(--text-xs); color: var(--color-text-muted); font-weight: 400; }
.cl-error__actions { display: flex; flex-direction: column; gap: var(--space-2); width: 100%; }

/* Editor */
.cl-editor {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-lg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.cl-editor__toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--color-border-light);
  background: var(--color-surface-alt);
}

.cl-editor__wordcount {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-family: var(--font-mono);
}

.cl-editor__textarea {
  width: 100%;
  min-height: 360px;
  padding: var(--space-5);
  border: none;
  background: transparent;
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: 1.7;
  resize: none;
  overflow: hidden;
}

.cl-editor__textarea:focus { outline: none; }

.cl-regen {
  align-self: flex-end;
  color: var(--color-text-muted);
}

/* Download button */
.btn-download {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background 150ms ease, border-color 150ms ease;
  min-height: 44px;
  width: 100%;
  justify-content: center;
}

.btn-download:hover:not(:disabled) { background: var(--app-primary-light); border-color: var(--app-primary); }
.btn-download:disabled { opacity: 0.5; cursor: not-allowed; }

/* Generate button */
.btn-generate {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  background: var(--app-accent);
  color: var(--app-accent-text);
  border: none;
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  font-weight: 700;
  cursor: pointer;
  min-height: 44px;
  transition: background 150ms ease;
  width: 100%;
}

.btn-generate:hover:not(:disabled) { background: var(--app-accent-hover); }
.btn-generate:disabled { opacity: 0.6; cursor: not-allowed; }

/* ── Action bar ──────────────────────────────────────────────────────── */

.workspace__actions {
  display: flex;
  gap: var(--space-3);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border-light);
  margin-top: var(--space-2);
}

.action-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg);
  font-size: var(--text-sm);
  font-weight: 700;
  cursor: pointer;
  border: 2px solid transparent;
  min-height: 48px;
  transition: background 150ms ease, border-color 150ms ease;
}

.action-btn:disabled { opacity: 0.5; cursor: not-allowed; }

.action-btn--apply {
  background: rgba(39, 174, 96, 0.10);
  border-color: var(--color-success);
  color: var(--color-success);
}
.action-btn--apply:hover:not(:disabled)  { background: rgba(39, 174, 96, 0.20); }

.action-btn--reject {
  background: rgba(192, 57, 43, 0.08);
  border-color: var(--color-error);
  color: var(--color-error);
}
.action-btn--reject:hover:not(:disabled) { background: rgba(192, 57, 43, 0.16); }

/* ── Shared badges ───────────────────────────────────────────────────── */

.score-badge {
  padding: 2px var(--space-2);
  border-radius: 999px;
  font-size: var(--text-xs);
  font-weight: 700;
  font-family: var(--font-mono);
}
.score-badge--high     { background: rgba(39,174,96,0.12);   color: var(--score-high);     }
.score-badge--mid-high { background: rgba(43,124,184,0.12);  color: var(--score-mid-high); }
.score-badge--mid      { background: rgba(212,137,26,0.12);  color: var(--score-mid);      }
.score-badge--low      { background: rgba(192,57,43,0.12);   color: var(--score-low);      }

/* Perfect Match shimmer — fires once when a ≥70% job opens */
@keyframes shimmer-badge {
  0%   { box-shadow: 0 0 0 0 rgba(212, 175, 55, 0); background: rgba(39,174,96,0.12); }
  30%  { box-shadow: 0 0 8px 3px rgba(212, 175, 55, 0.6); background: rgba(212, 175, 55, 0.2); }
  100% { box-shadow: 0 0 0 0 rgba(212, 175, 55, 0); background: rgba(39,174,96,0.12); }
}
.score-badge--shimmer { animation: shimmer-badge 850ms ease-out forwards; }

.remote-badge {
  padding: 2px var(--space-2);
  border-radius: 999px;
  font-size: var(--text-xs);
  font-weight: 600;
  background: var(--app-primary-light);
  color: var(--app-primary);
}

/* ── Ghost button ────────────────────────────────────────────────────── */

.btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-2) var(--space-4);
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  min-height: 36px;
  transition: background 150ms ease, color 150ms ease;
  text-decoration: none;
}
.btn-ghost:hover   { background: var(--color-surface-alt); color: var(--color-text); }
.btn-ghost:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-ghost--sm     { font-size: var(--text-xs); padding: var(--space-1) var(--space-3); min-height: 28px; }

/* ── Spinner ─────────────────────────────────────────────────────────── */

.spinner {
  width: 1.2rem;
  height: 1.2rem;
  border: 2px solid var(--color-border);
  border-top-color: var(--app-primary);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  flex-shrink: 0;
}

.spinner--lg { width: 2rem; height: 2rem; border-width: 3px; }

@keyframes spin { to { transform: rotate(360deg); } }

/* ── Toast ───────────────────────────────────────────────────────────── */

.toast {
  position: fixed;
  bottom: var(--space-6);
  left: 50%;
  transform: translateX(-50%);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-5);
  font-size: var(--text-sm);
  color: var(--color-text);
  box-shadow: var(--shadow-lg);
  z-index: 300;
  white-space: nowrap;
}

.toast-enter-active, .toast-leave-active { transition: opacity 250ms ease, transform 250ms ease; }
.toast-enter-from, .toast-leave-to { opacity: 0; transform: translateX(-50%) translateY(8px); }

/* ── Responsive ──────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .workspace__panels {
    grid-template-columns: 1fr;
  }

  .workspace__job-panel {
    position: static;
  }

  .cl-editor__textarea { min-height: 260px; }

  .toast {
    left:  var(--space-4);
    right: var(--space-4);
    transform: none;
    bottom: calc(56px + env(safe-area-inset-bottom) + var(--space-3));
  }
  .toast-enter-from, .toast-leave-to { transform: translateY(8px); }
}

@media (max-width: 600px) {
  .workspace { padding: var(--space-4); }
  .workspace__actions { flex-direction: column; }
}
</style>
