<script setup>
import { toast } from 'bulma-toast'

const { getMenuType } = useRoleMenu()
const tabs = useTabs()
const activeSave = useActiveSave()

const props = defineProps({ modelValue: Boolean, searchQuery: String })
const emit = defineEmits(['update:modelValue', 'update:searchQuery'])

const defaultSideBarMenu = ref('Menu')
const search = ref(props.searchQuery || '')
const searchRef = ref(null)
const searchTimer = ref(undefined)
const isSearching = ref(false)
const hasSearched = ref(false)

// ── entity presentation config ────────────────────────────────────────────────
const KIND_LABEL = { customer: 'Kunde', vendor: 'Lieferant', employee: 'Mitarbeiter', prospect: 'Interessent', salesrep: 'Vertrieb', fulfillment: 'Fulfillment' }
const INOUT_LABEL = { shipment: 'Lieferung', receipt: 'Wareneingang', customer_return: 'Kundenretoure' }

const ENTITY = {
  m_product: { label: 'Produkte', icon: 'ti-package', color: '#10b981', idField: 'm_product_id',
    title: s => s.product_name, sub: s => s.value,
    route: s => `/materials/products/${s.m_product_id}/edit`,
    tags: s => [s.sku && { t: 'SKU ' + s.sku }, s.mpn && { t: 'MPN ' + s.mpn }, s.jfsku && { t: 'JF ' + s.jfsku }].filter(Boolean) },
  c_bpartner: { label: 'Partner', icon: 'ti-users', color: '#3b82f6', idField: 'c_bpartner_id',
    title: s => s.name, sub: s => s.value,
    route: s => `/partners/partners/${s.c_bpartner_id}/edit`,
    tags: s => (s.kinds || []).map(k => ({ t: KIND_LABEL[k] || k, cls: 'kind-' + k })) },
  c_order: { label: 'Aufträge', icon: 'ti-file-invoice', color: '#f59e0b', idField: 'c_order_id',
    title: s => s.documentno, sub: s => s.poreference, date: s => s.dateordered,
    route: s => s.issotrx ? `/sales/orders/${s.c_order_id}/edit` : `/procurements/orders/${s.c_order_id}/edit`,
    tags: s => [{ t: s.issotrx ? 'Verkauf' : 'Einkauf', cls: s.issotrx ? 'tag-sales' : 'tag-purchase' }, s.isfulfillmentorder && { t: 'Fulfillment' }].filter(Boolean) },
  m_inout: { label: 'Lieferungen / Retouren', icon: 'ti-truck-delivery', color: '#06b6d4', idField: 'm_inout_id',
    title: s => s.documentno, sub: s => s.trackingno,
    route: s => s.inout_type === 'customer_return' ? `/sales/returns/${s.m_inout_id}/view` : (s.issotrx ? `/sales/shipments/${s.m_inout_id}/view` : `/procurements/receipts/${s.m_inout_id}/view`),
    tags: s => [{ t: INOUT_LABEL[s.inout_type] || 'Lieferung', cls: 'tag-' + (s.inout_type || 'shipment') }] },
  m_rma: { label: 'RMA', icon: 'ti-arrow-back-up', color: '#ef4444', idField: 'm_rma_id',
    title: s => s.documentno, sub: () => '',
    route: s => s.issotrx === false ? `/procurements/vendor-rma/${s.m_rma_id}/edit` : `/sales/customer-rma/${s.m_rma_id}/edit`,
    tags: () => [] },
  c_invoice: { label: 'Rechnungen', icon: 'ti-receipt', color: '#8b5cf6', idField: 'c_invoice_id',
    title: s => s.documentno, sub: s => s.poreference, date: s => s.dateinvoiced,
    route: s => s.issotrx ? `/sales/invoices/${s.c_invoice_id}/edit` : `/procurements/invoices/${s.c_invoice_id}/edit`,
    tags: s => [{ t: s.issotrx ? 'Verkauf' : 'Einkauf', cls: s.issotrx ? 'tag-sales' : 'tag-purchase' }] },
  c_payment: { label: 'Zahlungen', icon: 'ti-cash', color: '#22c55e', idField: 'c_payment_id',
    title: s => s.documentno, sub: s => s.checkno,
    route: s => s.isreceipt ? `/sales/payments/${s.c_payment_id}/view` : `/procurements/payments/${s.c_payment_id}/view`,
    tags: s => [{ t: s.isreceipt ? 'Eingang' : 'Ausgang', cls: s.isreceipt ? 'tag-sales' : 'tag-purchase' }] },
  r_request: { label: 'Anfragen', icon: 'ti-help-circle', color: '#ec4899', idField: 'r_request_id',
    title: s => s.documentno || ('#' + s.r_request_id), sub: s => s.summary,
    route: s => `/requests/requests/${s.r_request_id}/edit`, tags: () => [] },
  ad_user: { label: 'Kontakte', icon: 'ti-user', color: '#64748b', idField: 'ad_user_id',
    title: s => s.name, sub: s => s.email,
    route: s => `/users/users/${s.ad_user_id}/view`, tags: () => [] },
}
const GROUP_ORDER = ['m_product', 'c_bpartner', 'c_order', 'm_inout', 'm_rma', 'c_invoice', 'c_payment', 'r_request', 'ad_user']
const REL_LABEL = {
  partner: 'Partner', orders: 'Aufträge', order: 'Auftrag', shipments: 'Lieferungen', invoices: 'Rechnungen',
  invoice: 'Rechnung', payments: 'Zahlungen', payment: 'Zahlung', rmas: 'RMA', rma: 'RMA', requests: 'Anfragen',
  contacts: 'Kontakte', product: 'Produkt', shipment: 'Lieferung', original_shipment: 'Original-Lieferung',
  return_movements: 'Retouren-Bewegungen',
}

// ── state ─────────────────────────────────────────────────────────────────────
const results = ref({})       // index -> [_source]
const qtyMap = ref({})        // m_product_id -> {qtyonhand,...}
const showBom = ref(false)    // BOM articles (IsBOM / isJtlBom) are hidden by default
const selected = ref(null)    // { entity, source, key }
const detail = ref({ loading: false, data: {} })
const productCard = ref({ loading: false, data: null }) // live product detail (iDempiere) for m_product hits
const isNarrow = ref(false)   // phone → use Strapi 'small' image instead of 'medium'

const fmtDate = (v) => {
  if (!v) return ''
  const d = new Date(String(v).replace(' ', 'T'))
  return isNaN(d.getTime()) ? String(v) : d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
}
const fmtQty = (n) => Number(n || 0).toLocaleString('de-DE')

// fullscreen image lightbox (click the product image to open)
const lightbox = ref({ open: false, src: '', alt: '' })
const openImage = (image) => {
  if (!image) return
  const src = image.full || image.large || image.medium || image.small || image.thumbnail
  if (!src) return
  lightbox.value = { open: true, src, alt: productCard.value?.data?.name || '' }
}
const closeImage = () => { lightbox.value = { open: false, src: '', alt: '' } }

// temporarily hidden from the tile grid (still searched + still shown as related docs)
// leaves 4 visible tiles (Produkte, Partner, Aufträge, Anfragen) → 2×2 grid (2 top, 2 bottom)
const HIDDEN_TILES = ['ad_user', 'c_payment', 'c_invoice', 'm_inout', 'm_rma']
// BOM flags come from the async qty enrichment (not in ES), so the filter is client-side
const visibleHits = (idx, hits) => (idx === 'm_product' && !showBom.value)
  ? hits.filter(s => !qtyMap.value[s.m_product_id]?.isBom)
  : hits
// tiles in fixed order/positions (empty ones keep their place)
const tiles = computed(() => GROUP_ORDER.filter(idx => !HIDDEN_TILES.includes(idx))
  .map(idx => {
    const all = results.value[idx] || []
    const hits = visibleHits(idx, all)
    return { idx, cfg: ENTITY[idx], hits, hiddenBom: all.length - hits.length }
  }))
const totalResults = computed(() => tiles.value.reduce((a, t) => a + t.hits.length, 0))
watch(showBom, (v) => { try { localStorage.setItem('logship_search_show_bom', v ? '1' : '0') } catch { /* private mode */ } })

const keyOf = (idx, s) => `${idx}:${s[ENTITY[idx].idField]}`

const highlight = (text) => {
  const term = (search.value || '').replace(/\*/g, '').trim()
  if (!text || !term) return text ?? ''
  const re = new RegExp(`(${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi')
  return String(text).replace(re, '<mark class="sm-hl">$1</mark>')
}

const throttleSearch = () => {
  clearTimeout(searchTimer.value)
  searchTimer.value = setTimeout(() => { if (search.value !== '') searchFetch(search.value) }, 400)
}

const searchFetch = async (query) => {
  isSearching.value = true
  hasSearched.value = true
  selected.value = null
  detail.value = { loading: false, data: {} }
  productCard.value = { loading: false, data: null }
  try {
    const res = await $fetch(`/api/search/${encodeURIComponent(query)}`, { query: { defaultSideBarMenu: defaultSideBarMenu.value } })
    const next = {}
    for (const idx of GROUP_ORDER) next[idx] = (res?.[idx]?.hits?.hits || []).map(h => h._source)
    results.value = next
    enrichProductQty(next.m_product || [])
  } catch { results.value = {} }
  isSearching.value = false
}

const enrichProductQty = async (products) => {
  const ids = products.map(p => p.m_product_id).filter(Boolean)
  if (!ids.length) return
  try {
    const res = await $fetch('/api/materials/products/quantities', { method: 'POST', body: { ids } })
    qtyMap.value = { ...qtyMap.value, ...(res?.quantities || {}) }
  } catch { /* best effort */ }
}

// click a result -> preview in the right column (stays in modal).
// Products show a live detail card (fetched fresh from iDempiere); everything
// else shows its related documents.
const selectResult = async (idx, s) => {
  const k = keyOf(idx, s)
  selected.value = { entity: idx, source: s, key: k }
  const id = s[ENTITY[idx].idField]

  if (idx === 'm_product') {
    detail.value = { loading: false, data: {} }
    productCard.value = { loading: true, data: null }
    try {
      const card = await $fetch(`/api/search/product-card/${id}`)
      if (selected.value?.key === k) productCard.value = { loading: false, data: card }
    } catch { if (selected.value?.key === k) productCard.value = { loading: false, data: null } }
    return
  }

  productCard.value = { loading: false, data: null }
  detail.value = { loading: true, data: {} }
  try {
    const res = await $fetch(`/api/search/related/${idx}/${id}`, { query: { defaultSideBarMenu: defaultSideBarMenu.value } })
    if (selected.value?.key === k) detail.value = { loading: false, data: res?.related || {} }
  } catch { detail.value = { loading: false, data: {} } }
}

// link permissions: limited roles ("c") may only open Orders & Products;
// non-limited roles may open everything.
const isLimited = computed(() => defaultSideBarMenu.value === 'c')
const canOpen = (entity) => !isLimited.value || entity === 'c_order' || entity === 'm_product'

// open a record IN-APP: push/activate a tab in the app tab bar and navigate in the
// same browser window (no window.open) — the modal closes.
const openRoute = (route, text) => {
  if (activeSave.value) {
    toast({ message: 'Save button is active, please submit the data before leaving!', type: 'is-warning', duration: 2000 })
    return
  }
  tabs.value = tabs.value.map(t => ({ ...t, active: false }))
  const existingTab = tabs.value.find(t => t.link === route)
  if (existingTab) existingTab.active = true
  else tabs.value.push({ link: route, text: String(text || '').substring(0, 40) || route, active: true })
  try { localStorage.setItem('tabs', JSON.stringify(tabs.value)) } catch { /* private mode */ }
  closeSearchModal()
  navigateTo(route)
}
// row/„Öffnen" click — falls back to the info preview when the record has no
// route or the role may not open it
const openRecord = (idx, s) => {
  const route = canOpen(idx) ? ENTITY[idx].route(s) : null
  if (!route) { selectResult(idx, s); return }
  openRoute(route, ENTITY[idx].title(s) || ENTITY[idx].label)
}
const openRelated = (doc) => {
  if (!canOpen(doc.index)) return
  const route = ENTITY[doc.index]?.route(doc)
  if (route) openRoute(route, relTitle(doc))
}
const relTitle = (doc) => ENTITY[doc.index]?.title(doc) || doc.documentno || doc.name || ('#' + doc[ENTITY[doc.index]?.idField])
// related rows are compact: document entries show docno + date + amount on ONE
// line; a sub line only where it identifies the record (partner/product value,
// contact email). Free text (request summary, poreference, trackingno) stays out —
// it can be arbitrarily long and would blow up the fixed-width detail column.
const REL_SUB = ['ad_user', 'c_bpartner', 'm_product']
const relSub = (doc) => REL_SUB.includes(doc.index) ? ENTITY[doc.index]?.sub(doc) : ''
const relDate = (doc) => fmtDate(doc.dateordered || doc.dateinvoiced || doc.datetrx || doc.movementdate || doc.created)
const relAmt = (doc) => {
  const v = doc.grandtotal ?? doc.payamt ?? doc.amt
  return (v === undefined || v === null) ? ''
    : Number(v).toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €'
}
// contacts: phone 1 + 2 (phone2 needs the ad_user index resynced with the field)
const relPhones = (doc) => doc.index === 'ad_user'
  ? [doc.phone, doc.phone2].map(p => String(p ?? '').trim()).filter(Boolean)
  : []

// ── modal plumbing ────────────────────────────────────────────────────────────
const showSearchModal = ref(props.modelValue ?? false)
watch(() => props.modelValue, () => { showSearchModal.value = props.modelValue })
watch(showSearchModal, () => { setTimeout(() => searchRef.value?.focus(), 100) })
watch(() => props.searchQuery, () => { search.value = props.searchQuery; throttleSearch() })

const closeSearchModal = () => {
  showSearchModal.value = false
  search.value = ''
  emit('update:modelValue', false)
  emit('update:searchQuery', '')
}

onMounted(() => {
  defaultSideBarMenu.value = getMenuType()
  try { showBom.value = localStorage.getItem('logship_search_show_bom') === '1' } catch { /* private mode */ }
  const onResize = () => { isNarrow.value = window.innerWidth <= 860 }
  onResize()
  const onKey = (e) => {
    if (e.key !== 'Escape') return
    if (lightbox.value.open) { closeImage(); return }
    if (showSearchModal.value) closeSearchModal()
  }
  window.addEventListener('keydown', onKey)
  window.addEventListener('resize', onResize)
  onBeforeUnmount(() => { window.removeEventListener('keydown', onKey); window.removeEventListener('resize', onResize) })
})
</script>

<template>
  <div class="sm-overlay" :class="{ active: showSearchModal }" :style="{ display: showSearchModal ? 'flex' : 'none' }" @click.self="closeSearchModal">
    <div class="sm-shell">
      <!-- header -->
      <header class="sm-head">
        <div class="sm-search">
          <i class="ti ti-search"></i>
          <input ref="searchRef" type="search" v-model="search" @input="throttleSearch" @keyup.enter="throttleSearch"
                 placeholder="Suchen — Produkt, Partner, Auftrag, Lieferung, Tracking-Nr., Marktplatz-ID …" />
          <span v-if="isSearching" class="sm-busy"><i class="ti ti-loader-2 spin"></i></span>
        </div>
        <div class="sm-head-meta">
          <span v-if="hasSearched && !isSearching" class="sm-total">{{ totalResults }}</span>
          <button class="sm-x" @click="closeSearchModal"><i class="ti ti-x"></i></button>
        </div>
      </header>

      <!-- body: tiles + detail -->
      <div class="sm-body">
        <div v-if="!hasSearched" class="sm-state sm-idle">
          <i class="ti ti-command"></i><h4>Globale Suche</h4><p>Tippen, um über alle Belege zu suchen. Klicken zeigt verknüpfte Dokumente.</p>
        </div>

        <template v-else>
          <!-- TILES -->
          <main class="sm-tiles">
            <section v-for="g in tiles" :key="g.idx" class="sm-tile" :class="{ 'is-empty': !g.hits.length }" :style="{ '--accent': g.cfg.color }">
              <div class="sm-tile-head">
                <span class="sm-tile-ic"><i class="ti" :class="g.cfg.icon"></i></span>
                <span class="sm-tile-label">{{ g.cfg.label }}</span>
                <label v-if="g.idx === 'm_product'" class="sm-bom-toggle" title="Stücklisten-Artikel (BOM / JTL-BOM) anzeigen" @click.stop>
                  <input type="checkbox" v-model="showBom" />
                  <span>BOM<template v-if="g.hiddenBom"> ({{ g.hiddenBom }})</template></span>
                </label>
                <span class="sm-tile-count">{{ g.hits.length }}</span>
              </div>
              <div class="sm-tile-rows">
                <div v-for="s in g.hits" :key="keyOf(g.idx, s)" class="sm-row"
                     :class="{ active: selected && selected.key === keyOf(g.idx, s) }" @click="selectResult(g.idx, s)">
                  <div class="sm-row-body">
                    <div class="sm-row-head">
                      <div class="sm-row-title" v-html="highlight(g.cfg.title(s))"></div>
                      <span v-if="g.cfg.date && fmtDate(g.cfg.date(s))" class="sm-row-date">{{ fmtDate(g.cfg.date(s)) }}</span>
                    </div>
                    <div v-if="g.cfg.sub(s)" class="sm-row-sub" v-html="highlight(g.cfg.sub(s))"></div>
                    <!-- meta tags (SKU/MPN/JF) + BOM flag on their own line, so their varying
                         length never shifts the qty columns below -->
                    <div v-if="g.cfg.tags(s).length || (g.idx === 'm_product' && qtyMap[s.m_product_id]?.isBom)" class="sm-chips">
                      <span v-for="(tag, i) in g.cfg.tags(s)" :key="i" class="sm-chip" :class="tag.cls">{{ tag.t }}</span>
                      <span v-if="g.idx === 'm_product' && qtyMap[s.m_product_id]?.isBom" class="sm-chip sm-chip-bom" title="Stückliste (BOM / JTL-BOM)">⚙ BOM</span>
                    </div>
                    <!-- qty chips in a fixed 3-column grid → identical positions on every product row -->
                    <div v-if="g.idx === 'm_product' && qtyMap[s.m_product_id]" class="sm-qty-grid">
                      <span class="sm-qchip" :class="qtyMap[s.m_product_id].qtyonhand > 0 ? 'q-oh' : 'q-zero'"><i>Bestand</i><b>{{ qtyMap[s.m_product_id].qtyonhand }}</b></span>
                      <span class="sm-qchip" :class="qtyMap[s.m_product_id].qtyreserved > 0 ? 'q-res' : 'q-zero'"><i>Res</i><b>{{ qtyMap[s.m_product_id].qtyreserved }}</b></span>
                      <span class="sm-qchip" :class="qtyMap[s.m_product_id].qtyordered > 0 ? 'q-ord' : 'q-zero'"><i>Best</i><b>{{ qtyMap[s.m_product_id].qtyordered }}</b></span>
                    </div>
                  </div>
                  <button v-if="canOpen(g.idx)" class="sm-open" title="Im App-Tab öffnen" @click.stop="openRecord(g.idx, s)">
                    <i class="ti ti-external-link"></i>
                  </button>
                </div>
                <div v-if="!g.hits.length" class="sm-tile-empty">
                  <i class="ti ti-minus"></i> {{ g.hiddenBom ? g.hiddenBom + ' BOM-Artikel ausgeblendet' : 'Keine Treffer' }}
                </div>
              </div>
            </section>
          </main>

          <!-- DETAIL / related -->
          <aside class="sm-detail" :class="{ open: !!selected }">
            <div v-if="!selected" class="sm-detail-empty">
              <i class="ti ti-affiliate"></i>
              <p>Ergebnis anklicken, um verknüpfte Dokumente zu sehen</p>
            </div>
            <template v-else>
              <div class="sm-detail-head" :style="{ '--accent': ENTITY[selected.entity].color }">
                <span class="sm-detail-ic"><i class="ti" :class="ENTITY[selected.entity].icon"></i></span>
                <div class="sm-detail-titles">
                  <div class="sm-detail-title">{{ ENTITY[selected.entity].title(selected.source) }}</div>
                  <div class="sm-detail-sub">{{ ENTITY[selected.entity].label }}</div>
                </div>
                <button v-if="canOpen(selected.entity) && ENTITY[selected.entity].route(selected.source)" class="sm-detail-open"
                        @click="openRecord(selected.entity, selected.source)"><i class="ti ti-external-link"></i> Öffnen</button>
              </div>

              <div class="sm-detail-body">
                <!-- PRODUCT: live detail card from iDempiere -->
                <template v-if="selected.entity === 'm_product'">
                  <div v-if="productCard.loading" class="sm-detail-loading"><i class="ti ti-loader-2 spin"></i> Produktdaten…</div>
                  <div v-else-if="productCard.data && productCard.data.qty" class="sm-pc">
                    <!-- head: article name (+ BOM tag) on the left, clickable image on the right -->
                    <div class="sm-pc-head">
                      <div class="sm-pc-headinfo">
                        <div class="sm-pc-name">{{ productCard.data.name || ENTITY['m_product'].title(selected.source) }}</div>
                        <div v-if="qtyMap[selected.source?.m_product_id]?.isBom" class="sm-pc-tags">
                          <span class="sm-chip sm-chip-bom" title="Stückliste (BOM / JTL-BOM)">⚙ BOM</span>
                        </div>
                      </div>
                      <div class="sm-pc-side">
                        <button v-if="productCard.data.image" type="button" class="sm-pc-media sm-pc-media-btn"
                                @click="openImage(productCard.data.image)" title="Bild in voller Größe anzeigen">
                          <img :src="isNarrow ? (productCard.data.image.small || productCard.data.image.medium)
                                              : (productCard.data.image.medium || productCard.data.image.small)"
                               :alt="productCard.data.name" loading="lazy" />
                          <span class="sm-pc-zoom"><i class="ti ti-zoom-in"></i></span>
                        </button>
                        <div v-else class="sm-pc-media sm-pc-noimg"><i class="ti ti-photo-off"></i></div>
                      </div>
                    </div>

                    <hr class="sm-pc-sep" />

                    <div class="sm-pc-stats">
                      <div class="sm-pc-stat" :class="productCard.data.qty.onhand > 0 ? 'q-oh' : 'q-zero'"><b>{{ fmtQty(productCard.data.qty.onhand) }}</b><span>Bestand</span></div>
                      <div class="sm-pc-stat" :class="productCard.data.qty.available > 0 ? 'q-avail' : 'q-zero'"><b>{{ fmtQty(productCard.data.qty.available) }}</b><span>Verfügbar</span></div>
                      <div class="sm-pc-stat" :class="productCard.data.qty.reserved > 0 ? 'q-res' : 'q-zero'"><b>{{ fmtQty(productCard.data.qty.reserved) }}</b><span>Reserviert</span></div>
                      <div class="sm-pc-stat" :class="productCard.data.qty.ordered > 0 ? 'q-ord' : 'q-zero'"><b>{{ fmtQty(productCard.data.qty.ordered) }}</b><span>Bestellt</span></div>
                      <div class="sm-pc-stat" :class="productCard.data.qty.returnOnHand > 0 ? 'q-ret' : 'q-zero'"><b>{{ fmtQty(productCard.data.qty.returnOnHand) }}</b><span>Nicht versandfähig</span></div>
                    </div>

                    <!-- all locators the article is stored on (below the qty cards) — hidden for limited roles -->
                    <template v-if="!isLimited">
                      <hr class="sm-pc-sep" />
                      <div class="sm-pc-locators">
                        <div class="sm-pc-loc-head"><i class="ti ti-map-pin"></i> Lagerplätze<b v-if="productCard.data.locators?.length">{{ productCard.data.locators.length }}</b></div>
                        <div v-if="productCard.data.locators?.length" class="sm-pc-loc-list">
                          <div v-for="(loc, i) in productCard.data.locators" :key="i" class="sm-pc-loc" :class="{ 'is-return': loc.isReturn }">
                            <span class="sm-pc-loc-name"><i class="ti ti-map-pin"></i>{{ loc.name }}<em v-if="loc.isReturn">Retoure</em></span>
                            <span class="sm-pc-loc-qty">{{ fmtQty(loc.onhand) }}</span>
                          </div>
                        </div>
                        <div v-else class="sm-pc-loc-empty"><i class="ti ti-map-pin-off"></i> kein Lagerplatz belegt</div>
                      </div>
                    </template>

                    <hr class="sm-pc-sep" />

                    <!-- product identification fields (SKU/JFSKU/Wert/UPC/ASIN/MPN + Erstellt) -->
                    <div class="sm-pc-fields">
                      <div v-if="productCard.data.sku" class="sm-pc-field"><span>SKU</span><b>{{ productCard.data.sku }}</b></div>
                      <div v-if="productCard.data.jfsku" class="sm-pc-field"><span>JFSKU</span><b>{{ productCard.data.jfsku }}</b></div>
                      <div v-if="productCard.data.value" class="sm-pc-field"><span>Wert</span><b>{{ productCard.data.value }}</b></div>
                      <div v-if="productCard.data.upc" class="sm-pc-field"><span>UPC</span><b>{{ productCard.data.upc }}</b></div>
                      <div v-if="productCard.data.asin" class="sm-pc-field"><span>ASIN</span><b>{{ productCard.data.asin }}</b></div>
                      <div v-if="productCard.data.mpn" class="sm-pc-field"><span>MPN</span><b>{{ productCard.data.mpn }}</b></div>
                      <div v-if="productCard.data.created" class="sm-pc-field"><span>Erstellt</span><b>{{ fmtDate(productCard.data.created) }}</b></div>
                    </div>
                  </div>
                  <div v-else class="sm-detail-empty small"><i class="ti ti-package-off"></i><p>keine Produktdaten</p></div>
                </template>

                <!-- everything else: related documents -->
                <template v-else>
                  <div v-if="detail.loading" class="sm-detail-loading"><i class="ti ti-loader-2 spin"></i> verknüpfte Dokumente…</div>
                  <template v-else>
                    <div class="sm-rel-grid">
                      <div v-for="(docs, key) in detail.data" :key="key" class="sm-rel">
                        <div class="sm-rel-head"><i class="ti" :class="ENTITY[docs[0]?.index]?.icon || 'ti-link'"></i>{{ REL_LABEL[key] || key }}<b>{{ docs.length }}</b></div>
                        <div class="sm-rel-list">
                          <div v-for="(doc, i) in docs" :key="i" class="sm-rel-item" :class="{ 'no-link': !canOpen(doc.index) }"
                               @click="canOpen(doc.index) && openRelated(doc)"
                               :style="{ '--accent': ENTITY[doc.index]?.color }">
                            <span class="sm-rel-dot"></span>
                            <span class="sm-rel-text">
                              <span class="sm-rel-line">
                                <b>{{ relTitle(doc) }}</b>
                                <em v-if="relDate(doc)" class="sm-rel-date">{{ relDate(doc) }}</em>
                                <span v-if="relAmt(doc)" class="sm-rel-amt">{{ relAmt(doc) }}</span>
                              </span>
                              <i v-if="relSub(doc)">{{ relSub(doc) }}</i>
                              <span v-for="(p, pi) in relPhones(doc)" :key="'ph' + pi" class="sm-rel-phone">
                                <i class="ti ti-phone"></i>{{ p }}
                              </span>
                            </span>
                            <i v-if="canOpen(doc.index)" class="ti ti-external-link sm-rel-go"></i>
                          </div>
                        </div>
                      </div>
                    </div>
                    <div v-if="!Object.keys(detail.data).length" class="sm-detail-empty small">
                      <i class="ti ti-link-off"></i><p>keine verknüpften Dokumente</p>
                    </div>
                  </template>
                </template>
              </div>
            </template>
          </aside>
        </template>
      </div>
    </div>

    <!-- fullscreen image lightbox -->
    <div v-if="lightbox.open" class="sm-lightbox" @click="closeImage">
      <img :src="lightbox.src" :alt="lightbox.alt" @click.stop />
      <button class="sm-lightbox-x" title="Schließen" @click.stop="closeImage"><i class="ti ti-x"></i></button>
    </div>
  </div>
</template>

<style scoped>
.sm-overlay { position: fixed; inset: 0; z-index: 1000; background: rgba(15, 23, 42, .55); backdrop-filter: blur(4px);
  display: flex; align-items: stretch; justify-content: center; padding: 0; }
.sm-shell { width: 100vw; height: 100vh; max-height: 100vh; display: flex; flex-direction: column;
  background: var(--theme-bg-primary, #fff); border-radius: 0; overflow: hidden; animation: sm-pop .16s ease; }
@keyframes sm-pop { from { opacity: 0; } }

/* header */
.sm-head { display: flex; align-items: center; gap: 12px; padding: 14px 16px;
  background: linear-gradient(120deg, #6366f1, #8b5cf6 55%, #d946ef); color: #fff; }
.sm-search { flex: 1; display: flex; align-items: center; gap: 10px; background: rgba(255, 255, 255, .16);
  border: 1px solid rgba(255, 255, 255, .25); border-radius: 12px; padding: 10px 14px; }
.sm-search i { font-size: 1.15rem; opacity: .9; }
.sm-search input { flex: 1; background: transparent; border: 0; outline: 0; color: #fff; font-size: 1rem; }
.sm-search input::placeholder { color: rgba(255, 255, 255, .75); }
.sm-busy .spin { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.sm-head-meta { display: flex; align-items: center; gap: 10px; }
.sm-total { background: rgba(255, 255, 255, .22); border-radius: 20px; padding: 3px 11px; font-size: .82rem; font-weight: 600; }
.sm-x { width: 36px; height: 36px; border: 0; border-radius: 10px; background: rgba(255, 255, 255, .16); color: #fff; cursor: pointer; transition: .15s; }
.sm-x:hover { background: rgba(255, 255, 255, .32); }

/* body */
.sm-body { display: flex; min-height: 0; flex: 1; overflow: hidden; }

/* states */
.sm-state { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  padding: 4rem 2rem; color: #64748b; text-align: center; }
.sm-state > i { font-size: 3.2rem; color: #cbd5e1; }
.sm-state h4 { margin: 0; color: #475569; } .sm-state p { margin: 0; font-size: .88rem; max-width: 360px; }
.sm-loader { width: 38px; height: 38px; border: 3px solid #e2e8f0; border-top-color: #8b5cf6; border-radius: 50%; animation: spin .8s linear infinite; }

/* tiles area */
.sm-tiles { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; padding: 16px;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  /* top row taller, bottom row 25% shorter (the freed space goes to the top) */
  grid-template-rows: minmax(0, 1.25fr) minmax(0, 0.75fr); grid-auto-rows: 1fr; gap: 16px; }
.sm-tile { display: flex; flex-direction: column; border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  border-radius: 14px; overflow: hidden; background: var(--theme-bg-primary, #fff);
  box-shadow: 0 1px 2px rgba(2, 6, 23, .04); transition: box-shadow .18s, transform .18s; min-height: 0; }
.sm-tile:hover { box-shadow: 0 12px 28px -12px color-mix(in srgb, var(--accent) 55%, transparent); }
.sm-tile.is-empty { opacity: .72; border-style: dashed; }
.sm-tile.is-empty:hover { box-shadow: 0 1px 2px rgba(2, 6, 23, .04); }
.sm-tile-empty { display: flex; align-items: center; justify-content: center; gap: 6px; padding: 20px 12px; color: #94a3b8; font-size: .78rem; }
.sm-tile-head { display: flex; align-items: center; gap: 9px; padding: 11px 13px; font-weight: 700; font-size: .9rem;
  color: color-mix(in srgb, var(--accent) 78%, #0f172a); background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 22%, transparent); position: sticky; top: 0; }
.sm-tile-ic { width: 28px; height: 28px; display: grid; place-items: center; border-radius: 9px; color: #fff;
  background: var(--accent); font-size: 1rem; }
.sm-tile-label { flex: 1; }
.sm-tile-count { background: color-mix(in srgb, var(--accent) 20%, transparent); color: color-mix(in srgb, var(--accent) 80%, #0f172a);
  border-radius: 20px; padding: 1px 9px; font-size: .76rem; }
/* per-tile BOM toggle (products only) */
.sm-bom-toggle { display: flex; align-items: center; gap: 5px; font-size: .68rem; font-weight: 700; cursor: pointer;
  color: color-mix(in srgb, var(--accent) 72%, #0f172a); background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent); border-radius: 7px; padding: 2px 8px; user-select: none; white-space: nowrap; }
.sm-bom-toggle input { accent-color: var(--accent); margin: 0; cursor: pointer; }
.sm-tile-rows { flex: 1; min-height: 0; overflow-y: auto; padding: 5px; }

.sm-row { display: flex; align-items: center; gap: 6px; padding: 8px 9px; border-radius: 10px; cursor: pointer; transition: background .12s; }
.sm-row:hover { background: color-mix(in srgb, var(--accent) 9%, transparent); }
.sm-row.active { background: color-mix(in srgb, var(--accent) 16%, transparent);
  outline: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); }
.sm-row-body { flex: 1; min-width: 0; }
.sm-row-head { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.sm-row-head .sm-row-title { flex: 1; }
.sm-row-date { flex: none; font-size: .66rem; color: #94a3b8; white-space: nowrap; }
.sm-row-title { font-size: .86rem; font-weight: 600; color: var(--theme-text-primary, #0f172a); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sm-row-sub { font-size: .74rem; color: #94a3b8; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sm-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.sm-chip { font-size: .62rem; font-weight: 700; padding: 1px 6px; border-radius: 5px; background: #eef2f7; color: #475569; white-space: nowrap; }
.tag-sales, .kind-customer { background: #dcfce7; color: #047857; }
.tag-purchase, .kind-vendor { background: #fee2e2; color: #b91c1c; }
.tag-customer_return { background: #fef3c7; color: #b45309; }
.tag-receipt { background: #e0f2fe; color: #0369a1; }
.kind-employee { background: #ede9fe; color: #6d28d9; } .kind-prospect { background: #fce7f3; color: #9d174d; }
.kind-fulfillment, .kind-salesrep { background: #cffafe; color: #0e7490; }
.q-oh { background: #dcfce7; color: #166534; } .q-res { background: #fef9c3; color: #854d0e; } .q-ord { background: #dbeafe; color: #1e40af; }
.q-zero { background: #f1f5f9; color: #94a3b8; }
.sm-chip-bom { background: #ede9fe; color: #6d28d9; }
/* fixed 3-column qty grid so Bestand / Res / Best line up across every product row */
.sm-qty-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; margin-top: 4px; }
.sm-qchip { display: flex; align-items: baseline; justify-content: center; gap: 4px;
  font-size: .62rem; font-weight: 700; padding: 2px 5px; border-radius: 5px;
  background: #eef2f7; color: #475569; white-space: nowrap; overflow: hidden; }
.sm-qchip i { font-style: normal; opacity: .8; }
.sm-qchip b { font-weight: 800; }
/* colour the tile qty chips when qty > 0 — higher specificity than the base
   .sm-qchip rule, which would otherwise win by source order and grey them all. */
.sm-qchip.q-oh { background: #dcfce7; color: #166534; }
.sm-qchip.q-res { background: #fef9c3; color: #854d0e; }
.sm-qchip.q-ord { background: #dbeafe; color: #1e40af; }
.sm-qchip.q-zero { background: #f1f5f9; color: #94a3b8; }
.sm-open { flex: none; width: 30px; height: 30px; border: 0; border-radius: 8px; background: transparent; color: #94a3b8; cursor: pointer; transition: .14s; }
.sm-open:hover { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); }

/* detail / related column */
.sm-detail { flex: none; width: min(40vw, 660px); border-left: 1px solid color-mix(in srgb, #94a3b8 20%, transparent);
  background: color-mix(in srgb, var(--theme-bg-secondary, #f8fafc) 100%, transparent); display: flex; flex-direction: column; overflow: hidden; }
.sm-detail-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; color: #94a3b8; text-align: center; padding: 2rem; }
.sm-detail-empty i { font-size: 2.6rem; color: #cbd5e1; } .sm-detail-empty p { margin: 0; font-size: .84rem; max-width: 220px; }
.sm-detail-empty.small i { font-size: 1.8rem; }
.sm-detail-head { display: flex; align-items: center; gap: 10px; padding: 14px; border-bottom: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent); }
.sm-detail-ic { width: 34px; height: 34px; display: grid; place-items: center; border-radius: 10px; background: var(--accent); color: #fff; font-size: 1.1rem; }
.sm-detail-titles { flex: 1; min-width: 0; }
.sm-detail-title { font-weight: 700; font-size: .95rem; color: var(--theme-text-primary, #0f172a); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sm-detail-sub { font-size: .75rem; color: #94a3b8; }
.sm-detail-open { border: 0; border-radius: 9px; background: var(--accent); color: #fff; font-size: .76rem; font-weight: 700; padding: 6px 10px; cursor: pointer; display: flex; align-items: center; gap: 4px; }
.sm-detail-open:hover { filter: brightness(1.08); }
/* overflow-x hidden = hard guard: long related-doc content may never widen the
   fixed-size detail column or introduce horizontal scrolling */
.sm-detail-body { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 12px; }
.sm-detail-loading { color: #94a3b8; font-size: .82rem; padding: 10px; display: flex; gap: 6px; align-items: center; }
/* two compact columns — minmax(0, 1fr) so content min-width can't stretch a track */
.sm-rel-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px 14px; align-items: start; }
.sm-rel { margin-bottom: 10px; min-width: 0; }
.sm-rel-head { display: flex; align-items: center; gap: 7px; font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #64748b; margin-bottom: 4px; }
.sm-rel-head b { margin-left: auto; background: #e2e8f0; color: #475569; border-radius: 20px; padding: 0 7px; font-size: .7rem; }
.sm-rel-item { display: flex; align-items: center; gap: 7px; padding: 4px 8px; border-radius: 8px; cursor: pointer;
  background: var(--theme-bg-primary, #fff); border: 1px solid color-mix(in srgb, #94a3b8 16%, transparent); margin-bottom: 3px; transition: .14s; }
.sm-rel-item:hover { border-color: var(--accent); transform: translateX(2px); }
.sm-rel-item.no-link { cursor: default; }
.sm-rel-item.no-link:hover { border-color: color-mix(in srgb, #94a3b8 16%, transparent); transform: none; }
.sm-rel-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex: none; }
.sm-rel-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.sm-rel-text b { font-size: .78rem; font-weight: 600; color: var(--theme-text-primary, #0f172a); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sm-rel-text i { font-size: .7rem; color: #94a3b8; font-style: normal; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* docno · date · amount on one compact line */
.sm-rel-line { display: flex; align-items: baseline; gap: 7px; min-width: 0; }
.sm-rel-line b { flex: 1; }
.sm-rel-date { flex: none; font-size: .66rem; font-style: normal; color: #94a3b8; white-space: nowrap; }
.sm-rel-amt { flex: none; font-size: .72rem; font-weight: 700; color: var(--theme-text-primary, #0f172a);
  white-space: nowrap; font-variant-numeric: tabular-nums; }
.sm-rel-phone { display: flex; align-items: center; gap: 4px; font-size: .7rem; color: #64748b;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sm-rel-phone .ti { font-size: .78rem; color: #94a3b8; flex: none; }
.sm-rel-go { color: #cbd5e1; font-size: .9rem; } .sm-rel-item:hover .sm-rel-go { color: var(--accent); }

/* product detail card */
.sm-pc { display: flex; flex-direction: column; gap: 14px; }
.sm-pc-head { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 14px; }
/* article name (big) + BOM tag, left of the image */
.sm-pc-headinfo { flex: 1 1 150px; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.sm-pc-name { font-size: 1.18rem; font-weight: 800; line-height: 1.28; color: var(--theme-text-primary, #0f172a);
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; word-break: break-word; }
.sm-pc-side { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.sm-pc-tags { display: flex; flex-wrap: wrap; gap: 4px; }
.sm-pc-media { width: 210px; height: 210px; border-radius: 12px; overflow: hidden; display: grid; place-items: center;
  background: color-mix(in srgb, #94a3b8 8%, var(--theme-bg-primary, #fff)); border: 1px solid color-mix(in srgb, #94a3b8 16%, transparent); }
/* image anchored to the top-right corner; always show the WHOLE image (scaled to fit & centered),
   never cropped. Size via max-* + auto so a global lazy-load img rule can't force-stretch/crop it. */
.sm-pc-media img { max-width: 100%; max-height: 100%; width: auto !important; height: auto !important;
  object-fit: contain; object-position: center; display: block; }
.sm-pc-media.sm-pc-noimg { color: #cbd5e1; font-size: 2.6rem; }
/* clickable image → opens the fullscreen lightbox */
.sm-pc-media-btn { padding: 0; cursor: zoom-in; position: relative; transition: box-shadow .15s, transform .15s; }
.sm-pc-media-btn:hover { box-shadow: 0 10px 24px -10px rgba(2, 6, 23, .45); transform: translateY(-1px); }
.sm-pc-zoom { position: absolute; right: 6px; bottom: 6px; width: 28px; height: 28px; display: grid; place-items: center;
  border-radius: 8px; background: rgba(15, 23, 42, .62); color: #fff; font-size: 1rem; opacity: 0; transition: opacity .15s; }
.sm-pc-media-btn:hover .sm-pc-zoom { opacity: 1; }

/* section separators */
.sm-pc-sep { border: 0; border-top: 1px solid color-mix(in srgb, #94a3b8 26%, transparent); margin: 0; width: 100%; }

/* locator breakdown — where the article physically sits */
.sm-pc-locators { display: flex; flex-direction: column; gap: 8px; padding: 12px; border-radius: 12px;
  background: color-mix(in srgb, #06b6d4 7%, var(--theme-bg-primary, #fff));
  border: 1px solid color-mix(in srgb, #06b6d4 24%, transparent); }
.sm-pc-loc-head { display: flex; align-items: center; gap: 7px; font-size: .72rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .04em; color: #0e7490; }
.sm-pc-loc-head i { font-size: .98rem; }
.sm-pc-loc-head b { margin-left: auto; background: color-mix(in srgb, #06b6d4 20%, transparent); color: #0e7490;
  border-radius: 20px; padding: 0 8px; font-size: .72rem; }
.sm-pc-loc-list { display: flex; flex-direction: column; gap: 5px; max-height: 176px; overflow-y: auto; }
.sm-pc-loc { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 8px;
  background: var(--theme-bg-primary, #fff); border: 1px solid color-mix(in srgb, #94a3b8 16%, transparent); }
.sm-pc-loc-name { flex: 1; min-width: 0; display: flex; align-items: center; gap: 6px; font-size: .8rem; font-weight: 600;
  color: var(--theme-text-primary, #0f172a); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sm-pc-loc-name i { color: #06b6d4; font-size: .92rem; flex: none; }
.sm-pc-loc-name em { flex: none; font-style: normal; font-size: .58rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .03em; background: #fee2e2; color: #b91c1c; padding: 1px 6px; border-radius: 5px; }
.sm-pc-loc.is-return .sm-pc-loc-name i { color: #b91c1c; }
.sm-pc-loc-qty { flex: none; font-size: .92rem; font-weight: 800; color: #166534; }
.sm-pc-loc-empty { display: flex; align-items: center; gap: 6px; padding: 4px 2px; font-size: .78rem; color: #94a3b8; }
.sm-pc-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(84px, 1fr)); gap: 8px; }
.sm-pc-stat { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  padding: 10px 6px; border-radius: 10px; text-align: center; }
.sm-pc-stat b { font-size: 1.1rem; font-weight: 800; line-height: 1; }
.sm-pc-stat span { font-size: .6rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; opacity: .85; }
.sm-pc-stat.q-oh { background: #dcfce7; color: #166534; }
.sm-pc-stat.q-avail { background: #cffafe; color: #0e7490; }
.sm-pc-stat.q-res { background: #fef9c3; color: #854d0e; }
.sm-pc-stat.q-ord { background: #dbeafe; color: #1e40af; }
.sm-pc-stat.q-ret { background: #fee2e2; color: #b91c1c; }
.sm-pc-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.sm-pc-field { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border-radius: 9px; min-width: 0;
  background: var(--theme-bg-primary, #fff); border: 1px solid color-mix(in srgb, #94a3b8 16%, transparent); }
.sm-pc-field span { font-size: .6rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #94a3b8; }
.sm-pc-field b { font-size: .82rem; font-weight: 600; color: var(--theme-text-primary, #0f172a);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; user-select: text; }

/* fullscreen image lightbox */
.sm-lightbox { position: fixed; inset: 0; z-index: 1100; background: rgba(2, 6, 23, .92);
  display: flex; align-items: center; justify-content: center; padding: 4vmin; cursor: zoom-out; animation: sm-pop .16s ease; }
.sm-lightbox img { max-width: 96vw; max-height: 96vh; width: auto !important; height: auto !important;
  object-fit: contain; border-radius: 8px; box-shadow: 0 24px 70px rgba(0, 0, 0, .55); cursor: default; }
.sm-lightbox-x { position: absolute; top: 18px; right: 18px; width: 44px; height: 44px; border: 0; border-radius: 12px;
  background: rgba(255, 255, 255, .14); color: #fff; font-size: 1.3rem; cursor: pointer; transition: .15s; }
.sm-lightbox-x:hover { background: rgba(255, 255, 255, .3); }

:deep(.sm-hl) { background: linear-gradient(transparent 55%, color-mix(in srgb, #fde047 80%, transparent) 55%); border-radius: 2px; font-weight: 700; }

/* responsive: detail becomes a bottom sheet on narrow screens */
@media (max-width: 860px) {
  .sm-shell { width: 100vw; max-height: 100vh; height: 100vh; border-radius: 0; }
  .sm-body { flex-direction: column; }
  .sm-detail { width: auto; border-left: 0; border-top: 1px solid color-mix(in srgb, #94a3b8 20%, transparent); max-height: 48%; }
  .sm-detail:not(.open) { display: none; }
  .sm-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: auto; }
  .sm-tile { max-height: 50vh; }
}

/* phones: dedicated compact structure — everything much smaller, tiles stacked
   in one column, taller detail sheet, single-column related list */
@media (max-width: 640px) {
  .sm-head { padding: 8px 10px; gap: 8px; }
  .sm-search { padding: 7px 10px; border-radius: 10px; }
  .sm-search input { font-size: .88rem; }
  .sm-x { width: 30px; height: 30px; }
  .sm-tiles { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; grid-auto-rows: auto; padding: 8px; gap: 8px; }
  .sm-tile { max-height: 40vh; }
  .sm-tile-head { padding: 7px 9px; font-size: .78rem; gap: 6px; }
  .sm-tile-ic { width: 22px; height: 22px; font-size: .82rem; border-radius: 7px; }
  .sm-tile-count, .sm-bom-toggle { font-size: .62rem; }
  .sm-row { padding: 5px 6px; gap: 4px; }
  .sm-row-title { font-size: .74rem; }
  .sm-row-date { font-size: .58rem; }
  .sm-row-sub { font-size: .64rem; }
  .sm-chip { font-size: .55rem; }
  .sm-qchip { font-size: .56rem; padding: 1px 4px; }
  .sm-open { width: 24px; height: 24px; }
  .sm-detail { max-height: 56%; }
  .sm-detail-head { padding: 8px 10px; gap: 8px; }
  .sm-detail-ic { width: 26px; height: 26px; font-size: .9rem; }
  .sm-detail-title { font-size: .82rem; }
  .sm-detail-sub { font-size: .66rem; }
  .sm-detail-open { font-size: .66rem; padding: 4px 8px; }
  .sm-detail-body { padding: 8px; }
  .sm-rel-grid { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .sm-rel { margin-bottom: 8px; }
  .sm-rel-head { font-size: .62rem; margin-bottom: 3px; }
  .sm-rel-item { padding: 3px 6px; margin-bottom: 2px; gap: 6px; }
  .sm-rel-text b { font-size: .7rem; }
  .sm-rel-date { font-size: .6rem; }
  .sm-rel-amt { font-size: .64rem; }
  .sm-rel-text i, .sm-rel-phone { font-size: .62rem; }
  .sm-pc-name { font-size: .95rem; }
  .sm-pc-media { width: 120px; height: 120px; }
  .sm-pc-stats { grid-template-columns: repeat(auto-fit, minmax(64px, 1fr)); gap: 5px; }
  .sm-pc-stat b { font-size: .9rem; }
  .sm-pc-stat span { font-size: .54rem; }
  .sm-pc-field b { font-size: .72rem; }
}

/* dark mode */
.dark .sm-shell { background: #0b1220; }
.dark .sm-tile { background: #0f172a; }
.dark .sm-detail { background: #0b1220; }
.dark .sm-rel-item { background: #0f172a; }
.dark .sm-pc-media { background: #0f172a; }
.dark .sm-pc-field { background: #0f172a; }
.dark .sm-pc-name { color: #e2e8f0; }
.dark .sm-pc-sep { border-top-color: color-mix(in srgb, #94a3b8 22%, transparent); }
.dark .sm-pc-locators { background: #0f172a; border-color: color-mix(in srgb, #06b6d4 32%, transparent); }
.dark .sm-pc-loc { background: #0b1220; }
.dark .sm-pc-loc-name { color: #e2e8f0; }
.dark .sm-pc-loc-qty { color: #4ade80; }
.dark .sm-qchip.q-oh { background: #14532d; color: #bbf7d0; }
.dark .sm-qchip.q-res { background: #422006; color: #fde68a; }
.dark .sm-qchip.q-ord { background: #1e3a8a; color: #bfdbfe; }
.dark .sm-qchip.q-zero { background: #1e293b; color: #64748b; }
.dark .sm-chip { background: #1e293b; color: #cbd5e1; }
.dark .sm-qchip { background: #1e293b; color: #cbd5e1; }
.dark .q-zero { background: #1e293b; color: #64748b; }
.dark .sm-row-title, .dark .sm-detail-title, .dark .sm-rel-text b, .dark .sm-rel-amt { color: #e2e8f0; }
.dark .sm-bom-toggle { color: color-mix(in srgb, var(--accent) 70%, #e2e8f0); }
</style>
