<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { list } from '@/api/resources'
import client from '@/api/client'
import * as pdfjsLib from 'pdfjs-dist'
import pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'

pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerUrl

interface El { type: string; [k: string]: any }
interface Template {
  id: string | null; isDefault: boolean; name: string
  widthMm: number; heightMm: number; bleedMm: number; safeMm: number
  front: El[]; back: El[]
  hasBackground?: boolean; fonts?: string[]
}
interface Company { '@id': string; id: string; name: string; slug: string; brandingConfig: Record<string, string> | unknown[] }
interface Employee { id: string; firstName: string; lastName: string; position: string | null; email: string | null; phone: string | null; mobile: string | null; company: string; department: string | null }

const SCALE = 6 // px pro mm

const companies = ref<Company[]>([])
const selectedCompanyId = ref('')
const tpl = ref<Template | null>(null)
const side = ref<'front' | 'back'>('front')
const selectedIndex = ref<number | null>(null)
const sample = ref<Employee | null>(null)
const employees = ref<Employee[]>([])
const loading = ref(true)
const saving = ref(false)
const saved = ref(false)

const selectedCompany = computed(() => companies.value.find((c) => c['@id'] === selectedCompanyId.value))
const branding = computed(() => {
  const b = selectedCompany.value?.brandingConfig
  return (b && !Array.isArray(b) ? b : {}) as Record<string, string>
})
const els = computed<El[]>(() => (tpl.value ? tpl.value[side.value] : []))
const selected = computed(() => (selectedIndex.value !== null ? els.value[selectedIndex.value] : null))

// Hintergrund-PDF als Bild (pro Seite) für die Canvas-Vorschau
const bgImages = ref<{ front: string | null; back: string | null }>({ front: null, back: null })
const currentBg = computed(() => bgImages.value[side.value])

// Undo-History (Snapshots von front/back)
const history = ref<string[]>([])
function snapshot() {
  if (!tpl.value) return
  history.value.push(JSON.stringify({ front: tpl.value.front, back: tpl.value.back }))
  if (history.value.length > 50) history.value.shift()
}
function undo() {
  const snap = history.value.pop()
  if (!snap || !tpl.value) return
  const { front, back } = JSON.parse(snap)
  tpl.value.front = front
  tpl.value.back = back
  selectedIndex.value = null
}

const BINDINGS = [
  ['fullName', 'Name'], ['firstName', 'Vorname'], ['lastName', 'Nachname'],
  ['position', 'Position'], ['department', 'Abteilung'], ['email', 'E-Mail'],
  ['phone', 'Telefon'], ['mobile', 'Mobil'], ['company.name', 'Firma'],
  ['profileUrl', 'Profil-URL'], ['shortUrl', 'Kurz-URL'],
]

function colorCss(c: any): string {
  if (!c) return '#343434'
  if (c.hex) return c.hex
  switch (c.ref) {
    case 'primary': return branding.value.primaryColor || '#f58220'
    case 'dark': return branding.value.primaryDark || '#222222'
    case 'white': return '#ffffff'
    default: return '#343434'
  }
}
function bindingValue(b: string): string {
  const e = sample.value
  if (!e) return `{${b}}`
  const map: Record<string, string> = {
    fullName: `${e.firstName} ${e.lastName}`.trim(), firstName: e.firstName, lastName: e.lastName,
    position: e.position || '', department: e.department || '', email: e.email || '',
    phone: e.phone || '', mobile: e.mobile || '', 'company.name': selectedCompany.value?.name || '',
    profileUrl: 'profil-url', shortUrl: 'kurz-url',
  }
  // Leere Werte als Platzhalter zeigen, damit das Element editierbar sichtbar bleibt
  return map[b] || `{${b}}`
}
function elText(el: El): string {
  const v = el.type === 'field' ? bindingValue(el.binding) : (el.text ?? '')
  return (el.prefix ?? '') + v
}
function elStyle(el: El): any {
  const base: Record<string, string> = {
    left: el.x * SCALE + 'px', top: el.y * SCALE + 'px',
  }
  if (el.type === 'rect') {
    return { ...base, width: el.w * SCALE + 'px', height: el.h * SCALE + 'px', background: colorCss(el.fill) }
  }
  if (el.type === 'line') {
    return { ...base, width: el.w * SCALE + 'px', height: Math.max(1, (el.lineWidth || 0.3) * SCALE) + 'px', background: colorCss(el.color) }
  }
  if (el.type === 'qr') {
    return { ...base, width: el.w * SCALE + 'px', height: el.h * SCALE + 'px' }
  }
  if (el.type === 'image') {
    return { ...base, width: el.w * SCALE + 'px', height: el.h * SCALE + 'px' }
  }
  // field / text
  return {
    ...base, width: (el.w || 40) * SCALE + 'px',
    fontSize: (el.fontSize || 9) * 0.3528 * SCALE + 'px',
    fontWeight: el.bold ? '700' : '400',
    textAlign: el.align === 'C' ? 'center' : el.align === 'R' ? 'right' : 'left',
    color: colorCss(el.color), lineHeight: '1.1', whiteSpace: 'pre-wrap',
  }
}

// --- Laden ---
async function loadTemplate() {
  if (!selectedCompanyId.value) return
  const cid = selectedCompany.value!.id
  const { data } = await client.get<Template>(`/companies/${cid}/card-template`)
  tpl.value = data
  selectedIndex.value = null
  history.value = []
  sample.value = employees.value.find((e) => e.company === selectedCompanyId.value) ?? null
  await renderBackground()
}

// Rendert das Hintergrund-PDF (Seite 1 = Vorder-, 2 = Rückseite) als Bild für den Canvas
async function renderBackground() {
  bgImages.value = { front: null, back: null }
  if (!tpl.value?.hasBackground) return
  try {
    const res = await client.get(`/companies/${selectedCompany.value!.id}/card-template/background`, { responseType: 'arraybuffer' })
    const doc = await pdfjsLib.getDocument({ data: new Uint8Array(res.data as ArrayBuffer) }).promise
    const pxW = (tpl.value.widthMm + 2 * tpl.value.bleedMm) * SCALE
    for (let p = 1; p <= Math.min(2, doc.numPages); p++) {
      const page = await doc.getPage(p)
      const scale = pxW / page.getViewport({ scale: 1 }).width
      const vp = page.getViewport({ scale })
      const canvas = document.createElement('canvas')
      canvas.width = vp.width
      canvas.height = vp.height
      await page.render({ canvas, canvasContext: canvas.getContext('2d')!, viewport: vp }).promise
      const url = canvas.toDataURL('image/png')
      if (p === 1) bgImages.value.front = url
      else bgImages.value.back = url
    }
  } catch {
    // Vorschau optional – bei Fehler bleibt der Hinweis-Hintergrund
  }
}
async function load() {
  loading.value = true
  ;[companies.value, employees.value] = await Promise.all([
    list<Company>('companies').then((r) => r.member),
    list<Employee>('employees').then((r) => r.member).catch(() => []),
  ])
  if (companies.value[0]) selectedCompanyId.value = companies.value[0]['@id']
  await loadTemplate()
  loading.value = false
}

// --- Drag ---
function startDrag(e: MouseEvent, idx: number) {
  selectedIndex.value = idx
  const el = els.value[idx]
  const sx = e.clientX, sy = e.clientY, ox = el.x, oy = el.y
  const w = tpl.value!.widthMm, h = tpl.value!.heightMm, bl = tpl.value!.bleedMm
  let snapped = false
  const move = (ev: MouseEvent) => {
    if (!snapped) { snapshot(); snapped = true }
    el.x = Math.min(w, Math.max(-bl, +(ox + (ev.clientX - sx) / SCALE).toFixed(1)))
    el.y = Math.min(h, Math.max(-bl, +(oy + (ev.clientY - sy) / SCALE).toFixed(1)))
  }
  const up = () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up) }
  window.addEventListener('mousemove', move); window.addEventListener('mouseup', up)
  e.preventDefault()
}

function startResize(e: MouseEvent, idx: number) {
  selectedIndex.value = idx
  const el = els.value[idx]
  const sx = e.clientX, sy = e.clientY
  const ow = el.w ?? 40, oh = el.h ?? 0
  const hasH = ['rect', 'qr', 'image'].includes(el.type)
  let snapped = false
  const move = (ev: MouseEvent) => {
    if (!snapped) { snapshot(); snapped = true }
    el.w = Math.max(2, +(ow + (ev.clientX - sx) / SCALE).toFixed(1))
    if (hasH) el.h = Math.max(2, +(oh + (ev.clientY - sy) / SCALE).toFixed(1))
  }
  const up = () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up) }
  window.addEventListener('mousemove', move); window.addEventListener('mouseup', up)
  e.preventDefault()
  e.stopPropagation()
}

// --- Elemente hinzufügen / löschen ---
function add(type: string) {
  const defs: Record<string, El> = {
    field: { type: 'field', binding: 'fullName', x: 8, y: 8, w: 50, fontSize: 11, bold: false, align: 'L', color: { ref: 'text' } },
    text: { type: 'text', text: 'Text', x: 8, y: 8, w: 50, fontSize: 9, bold: false, align: 'L', color: { ref: 'text' } },
    qr: { type: 'qr', x: 8, y: 8, w: 18, h: 18 },
    image: { type: 'image', binding: 'logo', x: 8, y: 8, w: 30, h: 10, align: 'L' },
    rect: { type: 'rect', x: 8, y: 8, w: 30, h: 15, fill: { ref: 'primary' } },
    line: { type: 'line', x: 8, y: 8, w: 25, h: 0, lineWidth: 0.5, color: { ref: 'primary' } },
  }
  snapshot()
  els.value.push({ ...defs[type] })
  selectedIndex.value = els.value.length - 1
}
function removeSelected() {
  if (selectedIndex.value === null) return
  snapshot()
  els.value.splice(selectedIndex.value, 1)
  selectedIndex.value = null
}

// --- Farben im Panel ---
const COLOR_KEY = (el: El) => (el.type === 'rect' ? 'fill' : 'color')
function colorMode(el: El): string {
  const c = el[COLOR_KEY(el)]
  if (c?.hex) return 'custom'
  return c?.ref ?? 'text'
}
function setColorMode(el: El, val: string) {
  el[COLOR_KEY(el)] = val === 'custom' ? { hex: '#000000' } : { ref: val }
}
function setColorHex(el: El, hex: string) { el[COLOR_KEY(el)] = { hex } }

// --- Speichern / Vorschau ---
async function save() {
  if (!tpl.value || !selectedCompany.value) return
  saving.value = true; saved.value = false
  try {
    const { data } = await client.put<Template>(`/companies/${selectedCompany.value.id}/card-template`, tpl.value)
    tpl.value = data
    saved.value = true
  } finally { saving.value = false }
}
// --- Druck-Assets: Hintergrund-PDF & Schriften ---
const uploading = ref(false)
function cidPath() { return selectedCompany.value?.id }

async function uploadBackground(e: Event) {
  const file = (e.target as HTMLInputElement).files?.[0]
  if (!file || !tpl.value) return
  uploading.value = true
  try {
    const fd = new FormData(); fd.append('file', file)
    await client.post(`/companies/${cidPath()}/card-template/background`, fd)
    tpl.value.hasBackground = true
    await renderBackground()
  } catch { alert('Upload fehlgeschlagen (nur PDF).') } finally { uploading.value = false; (e.target as HTMLInputElement).value = '' }
}
async function removeBackground() {
  if (!tpl.value || !confirm('Hintergrund-PDF entfernen?')) return
  await client.delete(`/companies/${cidPath()}/card-template/background`)
  tpl.value.hasBackground = false
  bgImages.value = { front: null, back: null }
}
async function uploadFont(e: Event) {
  const file = (e.target as HTMLInputElement).files?.[0]
  if (!file || !tpl.value) return
  const family = (prompt('Name der Schriftart (z. B. „Brand")', file.name.replace(/\.(ttf|otf)$/i, '')) || '').trim()
  if (!family) { (e.target as HTMLInputElement).value = ''; return }
  uploading.value = true
  try {
    const fd = new FormData(); fd.append('file', file); fd.append('family', family)
    await client.post(`/companies/${cidPath()}/card-template/font`, fd)
    tpl.value.fonts = [...(tpl.value.fonts ?? []).filter((f) => f !== family), family]
  } catch { alert('Upload fehlgeschlagen (nur TTF/OTF).') } finally { uploading.value = false; (e.target as HTMLInputElement).value = '' }
}

async function previewPdf() {
  if (!sample.value) { alert('Kein Mitarbeiter in dieser Firma für die Vorschau.'); return }
  const res = await client.get(`/employees/${sample.value.id}/card.pdf`, { responseType: 'blob' })
  window.open(URL.createObjectURL(res.data as Blob), '_blank')
}

function onKey(e: KeyboardEvent) {
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'z') { e.preventDefault(); undo() }
}
onMounted(() => { load(); window.addEventListener('keydown', onKey) })
onUnmounted(() => window.removeEventListener('keydown', onKey))
</script>

<template>
  <section>
    <div class="page-head">
      <div><h1>Visitenkarten-Editor</h1><p class="muted">Layout pro Firma · druckfertiges PDF</p></div>
      <div class="head-actions">
        <select v-if="companies.length > 1" class="input" v-model="selectedCompanyId" @change="loadTemplate">
          <option v-for="c in companies" :key="c['@id']" :value="c['@id']">{{ c.name }}</option>
        </select>
        <button class="btn btn-ghost" :disabled="!history.length" @click="undo" title="Rückgängig (Strg+Z)">↶</button>
        <button class="btn btn-ghost" @click="previewPdf">PDF-Vorschau</button>
        <button class="btn btn-primary" :disabled="saving" @click="save">{{ saving ? 'Speichern…' : 'Speichern' }}</button>
      </div>
    </div>
    <p v-if="saved" class="ok">✓ Vorlage gespeichert</p>

    <div v-if="loading" class="muted">Lädt…</div>
    <div v-else-if="tpl" class="editor">
      <!-- Werkzeugleiste + Canvas -->
      <div>
        <div class="toolbar">
          <div class="tabs">
            <button :class="['tab', { active: side === 'front' }]" @click="side = 'front'; selectedIndex = null">Vorderseite</button>
            <button :class="['tab', { active: side === 'back' }]" @click="side = 'back'; selectedIndex = null">Rückseite</button>
          </div>
          <div class="adds">
            <button class="btn btn-soft btn-sm" @click="add('field')">+ Feld</button>
            <button class="btn btn-soft btn-sm" @click="add('text')">+ Text</button>
            <button class="btn btn-soft btn-sm" @click="add('qr')">+ QR</button>
            <button class="btn btn-soft btn-sm" @click="add('image')">+ Logo</button>
            <button class="btn btn-soft btn-sm" @click="add('rect')">+ Fläche</button>
            <button class="btn btn-soft btn-sm" @click="add('line')">+ Linie</button>
          </div>
        </div>

        <div class="assets">
          <label class="btn btn-ghost btn-sm" :class="{ disabled: uploading }">
            Hintergrund-PDF…
            <input type="file" accept="application/pdf,.pdf" hidden @change="uploadBackground" />
          </label>
          <template v-if="tpl.hasBackground">
            <span class="badge badge-active">Hintergrund aktiv</span>
            <button class="btn btn-ghost btn-sm" @click="removeBackground">Entfernen</button>
          </template>
          <span class="sep"></span>
          <label class="btn btn-ghost btn-sm" :class="{ disabled: uploading }">
            Schrift…
            <input type="file" accept=".ttf,.otf" hidden @change="uploadFont" />
          </label>
          <span v-for="f in (tpl.fonts || [])" :key="f" class="chip">{{ f }}</span>
        </div>

        <div class="stage">
          <!-- Beschnitt-Fläche -->
          <div class="bleed" :style="{ width: (tpl.widthMm + 2 * tpl.bleedMm) * SCALE + 'px', height: (tpl.heightMm + 2 * tpl.bleedMm) * SCALE + 'px' }">
            <!-- Hintergrund-PDF als Bild (füllt inkl. Beschnitt) -->
            <img v-if="currentBg" :src="currentBg" class="bg-img" />
            <!-- Endformat (Trim) -->
            <div class="trim" :class="{ transparent: !!currentBg }" :style="{ left: tpl.bleedMm * SCALE + 'px', top: tpl.bleedMm * SCALE + 'px', width: tpl.widthMm * SCALE + 'px', height: tpl.heightMm * SCALE + 'px' }" @mousedown.self="selectedIndex = null">
              <!-- Hinweis nur, solange Hintergrund-PDF noch nicht gerendert ist -->
              <div v-if="tpl.hasBackground && !currentBg" class="bg-note">Hintergrund-PDF wird geladen…</div>
              <!-- Sicherheitsabstand -->
              <div class="safe" :style="{ inset: tpl.safeMm * SCALE + 'px' }"></div>
              <!-- Elemente -->
              <div
                v-for="(el, i) in els" :key="i"
                class="el" :class="{ sel: i === selectedIndex, ['t-' + el.type]: true }"
                :style="elStyle(el)"
                @mousedown="startDrag($event, i)"
              >
                <template v-if="el.type === 'field' || el.type === 'text'">{{ elText(el) }}</template>
                <div v-else-if="el.type === 'qr'" class="ph-qr">QR</div>
                <img v-else-if="el.type === 'image' && branding.logoUrl" :src="branding.logoUrl" class="logo-img" />
                <div v-else-if="el.type === 'image'" class="ph-logo">Logo</div>
                <div v-if="i === selectedIndex" class="handle" @mousedown.stop="startResize($event, i)"></div>
              </div>
            </div>
          </div>
          <div class="legend muted">
            <span class="lg-bleed"></span> Beschnitt
            <span class="lg-trim"></span> Endformat
            <span class="lg-safe"></span> Sicherheit
          </div>
        </div>
      </div>

      <!-- Eigenschaften -->
      <div class="card props">
        <h3>{{ selected ? 'Eigenschaften' : 'Karten-Einstellungen' }}</h3>

        <!-- Karten-/Format-Einstellungen, wenn kein Element ausgewählt ist -->
        <template v-if="!selected">
          <div class="field"><label>Name der Vorlage</label><input class="input" v-model="tpl.name" /></div>
          <div class="grid2">
            <div class="field"><label>Breite (mm)</label><input class="input" type="number" step="1" v-model.number="tpl.widthMm" /></div>
            <div class="field"><label>Höhe (mm)</label><input class="input" type="number" step="1" v-model.number="tpl.heightMm" /></div>
          </div>
          <div class="grid2">
            <div class="field"><label>Beschnitt (mm)</label><input class="input" type="number" step="0.5" min="0" v-model.number="tpl.bleedMm" /></div>
            <div class="field"><label>Sicherheit (mm)</label><input class="input" type="number" step="0.5" min="0" v-model.number="tpl.safeMm" /></div>
          </div>
          <p class="muted small">Standard: 85×55&nbsp;mm, 2&nbsp;mm Beschnitt. Wird beim Speichern übernommen und gilt fürs Druck-PDF. Element anklicken, um es zu bearbeiten.</p>
        </template>

        <template v-if="selected">
          <div class="field"><label>Typ</label><input class="input" :value="selected.type" disabled /></div>
          <div class="field" v-if="selected.type === 'field'">
            <label>Datenfeld</label>
            <select class="input" v-model="selected.binding">
              <option v-for="[v, l] in BINDINGS" :key="v" :value="v">{{ l }}</option>
            </select>
          </div>
          <div class="field" v-if="selected.type === 'text'"><label>Text</label><input class="input" v-model="selected.text" /></div>

          <div class="grid2">
            <div class="field"><label>X (mm)</label><input class="input" type="number" step="0.5" v-model.number="selected.x" /></div>
            <div class="field"><label>Y (mm)</label><input class="input" type="number" step="0.5" v-model.number="selected.y" /></div>
          </div>
          <div class="grid2" v-if="['rect','qr','image','line'].includes(selected.type)">
            <div class="field"><label>Breite (mm)</label><input class="input" type="number" step="0.5" v-model.number="selected.w" /></div>
            <div class="field" v-if="selected.type !== 'line'"><label>Höhe (mm)</label><input class="input" type="number" step="0.5" v-model.number="selected.h" /></div>
          </div>

          <template v-if="selected.type === 'field' || selected.type === 'text'">
            <div class="grid2">
              <div class="field"><label>Breite (mm)</label><input class="input" type="number" step="0.5" v-model.number="selected.w" /></div>
              <div class="field"><label>Schrift (pt)</label><input class="input" type="number" step="0.5" v-model.number="selected.fontSize" /></div>
            </div>
            <div class="grid2">
              <div class="field"><label>Ausrichtung</label>
                <select class="input" v-model="selected.align"><option value="L">Links</option><option value="C">Mitte</option><option value="R">Rechts</option></select>
              </div>
              <div class="field"><label>Fett</label><select class="input" v-model="selected.bold"><option :value="false">Nein</option><option :value="true">Ja</option></select></div>
            </div>
            <div class="field">
              <label>Schriftart</label>
              <select class="input" v-model="selected.fontFamily">
                <option value="">Standard (Helvetica)</option>
                <option value="times">Times</option>
                <option value="courier">Courier</option>
                <option v-for="f in (tpl.fonts || [])" :key="f" :value="f">{{ f }}</option>
              </select>
            </div>
          </template>

          <div class="field" v-if="selected.type !== 'qr' && selected.type !== 'image'">
            <label>Farbe</label>
            <select class="input" :value="colorMode(selected)" @change="setColorMode(selected, ($event.target as HTMLSelectElement).value)">
              <option value="primary">Primärfarbe</option>
              <option value="dark">Dunkel</option>
              <option value="text">Text</option>
              <option value="white">Weiß</option>
              <option value="custom">Eigene…</option>
            </select>
            <input v-if="colorMode(selected) === 'custom'" type="color" class="color-pick"
                   :value="(selected[COLOR_KEY(selected)] && selected[COLOR_KEY(selected)].hex) || '#000000'"
                   @input="setColorHex(selected, ($event.target as HTMLInputElement).value)" />
          </div>

          <button class="btn btn-ghost btn-sm del" @click="removeSelected">Element löschen</button>
        </template>
      </div>
    </div>
  </section>
</template>

<style scoped>
.page-head { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 1rem; }
.page-head .muted { margin: .2rem 0 0; }
.head-actions { display: flex; gap: .6rem; align-items: center; }
.ok { color: var(--success); font-weight: 600; font-size: .88rem; margin: 0 0 .8rem; }
.editor { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: 1.4rem; align-items: start; }
.toolbar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap; }
.tabs { display: flex; gap: .3rem; }
.tab { padding: .5rem .9rem; border: 1px solid var(--line); background: #fff; border-radius: var(--radius-sm); font-weight: 600; cursor: pointer; font-size: .85rem; }
.tab.active { background: var(--dark); color: #fff; border-color: var(--dark); }
.adds { display: flex; gap: .35rem; flex-wrap: wrap; }

.assets { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: 1rem; padding: .7rem .9rem; background: #fff; border: 1px solid var(--line); border-radius: var(--radius-sm); }
.assets .btn.disabled { opacity: .6; pointer-events: none; }
.assets .sep { width: 1px; height: 20px; background: var(--line); margin: 0 .3rem; }
.chip { background: var(--psc-orange-soft); color: var(--psc-orange-dark); padding: .15rem .6rem; border-radius: 999px; font-size: .78rem; font-weight: 600; }
.bg-note { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; color: #b3b3b3; font-size: 12px; background: repeating-linear-gradient(45deg, #fafafa, #fafafa 8px, #f2f2f2 8px, #f2f2f2 16px); pointer-events: none; }
.bg-note small { font-size: 10px; }
.stage { background: #ececec; border-radius: var(--radius); padding: 1.5rem; display: flex; flex-direction: column; align-items: center; overflow-x: auto; }
.bleed { position: relative; background: #fbe9da; box-shadow: var(--shadow-sm); }
.bg-img { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; }
.trim { position: absolute; background: #fff; overflow: hidden; z-index: 1; }
.trim.transparent { background: transparent; }
.handle { position: absolute; right: -5px; bottom: -5px; width: 10px; height: 10px; background: var(--psc-orange); border: 2px solid #fff; border-radius: 2px; cursor: nwse-resize; z-index: 5; }
.safe { position: absolute; border: 1px dashed #c9c9c9; pointer-events: none; }
.el { position: absolute; cursor: move; user-select: none; }
.el.sel { outline: 2px solid var(--psc-orange); outline-offset: 1px; }
.t-field, .t-text { overflow: hidden; }
.ph-qr { width: 100%; height: 100%; background: repeating-conic-gradient(#222 0% 25%, #fff 0% 50%) 50% / 6px 6px; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 9px; font-weight: 700; }
.ph-logo { width: 100%; height: 100%; border: 1px dashed #bbb; display: flex; align-items: center; justify-content: center; color: #999; font-size: 10px; }
.logo-img { width: 100%; height: 100%; object-fit: contain; }
.legend { display: flex; gap: .8rem; align-items: center; margin-top: 1rem; font-size: .75rem; }
.legend span:first-child, .legend span { display: inline-block; }
.lg-bleed, .lg-trim, .lg-safe { width: 14px; height: 10px; border-radius: 2px; vertical-align: middle; margin-right: -.4rem; }
.lg-bleed { background: #fbe9da; } .lg-trim { background: #fff; border: 1px solid #ccc; } .lg-safe { border: 1px dashed #c9c9c9; }

.props { padding: 1.2rem; }
.props h3 { margin-bottom: .8rem; }
.props .field { min-width: 0; }
.props :deep(.input) { width: 100%; min-width: 0; }
.small { font-size: .82rem; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; }
.grid2 > .field { min-width: 0; }
.color-pick { margin-top: .4rem; width: 100%; height: 34px; border: 1px solid #e0e0e0; border-radius: 8px; cursor: pointer; }
.del { margin-top: .8rem; color: var(--danger); }
</style>
