// mam.jsx — Multimedia Asset Manager.
// DAM scoped to music industry deliverables, with strict per-DSP specs as
// validators. Asset families: release artwork, motion artwork (Spotify Canvas /
// Apple Motion), stereo masters, spatial audio (Dolby Atmos ADM BWF), music
// videos with DSP variants, artist profile pictures (per DSP + social), and
// artist page motion (Spotify Canvas-Artist / Apple Motion Artist Image).
//
// Tabs: Library · Releases · Artists · Audio · Video · Specs
// EXPORT: window.ScreenMam, window.MamSpecs
// ============================================================================
(() => {
  const { useState, useMemo, useEffect } = React;
  const useTweaks = window.useTweaks || ((d) => useState(d));
  const TweaksPanel = window.TweaksPanel;
  const TweakSection = window.TweakSection;
  const TweakRadio = window.TweakRadio;
  const TweakToggle = window.TweakToggle;

  // ── DSP spec library ───────────────────────────────────────────────
  // Public spec sheets as of 2024–25. All numbers normalized to base units.
  // px = pixels, B = bytes (1 MB = 1024² B), s = seconds, Hz = sample rate.
  const SPECS = {
    'release-artwork': {
      label: 'Release artwork (cover)',
      family: 'image',
      kind: 'static',
      dsps: {
        spotify:  { square: true, minPx: 3000, maxPx: 3000, color: 'RGB sRGB',  format: ['JPEG','PNG'], maxB: 20*1024*1024,  bitDepth: 8, dpi: 72,  notes: 'No DSP logos, watermarks, or social handles.' },
        apple:    { square: true, minPx: 3000, maxPx: 4000, color: 'RGB sRGB',  format: ['JPEG','PNG'], maxB: 20*1024*1024,  bitDepth: 8, dpi: 72,  notes: 'No artwork URLs or contact info.' },
        amazon:   { square: true, minPx: 1400, maxPx: 4000, color: 'RGB sRGB',  format: ['JPEG'],       maxB: 100*1024*1024, bitDepth: 8, dpi: 300, notes: 'JPEG only, baseline encoding.' },
        youtube:  { square: true, minPx: 1400, maxPx: 4000, color: 'RGB sRGB',  format: ['JPEG','PNG'], maxB: 50*1024*1024,  bitDepth: 8, dpi: 72,  notes: 'Square only.' },
        tidal:    { square: true, minPx: 3000, maxPx: 6000, color: 'RGB sRGB',  format: ['JPEG','PNG'], maxB: 25*1024*1024,  bitDepth: 8, dpi: 72,  notes: 'High-res preferred.' },
        deezer:   { square: true, minPx: 1400, maxPx: 3000, color: 'RGB sRGB',  format: ['JPEG'],       maxB: 10*1024*1024,  bitDepth: 8, dpi: 72,  notes: 'JPEG, no transparency.' },
        pandora:  { square: true, minPx: 1400, maxPx: 4000, color: 'RGB sRGB',  format: ['JPEG'],       maxB: 50*1024*1024,  bitDepth: 8, dpi: 72,  notes: '' },
      }
    },
    'motion-artwork': {
      label: 'Motion artwork (Canvas)',
      family: 'video',
      kind: 'loop',
      dsps: {
        spotify:  { aspect: '9:16',  w: 1080, h: 1920, durMin: 3, durMax: 8,  format: ['MP4','MOV'], codec: 'H.264', framerate: 25, maxB: 18*1024*1024,  loop: true,  audio: false, notes: 'Spotify Canvas. Vertical, silent, looping.' },
        apple:    { aspect: '1:1',   w: 1080, h: 1080, durMin: 12, durMax: 24,format: ['MP4','MOV'], codec: 'H.264 / ProRes', framerate: 24, maxB: 30*1024*1024, loop: true, audio: false, notes: 'Apple Motion Album Art. Square, silent.' },
        amazon:   { aspect: '1:1',   w: 1080, h: 1080, durMin: 5, durMax: 30, format: ['MP4'],       codec: 'H.264', framerate: 30, maxB: 30*1024*1024,  loop: true,  audio: false, notes: 'Amazon Motion Art.' },
        youtube:  { aspect: '16:9',  w: 1920, h: 1080, durMin: 15, durMax: 60,format: ['MP4','MOV'], codec: 'H.264', framerate: 30, maxB: 50*1024*1024,  loop: false, audio: true,  notes: 'Art Tracks (YouTube Music).' },
      }
    },
    'audio-stereo': {
      label: 'Stereo audio master',
      family: 'audio',
      kind: 'master',
      dsps: {
        spotify:  { container: 'WAV / FLAC',  bitDepth: [16,24], sampleRateHz: [44100, 48000, 88200, 96000], lufsTarget: -14, truePeakDbtp: -1, channels: 2, notes: 'Loudness-normalized to -14 LUFS at playback.' },
        apple:    { container: 'WAV / AIFF / FLAC', bitDepth: [16,24], sampleRateHz: [44100, 48000, 88200, 96000, 176400, 192000], lufsTarget: -16, truePeakDbtp: -1, channels: 2, notes: 'Apple Digital Masters: 24-bit / ≥48 kHz preferred.' },
        amazon:   { container: 'WAV / FLAC',  bitDepth: [16,24], sampleRateHz: [44100, 48000, 96000], lufsTarget: -14, truePeakDbtp: -2, channels: 2, notes: 'HD: 16-bit / 44.1 kHz min. Ultra HD: 24-bit / ≥48 kHz.' },
        tidal:    { container: 'WAV / FLAC',  bitDepth: [16,24], sampleRateHz: [44100, 48000, 88200, 96000, 176400, 192000], lufsTarget: -14, truePeakDbtp: -1, channels: 2, notes: 'HiFi+ MQA / FLAC.' },
        youtube:  { container: 'WAV / FLAC',  bitDepth: [16,24], sampleRateHz: [44100, 48000], lufsTarget: -14, truePeakDbtp: -1, channels: 2, notes: '' },
        deezer:   { container: 'FLAC',        bitDepth: [16],    sampleRateHz: [44100], lufsTarget: -15, truePeakDbtp: -1, channels: 2, notes: 'HiFi: lossless 16/44.1.' },
      }
    },
    'audio-spatial': {
      label: 'Spatial audio (Dolby Atmos)',
      family: 'audio',
      kind: 'spatial',
      dsps: {
        apple:    { container: 'ADM BWF',  bitDepth: [24], sampleRateHz: [48000, 96000], maxObjects: 118, lufsTarget: -18, truePeakDbtp: -1, channels: 'Atmos', notes: 'Dolby Atmos Renderer master, ADM BWF. Apple Music spatial.' },
        amazon:   { container: 'ADM BWF',  bitDepth: [24], sampleRateHz: [48000], maxObjects: 118, lufsTarget: -18, truePeakDbtp: -1, channels: 'Atmos', notes: 'Amazon Music HD Spatial.' },
        tidal:    { container: 'ADM BWF',  bitDepth: [24], sampleRateHz: [48000], maxObjects: 118, lufsTarget: -18, truePeakDbtp: -1, channels: 'Atmos', notes: '' },
      }
    },
    'music-video': {
      label: 'Music video',
      family: 'video',
      kind: 'feature',
      dsps: {
        spotify:  { aspect: '16:9',  w: 1920, h: 1080, durMin: 30, durMax: 600, format: ['MP4','MOV'], codec: 'H.264 / ProRes 422 HQ', framerate: [23.976,24,25,29.97,30], maxB: 4*1024*1024*1024, audio: 'AAC 320 / PCM 24/48', notes: 'Spotify Music Videos. Beta partners only.' },
        apple:    { aspect: '16:9',  w: 3840, h: 2160, durMin: 30, durMax: 1800,format: ['MOV','MP4'], codec: 'ProRes 422 HQ', framerate: [23.976,24,25,29.97,30], maxB: 50*1024*1024*1024, audio: 'PCM 24/48', notes: 'Apple Music. UHD master preferred, HDR10 / Dolby Vision optional.' },
        youtube:  { aspect: '16:9',  w: 3840, h: 2160, durMin: 1, durMax: 43200,format: ['MP4','MOV'], codec: 'H.264 / ProRes', framerate: [23.976,24,25,29.97,30,50,60], maxB: 256*1024*1024*1024, audio: 'AAC-LC / FLAC', notes: 'YouTube Content ID and Art Tracks.' },
        vevo:     { aspect: '16:9',  w: 1920, h: 1080, durMin: 30, durMax: 1800,format: ['MOV'], codec: 'ProRes 422 HQ', framerate: [23.976,24,25,29.97], maxB: 100*1024*1024*1024, audio: 'PCM 24/48', notes: 'Vevo distribution master.' },
        tidal:    { aspect: '16:9',  w: 1920, h: 1080, durMin: 30, durMax: 1800,format: ['MP4'], codec: 'H.264', framerate: [23.976,24,25,29.97,30], maxB: 8*1024*1024*1024, audio: 'AAC 320', notes: '' },
      }
    },
    'artist-profile': {
      label: 'Artist profile picture',
      family: 'image',
      kind: 'avatar',
      dsps: {
        spotify:   { square: true, minPx: 750,  maxPx: 6000, color: 'RGB sRGB', format: ['JPEG','PNG'], maxB: 20*1024*1024, notes: 'Spotify for Artists. Centered subject.' },
        apple:     { square: true, minPx: 1400, maxPx: 4000, color: 'RGB sRGB', format: ['JPEG','PNG'], maxB: 20*1024*1024, notes: 'Apple Music for Artists.' },
        amazon:    { square: true, minPx: 1024, maxPx: 4000, color: 'RGB sRGB', format: ['JPEG'],       maxB: 10*1024*1024, notes: '' },
        tidal:     { square: true, minPx: 750,  maxPx: 4000, color: 'RGB sRGB', format: ['JPEG','PNG'], maxB: 10*1024*1024, notes: '' },
        deezer:    { square: true, minPx: 1000, maxPx: 4000, color: 'RGB sRGB', format: ['JPEG','PNG'], maxB: 10*1024*1024, notes: '' },
        instagram: { square: true, minPx: 320,  maxPx: 1080, color: 'RGB sRGB', format: ['JPEG'],       maxB: 8*1024*1024,  notes: 'Instagram avatar (round-cropped).' },
        x:         { square: true, minPx: 400,  maxPx: 4000, color: 'RGB sRGB', format: ['JPEG','PNG'], maxB: 5*1024*1024,  notes: 'X / Twitter avatar.' },
        tiktok:    { square: true, minPx: 200,  maxPx: 1080, color: 'RGB sRGB', format: ['JPEG','PNG'], maxB: 5*1024*1024,  notes: 'TikTok profile.' },
        facebook:  { square: true, minPx: 320,  maxPx: 2048, color: 'RGB sRGB', format: ['JPEG','PNG'], maxB: 8*1024*1024,  notes: '' },
      }
    },
    'artist-header': {
      label: 'Artist page header',
      family: 'image',
      kind: 'banner',
      dsps: {
        spotify:  { aspect: '16:9',  w: 2660, h: 1140, color: 'RGB sRGB', format: ['JPEG','PNG'], maxB: 20*1024*1024, notes: 'Spotify artist header. Safe area centered, text avoided.' },
        apple:    { aspect: '16:9',  w: 4320, h: 1080, color: 'RGB sRGB', format: ['JPEG','PNG'], maxB: 20*1024*1024, notes: 'Apple Music artist hero.' },
        amazon:   { aspect: '16:9',  w: 2880, h: 1620, color: 'RGB sRGB', format: ['JPEG'],       maxB: 10*1024*1024, notes: 'Amazon Music artist banner.' },
        youtube:  { aspect: '16:9',  w: 2560, h: 1440, color: 'RGB sRGB', format: ['JPEG','PNG'], maxB: 6*1024*1024,  notes: 'YouTube channel art.' },
      }
    },
    'artist-motion': {
      label: 'Artist page motion',
      family: 'video',
      kind: 'loop',
      dsps: {
        spotify: { aspect: '16:9',  w: 1920, h: 1080, durMin: 5,  durMax: 30, format: ['MP4'], codec: 'H.264', framerate: 30, maxB: 50*1024*1024, loop: true,  audio: false, notes: 'Spotify Canvas-Artist (artist page hero loop).' },
        apple:   { aspect: '16:9',  w: 1920, h: 1080, durMin: 12, durMax: 24, format: ['MP4','MOV'], codec: 'H.264 / ProRes', framerate: 24, maxB: 30*1024*1024, loop: true, audio: false, notes: 'Apple Motion Artist Image.' },
        amazon:  { aspect: '16:9',  w: 1920, h: 1080, durMin: 8,  durMax: 30, format: ['MP4'], codec: 'H.264', framerate: 30, maxB: 30*1024*1024, loop: true,  audio: false, notes: 'Amazon Music Motion Art.' },
      }
    },
  };

  const ASSET_TYPES = Object.entries(SPECS).map(([k, v]) => ({ k, ...v }));
  const DSP_LABEL = {
    spotify: 'Spotify', apple: 'Apple Music', amazon: 'Amazon Music',
    youtube: 'YouTube Music', tidal: 'Tidal', deezer: 'Deezer', pandora: 'Pandora',
    vevo: 'Vevo', instagram: 'Instagram', x: 'X', tiktok: 'TikTok', facebook: 'Facebook',
  };
  const DSP_TONE = {
    spotify: '#1DB954', apple: '#FA2D48', amazon: '#00A8E1', youtube: '#FF0000',
    tidal: '#000000', deezer: '#A238FF', pandora: '#3668FF', vevo: '#EE1133',
    instagram: '#E1306C', x: '#000000', tiktok: '#FF0050', facebook: '#1877F2',
  };

  // ── format helpers ──────────────────────────────────────────────────
  function fmtBytes(b) {
    if (b == null) return '—';
    if (b < 1024) return b + ' B';
    if (b < 1024 * 1024) return (b / 1024).toFixed(1) + ' KB';
    if (b < 1024 * 1024 * 1024) return (b / (1024 * 1024)).toFixed(1) + ' MB';
    return (b / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
  }
  function fmtDur(s) {
    if (s == null) return '—';
    const m = Math.floor(s / 60), r = Math.floor(s % 60);
    return m ? `${m}:${String(r).padStart(2, '0')}` : `${s.toFixed(1)}s`;
  }

  // ── validator ───────────────────────────────────────────────────────
  // Returns { dsp, status: 'pass'|'warn'|'fail', issues: [{f,msg,sev}] }
  function validate(asset, dsp) {
    const spec = SPECS[asset.type]?.dsps[dsp];
    if (!spec) return { dsp, status: 'na', issues: [{ msg: 'No spec for this DSP', sev: 'info' }] };
    const out = [];
    const fam = SPECS[asset.type].family;

    if (fam === 'image') {
      if (spec.square) {
        if (asset.w !== asset.h) out.push({ f: 'aspect', msg: 'Must be square', sev: 'fail' });
      }
      if (spec.minPx && asset.w < spec.minPx) out.push({ f: 'size', msg: `Below ${spec.minPx}px (got ${asset.w}px)`, sev: 'fail' });
      if (spec.maxPx && asset.w > spec.maxPx) out.push({ f: 'size', msg: `Above ${spec.maxPx}px (got ${asset.w}px)`, sev: 'warn' });
      if (spec.format && !spec.format.includes(asset.format)) out.push({ f: 'format', msg: `Format ${asset.format} not in ${spec.format.join('/')}`, sev: 'fail' });
      if (spec.maxB && asset.bytes > spec.maxB) out.push({ f: 'size', msg: `Over ${fmtBytes(spec.maxB)} (got ${fmtBytes(asset.bytes)})`, sev: 'fail' });
      if (spec.color && asset.color !== spec.color) out.push({ f: 'color', msg: `Color profile must be ${spec.color}`, sev: 'warn' });
      if (spec.aspect) {
        const want = spec.aspect.split(':').map(Number);
        const have = asset.w / asset.h;
        const wanted = want[0] / want[1];
        if (Math.abs(have - wanted) / wanted > 0.02) out.push({ f: 'aspect', msg: `Aspect must be ${spec.aspect}`, sev: 'fail' });
      }
    } else if (fam === 'video') {
      if (spec.w && asset.w < spec.w) out.push({ f: 'size', msg: `Below ${spec.w}×${spec.h} (got ${asset.w}×${asset.h})`, sev: 'fail' });
      if (spec.aspect) {
        const want = spec.aspect.split(':').map(Number);
        const have = asset.w / asset.h;
        const wanted = want[0] / want[1];
        if (Math.abs(have - wanted) / wanted > 0.02) out.push({ f: 'aspect', msg: `Aspect must be ${spec.aspect}`, sev: 'fail' });
      }
      if (spec.durMin && asset.dur < spec.durMin) out.push({ f: 'dur', msg: `Below ${spec.durMin}s (got ${fmtDur(asset.dur)})`, sev: 'fail' });
      if (spec.durMax && asset.dur > spec.durMax) out.push({ f: 'dur', msg: `Above ${spec.durMax}s (got ${fmtDur(asset.dur)})`, sev: 'fail' });
      if (spec.format && !spec.format.includes(asset.format)) out.push({ f: 'format', msg: `Format must be ${spec.format.join('/')}`, sev: 'fail' });
      if (spec.maxB && asset.bytes > spec.maxB) out.push({ f: 'size', msg: `Over ${fmtBytes(spec.maxB)}`, sev: 'fail' });
      if (spec.codec && asset.codec && !String(spec.codec).toLowerCase().includes(String(asset.codec).toLowerCase().split(' ')[0])) {
        out.push({ f: 'codec', msg: `Codec ${asset.codec} — DSP wants ${spec.codec}`, sev: 'warn' });
      }
      if (spec.framerate) {
        const list = Array.isArray(spec.framerate) ? spec.framerate : [spec.framerate];
        if (!list.some(f => Math.abs(f - asset.fps) < 0.5)) out.push({ f: 'fps', msg: `Frame rate ${asset.fps} — wants ${list.join('/')} fps`, sev: 'warn' });
      }
      if (spec.audio === false && asset.hasAudio) out.push({ f: 'audio', msg: 'Audio track must be silent / removed', sev: 'fail' });
    } else if (fam === 'audio') {
      if (spec.bitDepth && !spec.bitDepth.includes(asset.bitDepth)) out.push({ f: 'bit', msg: `Bit depth ${asset.bitDepth} not in ${spec.bitDepth.join('/')}`, sev: 'fail' });
      if (spec.sampleRateHz && !spec.sampleRateHz.includes(asset.sr)) out.push({ f: 'sr', msg: `Sample rate ${asset.sr} not allowed`, sev: 'fail' });
      if (spec.lufsTarget != null && asset.lufs != null) {
        const delta = asset.lufs - spec.lufsTarget;
        if (Math.abs(delta) > 1.0) out.push({ f: 'lufs', msg: `LUFS ${asset.lufs} (target ${spec.lufsTarget})`, sev: Math.abs(delta) > 2 ? 'fail' : 'warn' });
      }
      if (spec.truePeakDbtp != null && asset.tp != null && asset.tp > spec.truePeakDbtp) {
        out.push({ f: 'tp', msg: `True peak ${asset.tp} dBTP — ceiling ${spec.truePeakDbtp}`, sev: 'fail' });
      }
      if (spec.maxObjects && asset.objects && asset.objects > spec.maxObjects) {
        out.push({ f: 'obj', msg: `${asset.objects} objects — ${spec.maxObjects} max`, sev: 'fail' });
      }
      if (spec.container && asset.format && !spec.container.toLowerCase().includes(asset.format.toLowerCase())) {
        out.push({ f: 'container', msg: `Container ${asset.format} not in ${spec.container}`, sev: 'warn' });
      }
    }

    const sev = out.some(i => i.sev === 'fail') ? 'fail' : out.some(i => i.sev === 'warn') ? 'warn' : 'pass';
    return { dsp, status: sev, issues: out };
  }

  // ── seed library ────────────────────────────────────────────────────
  // Synthesize a realistic library from existing RELEASE_GROUPS / RS.videos.
  function seedLibrary() {
    const out = [];
    const groups = (window.RELEASE_GROUPS || []).slice(0, 12);
    const rels   = (window.RELEASES || []).slice(0, 30);
    const videos = (window.RS && window.RS.videos) || [];
    const profiles = (window.RS && window.RS.profiles) || [];

    let id = 1;
    function next() { return 'mam_' + String(id++).padStart(4, '0'); }

    // Release artwork — one cover per release group, plus one motion artwork
    // for half of them. Mix 'compliant' with 'fail' and 'warn' so the library
    // has rich red/amber/green spread to look like real ops.
    groups.forEach((g, i) => {
      const variant = i % 7;
      // cover
      out.push({
        id: next(), type: 'release-artwork', name: `${g.title} — cover`, parent: g.title, parentId: g.id, parentKind: 'release',
        format: variant === 3 ? 'TIFF' : (variant === 5 ? 'PNG' : 'JPEG'),
        w: variant === 1 ? 1500 : (variant === 6 ? 6000 : 3000),
        h: variant === 1 ? 1500 : (variant === 6 ? 6000 : 3000),
        bytes: 4_500_000 + (i * 250_000) + (variant === 4 ? 30_000_000 : 0),
        color: variant === 2 ? 'CMYK' : 'RGB sRGB',
        bitDepth: 8, dpi: 300,
        thumb: g.art || '#888',
        uploaded: '2024-' + String(((i + 1) % 12) + 1).padStart(2, '0') + '-12',
        owner: g.label,
      });
      // canvas
      if (i % 2 === 0) {
        out.push({
          id: next(), type: 'motion-artwork', name: `${g.title} — Canvas`, parent: g.title, parentId: g.id, parentKind: 'release',
          format: 'MP4', codec: 'H.264',
          w: 1080, h: 1920, dur: 5 + (i % 4), fps: 25, bytes: 8_000_000 + (i * 200_000),
          hasAudio: false, thumb: g.art || '#888',
          uploaded: '2024-' + String(((i + 1) % 12) + 1).padStart(2, '0') + '-15',
          owner: g.label,
        });
      }
    });

    // Stereo masters — one per first 24 releases
    rels.slice(0, 24).forEach((r, i) => {
      const v = i % 6;
      out.push({
        id: next(), type: 'audio-stereo', name: `${r.title} — stereo master`, parent: r.title, parentId: r.id, parentKind: 'release',
        format: v === 4 ? 'MP3' : 'WAV',
        bitDepth: v === 2 ? 16 : 24, sr: v === 5 ? 44100 : 48000,
        dur: 180 + (i * 17) % 240,
        bytes: 60_000_000 + (i * 3_500_000),
        lufs: -14 + ((i % 5) - 2),
        tp: -1 + ((i % 3) - 1) * 0.5,
        uploaded: '2024-' + String(((i + 1) % 12) + 1).padStart(2, '0') + '-08',
        owner: r.label,
      });
    });

    // Spatial masters — Atmos for ~6 hero releases
    rels.slice(0, 8).forEach((r, i) => {
      out.push({
        id: next(), type: 'audio-spatial', name: `${r.title} — Atmos master`, parent: r.title, parentId: r.id, parentKind: 'release',
        format: 'BWF', container: 'ADM BWF',
        bitDepth: 24, sr: 48000, dur: 200 + i * 12,
        objects: 64 + i * 8,
        bytes: 1_500_000_000 + i * 100_000_000,
        lufs: -18 + (i % 3) - 1,
        tp: -1.0,
        uploaded: '2024-08-' + String(10 + i).padStart(2, '0'),
        owner: r.label,
      });
    });

    // Music videos
    videos.slice(0, 18).forEach((v, i) => {
      const rec = v['Recording'] || 'Untitled';
      const mod = i % 5;
      out.push({
        id: next(), type: 'music-video', name: `${rec} — official video`, parent: rec, parentId: v['Video ID'], parentKind: 'recording',
        format: mod === 1 ? 'AVI' : 'MOV', codec: mod === 0 ? 'ProRes 422 HQ' : 'H.264',
        w: mod === 2 ? 1280 : (mod === 4 ? 3840 : 1920),
        h: mod === 2 ? 720  : (mod === 4 ? 2160 : 1080),
        dur: 180 + (i * 23) % 220, fps: mod === 3 ? 60 : 24,
        bytes: 1_200_000_000 + (i * 80_000_000),
        hasAudio: true,
        uploaded: '2024-' + String(((i + 1) % 12) + 1).padStart(2, '0') + '-20',
        owner: 'TBD',
      });
    });

    // Artist profile pictures + headers + motion (per profile)
    profiles.slice(0, 14).forEach((p, i) => {
      const name = p.Name || p.name || ('Artist ' + i);
      const m = i % 5;
      out.push({
        id: next(), type: 'artist-profile', name: `${name} — avatar`, parent: name, parentId: p['Profile ID'] || ('p_' + i), parentKind: 'artist',
        format: 'JPEG', w: m === 0 ? 600 : 2400, h: m === 0 ? 600 : 2400, bytes: 2_500_000 + i * 100_000,
        color: 'RGB sRGB', bitDepth: 8,
        uploaded: '2024-09-' + String(5 + i).padStart(2, '0'),
        owner: name,
      });
      if (i % 2 === 0) {
        out.push({
          id: next(), type: 'artist-header', name: `${name} — header`, parent: name, parentId: p['Profile ID'] || ('p_' + i), parentKind: 'artist',
          format: 'JPEG', w: m === 1 ? 1920 : 4320, h: m === 1 ? 810 : 1080, bytes: 6_500_000 + i * 200_000,
          color: 'RGB sRGB', bitDepth: 8,
          uploaded: '2024-09-' + String(7 + i).padStart(2, '0'),
          owner: name,
        });
      }
      if (i % 3 === 0) {
        out.push({
          id: next(), type: 'artist-motion', name: `${name} — page motion`, parent: name, parentId: p['Profile ID'] || ('p_' + i), parentKind: 'artist',
          format: 'MP4', codec: 'H.264', w: 1920, h: 1080, dur: 12 + (i % 8), fps: 24,
          bytes: 24_000_000 + i * 1_000_000,
          hasAudio: false,
          uploaded: '2024-09-' + String(9 + i).padStart(2, '0'),
          owner: name,
        });
      }
    });

    return out;
  }

  // ── chrome ──────────────────────────────────────────────────────────
  function StatusDot({ status, size = 8 }) {
    const c = { pass: '#3a8a52', warn: '#c79538', fail: '#a04432', na: '#9a9a9a', info: '#5a8aa0' }[status] || '#9a9a9a';
    return <span style={{ display: 'inline-block', width: size, height: size, borderRadius: 0, background: c }} />;
  }
  function StatusPill({ status }) {
    const m = { pass: { l: 'PASS', c: '#3a8a52' }, warn: { l: 'WARN', c: '#c79538' }, fail: { l: 'FAIL', c: '#a04432' }, na: { l: 'N/A', c: '#9a9a9a' } };
    const s = m[status] || m.na;
    return (
      <span className="ff-mono upper" style={{
        fontSize: 9, letterSpacing: '.08em', fontWeight: 600,
        padding: '3px 7px', color: s.c, border: `1px solid ${s.c}33`, background: `${s.c}10`,
      }}>{s.l}</span>
    );
  }
  function HealthRing({ pct, size = 28 }) {
    const r = (size - 4) / 2, c = 2 * Math.PI * r;
    const tone = pct >= 85 ? '#3a8a52' : pct >= 60 ? '#c79538' : '#a04432';
    return (
      <svg width={size} height={size} style={{ display: 'block' }}>
        <circle cx={size / 2} cy={size / 2} r={r} stroke="var(--rule)" strokeWidth="2" fill="none" />
        <circle cx={size / 2} cy={size / 2} r={r} stroke={tone} strokeWidth="2" fill="none"
          strokeDasharray={`${(pct / 100) * c} ${c}`} transform={`rotate(-90 ${size / 2} ${size / 2})`} strokeLinecap="round" />
        <text x={size / 2} y={size / 2 + 3} textAnchor="middle" fontSize="9" fill="var(--ink-1)" fontWeight="600" fontFamily="ui-monospace,monospace">{pct}</text>
      </svg>
    );
  }

  function AssetThumb({ asset, size = 48 }) {
    const fam = SPECS[asset.type]?.family;
    const bg = asset.thumb || (fam === 'audio' ? '#2a2a2a' : (fam === 'video' ? '#3a3a3a' : '#888'));
    return (
      <div style={{
        width: size, height: size, background: bg, position: 'relative',
        display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
        border: '1px solid var(--rule-soft)',
      }}>
        {fam === 'audio' && (
          <svg width="60%" height="40%" viewBox="0 0 40 16">
            {Array.from({ length: 12 }).map((_, i) => (
              <rect key={i} x={i * 3.4} y={8 - (Math.sin(i * 0.7) * 6)} width="2" height={Math.abs(Math.sin(i * 0.7)) * 12} fill="rgba(255,255,255,.6)" />
            ))}
          </svg>
        )}
        {fam === 'video' && (
          <svg width="40%" height="40%" viewBox="0 0 16 16">
            <polygon points="4,3 13,8 4,13" fill="rgba(255,255,255,.85)" />
          </svg>
        )}
        {fam === 'image' && asset.type === 'release-artwork' && (
          <span className="ff-mono" style={{ fontSize: 8, color: 'rgba(255,255,255,.5)', letterSpacing: '.1em' }}>♪</span>
        )}
      </div>
    );
  }

  // ── Asset detail drawer ─────────────────────────────────────────────
  function AssetDrawer({ asset, onClose }) {
    if (!asset) return null;
    const spec = SPECS[asset.type];
    const dsps = Object.keys(spec.dsps);
    const results = dsps.map(d => validate(asset, d));
    const fam = spec.family;

    function Row({ label, mono, children }) {
      return (
        <div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 12, padding: '7px 0', borderBottom: '1px solid var(--rule-soft)', alignItems: 'center' }}>
          <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)' }}>{label}</span>
          <div className={mono ? 'ff-mono' : ''} style={{ fontSize: mono ? 12 : 13 }}>{children}</div>
        </div>
      );
    }

    return (
      <aside style={{
        position: 'fixed', top: 0, right: 0, bottom: 0, width: 520, background: 'var(--paper)',
        borderLeft: '1px solid var(--rule)', boxShadow: '-12px 0 32px rgba(0,0,0,.06)', zIndex: 60,
        display: 'flex', flexDirection: 'column',
      }}>
        <header style={{ padding: '16px 20px', borderBottom: '1px solid var(--rule)', display: 'flex', alignItems: 'flex-start', gap: 12 }}>
          <AssetThumb asset={asset} size={48} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', marginBottom: 2 }}>{spec.label}</div>
            <div style={{ fontSize: 15, fontWeight: 600, lineHeight: 1.2 }}>{asset.name}</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>{asset.parent}</div>
          </div>
          <button onClick={onClose} style={{ background: 'transparent', border: 0, color: 'var(--ink-3)', cursor: 'pointer', fontSize: 16, padding: 4 }}>×</button>
        </header>

        <div style={{ flex: 1, overflowY: 'auto', padding: '4px 20px 20px' }}>
          {/* properties */}
          <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', padding: '14px 0 4px' }}>FILE</div>
          <Row label="Format" mono>{asset.format}{asset.codec ? ` · ${asset.codec}` : ''}</Row>
          {fam === 'image' && <Row label="Dimensions" mono>{asset.w} × {asset.h} px</Row>}
          {fam === 'image' && <Row label="Color" mono>{asset.color} · {asset.bitDepth}-bit</Row>}
          {fam === 'video' && <Row label="Resolution" mono>{asset.w} × {asset.h} · {asset.fps} fps</Row>}
          {fam === 'video' && <Row label="Duration" mono>{fmtDur(asset.dur)}</Row>}
          {fam === 'video' && <Row label="Audio">{asset.hasAudio ? 'Embedded audio' : 'Silent'}</Row>}
          {fam === 'audio' && <Row label="Bit depth / SR" mono>{asset.bitDepth}-bit · {(asset.sr / 1000).toFixed(1)} kHz</Row>}
          {fam === 'audio' && <Row label="Loudness" mono>{asset.lufs} LUFS · {asset.tp} dBTP true peak</Row>}
          {fam === 'audio' && asset.objects && <Row label="Atmos objects" mono>{asset.objects}</Row>}
          {fam === 'audio' && <Row label="Duration" mono>{fmtDur(asset.dur)}</Row>}
          <Row label="Size" mono>{fmtBytes(asset.bytes)}</Row>
          <Row label="Uploaded" mono>{asset.uploaded}</Row>
          <Row label="Owner">{asset.owner}</Row>

          {/* DSP readiness */}
          <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', padding: '20px 0 8px' }}>DSP READINESS</div>
          {results.map(r => (
            <div key={r.dsp} style={{ borderBottom: '1px solid var(--rule-soft)', padding: '12px 0' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: r.issues.length ? 8 : 0 }}>
                <span style={{ width: 6, height: 14, background: DSP_TONE[r.dsp] || '#666' }} />
                <span style={{ flex: 1, fontSize: 13, fontWeight: 500 }}>{DSP_LABEL[r.dsp]}</span>
                <StatusPill status={r.status} />
              </div>
              {r.issues.map((it, i) => (
                <div key={i} className="ff-mono" style={{ fontSize: 11, color: it.sev === 'fail' ? '#a04432' : (it.sev === 'warn' ? '#c79538' : 'var(--ink-3)'), paddingLeft: 14, paddingTop: 3 }}>
                  · {it.msg}
                </div>
              ))}
            </div>
          ))}

          {/* actions */}
          <div style={{ padding: '20px 0' }}>
            <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', padding: '4px 0 8px' }}>ACTIONS</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
              <ActionBtn>Download original</ActionBtn>
              <ActionBtn>Auto-derive variants</ActionBtn>
              <ActionBtn>Re-encode for DSP</ActionBtn>
              <ActionBtn>Replace</ActionBtn>
              <ActionBtn>Push to delivery</ActionBtn>
              <ActionBtn danger>Withdraw</ActionBtn>
            </div>
          </div>
        </div>
      </aside>
    );
  }
  function ActionBtn({ children, danger, disabled, onClick }) {
    return (
      <button onClick={onClick} disabled={disabled} className="ff-mono upper" style={{
        fontSize: 10, letterSpacing: '.06em', fontWeight: 600,
        padding: '8px 10px',
        background: 'transparent',
        color: danger ? '#a04432' : 'var(--ink-1)',
        border: `1px solid ${danger ? '#a0443244' : 'var(--rule)'}`,
        cursor: disabled ? 'not-allowed' : 'pointer', borderRadius: 0,
      }}>{children}</button>
    );
  }

  // ── Library grid ────────────────────────────────────────────────────
  function LibraryGrid({ rows, onPick, selectedId }) {
    return (
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: 14 }}>
        {rows.map(a => {
          const spec = SPECS[a.type];
          const dsps = Object.keys(spec.dsps);
          const results = dsps.map(d => validate(a, d));
          const passing = results.filter(r => r.status === 'pass').length;
          const pct = Math.round((passing / dsps.length) * 100);
          const isSel = selectedId === a.id;
          return (
            <div key={a.id} onClick={() => onPick(a.id)} style={{
              background: 'var(--paper)', border: `1px solid ${isSel ? 'var(--ink-1)' : 'var(--rule-soft)'}`,
              cursor: 'pointer', display: 'flex', flexDirection: 'column',
            }}>
              <div style={{ aspectRatio: '1', background: a.thumb || '#3a3a3a', position: 'relative' }}>
                <AssetThumb asset={a} size="100%" />
                <div style={{ position: 'absolute', top: 8, right: 8 }}>
                  <HealthRing pct={pct} size={32} />
                </div>
                <div style={{ position: 'absolute', bottom: 8, left: 8, display: 'flex', gap: 2 }}>
                  {results.map((r, i) => (
                    <span key={i} title={`${DSP_LABEL[r.dsp]}: ${r.status}`} style={{ width: 10, height: 10, background: { pass: '#3a8a52', warn: '#c79538', fail: '#a04432', na: '#9a9a9a' }[r.status] || '#9a9a9a' }} />
                  ))}
                </div>
              </div>
              <div style={{ padding: '10px 12px' }}>
                <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', marginBottom: 2 }}>{spec.label}</div>
                <div style={{ fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.name}</div>
                <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 4, display: 'flex', justifyContent: 'space-between' }}>
                  <span>{a.format}{a.codec ? ` · ${a.codec.split(' ')[0]}` : ''}</span>
                  <span>{fmtBytes(a.bytes)}</span>
                </div>
                <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 2 }}>
                  {spec.family === 'image' && `${a.w}×${a.h}`}
                  {spec.family === 'video' && `${a.w}×${a.h} · ${fmtDur(a.dur)}`}
                  {spec.family === 'audio' && `${a.bitDepth}/${a.sr/1000} · ${fmtDur(a.dur)}`}
                </div>
              </div>
            </div>
          );
        })}
      </div>
    );
  }

  // ── DSP readiness matrix ────────────────────────────────────────────
  function ReadinessMatrix({ rows, dsps }) {
    // Group by parent (release / artist / recording)
    const byParent = new Map();
    rows.forEach(a => {
      const k = a.parent + '|' + a.parentKind;
      if (!byParent.has(k)) byParent.set(k, { parent: a.parent, kind: a.parentKind, assets: [] });
      byParent.get(k).assets.push(a);
    });
    const groups = [...byParent.values()].sort((a, b) => a.parent.localeCompare(b.parent));

    return (
      <div style={{ background: 'var(--paper)', border: '1px solid var(--rule-soft)' }}>
        <div style={{ display: 'grid', gridTemplateColumns: `1.6fr 90px repeat(${dsps.length}, minmax(70px, 1fr))`, gap: 8, padding: '10px 14px', borderBottom: '1px solid var(--rule-soft)', position: 'sticky', top: 0, background: 'var(--paper)', zIndex: 1 }}>
          <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)' }}>Parent</span>
          <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)' }}>Assets</span>
          {dsps.map(d => (
            <div key={d} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
              <span style={{ width: 6, height: 10, background: DSP_TONE[d] || '#666' }} />
              <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.06em', color: 'var(--ink-3)' }}>{DSP_LABEL[d]}</span>
            </div>
          ))}
        </div>
        {groups.map(g => {
          // for each DSP, aggregate worst status across assets that have a spec for that dsp
          const cells = dsps.map(d => {
            const eligible = g.assets.filter(a => SPECS[a.type].dsps[d]);
            if (!eligible.length) return { d, status: 'na', count: 0 };
            const statuses = eligible.map(a => validate(a, d).status);
            const status = statuses.includes('fail') ? 'fail' : statuses.includes('warn') ? 'warn' : 'pass';
            return { d, status, count: eligible.length };
          });
          return (
            <div key={g.parent + g.kind} style={{ display: 'grid', gridTemplateColumns: `1.6fr 90px repeat(${dsps.length}, minmax(70px, 1fr))`, gap: 8, padding: '10px 14px', borderBottom: '1px solid var(--rule-soft)', alignItems: 'center' }}>
              <div>
                <div style={{ fontSize: 13 }}>{g.parent}</div>
                <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.06em', color: 'var(--ink-3)' }}>{g.kind}</div>
              </div>
              <span className="ff-mono" style={{ fontSize: 11, color: 'var(--ink-3)' }}>{g.assets.length}</span>
              {cells.map(c => (
                <div key={c.d} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                  <StatusDot status={c.status} size={10} />
                  <span className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{c.count || '—'}</span>
                </div>
              ))}
            </div>
          );
        })}
      </div>
    );
  }

  // ── Spec sheet tab ──────────────────────────────────────────────────
  function SpecSheet() {
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>
        {ASSET_TYPES.map(t => {
          const dsps = Object.entries(t.dsps);
          return (
            <div key={t.k} style={{ background: 'var(--paper)', border: '1px solid var(--rule-soft)' }}>
              <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--rule-soft)', display: 'flex', alignItems: 'baseline', gap: 12 }}>
                <span style={{ fontSize: 14, fontWeight: 500 }}>{t.label}</span>
                <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)' }}>{t.family} · {t.kind}</span>
                <span className="ff-mono" style={{ fontSize: 11, color: 'var(--ink-3)', marginLeft: 'auto' }}>{dsps.length} DSPs</span>
              </div>
              <div style={{ overflowX: 'auto' }}>
                <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
                  <thead>
                    <tr style={{ background: 'var(--surface-2)' }}>
                      <th className="ff-mono upper" style={{ textAlign: 'left', padding: '8px 12px', fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', fontWeight: 600 }}>DSP</th>
                      {t.family === 'image' && <th className="ff-mono upper" style={{ textAlign: 'left', padding: '8px 12px', fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', fontWeight: 600 }}>Aspect / size</th>}
                      {t.family === 'image' && <th className="ff-mono upper" style={{ textAlign: 'left', padding: '8px 12px', fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', fontWeight: 600 }}>Color</th>}
                      {t.family === 'video' && <th className="ff-mono upper" style={{ textAlign: 'left', padding: '8px 12px', fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', fontWeight: 600 }}>Spec</th>}
                      {t.family === 'video' && <th className="ff-mono upper" style={{ textAlign: 'left', padding: '8px 12px', fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', fontWeight: 600 }}>Duration</th>}
                      {t.family === 'audio' && <th className="ff-mono upper" style={{ textAlign: 'left', padding: '8px 12px', fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', fontWeight: 600 }}>Container</th>}
                      {t.family === 'audio' && <th className="ff-mono upper" style={{ textAlign: 'left', padding: '8px 12px', fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', fontWeight: 600 }}>Format</th>}
                      <th className="ff-mono upper" style={{ textAlign: 'left', padding: '8px 12px', fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', fontWeight: 600 }}>{t.family === 'audio' ? 'Loudness' : 'Format / max'}</th>
                      <th className="ff-mono upper" style={{ textAlign: 'left', padding: '8px 12px', fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', fontWeight: 600 }}>Notes</th>
                    </tr>
                  </thead>
                  <tbody>
                    {dsps.map(([d, sp]) => (
                      <tr key={d} style={{ borderTop: '1px solid var(--rule-soft)' }}>
                        <td style={{ padding: '10px 12px', fontWeight: 500, fontSize: 12 }}>
                          <span style={{ display: 'inline-block', width: 4, height: 11, background: DSP_TONE[d] || '#666', marginRight: 6, verticalAlign: 'middle' }} />
                          {DSP_LABEL[d]}
                        </td>
                        {t.family === 'image' && <td className="ff-mono" style={{ padding: '10px 12px', fontSize: 11 }}>{sp.aspect || (sp.square ? 'Square' : '—')} · {sp.minPx ? `${sp.minPx}–${sp.maxPx}px` : (sp.w ? `${sp.w}×${sp.h}` : '—')}</td>}
                        {t.family === 'image' && <td className="ff-mono" style={{ padding: '10px 12px', fontSize: 11 }}>{sp.color}</td>}
                        {t.family === 'video' && <td className="ff-mono" style={{ padding: '10px 12px', fontSize: 11 }}>{sp.aspect} · {sp.w}×{sp.h} · {Array.isArray(sp.framerate) ? sp.framerate.join('/') : sp.framerate} fps</td>}
                        {t.family === 'video' && <td className="ff-mono" style={{ padding: '10px 12px', fontSize: 11 }}>{sp.durMin}–{sp.durMax}s</td>}
                        {t.family === 'audio' && <td className="ff-mono" style={{ padding: '10px 12px', fontSize: 11 }}>{sp.container}</td>}
                        {t.family === 'audio' && <td className="ff-mono" style={{ padding: '10px 12px', fontSize: 11 }}>{(sp.bitDepth || []).join('/')}-bit · {(sp.sampleRateHz || []).map(x => x / 1000).join('/')} kHz</td>}
                        <td className="ff-mono" style={{ padding: '10px 12px', fontSize: 11 }}>
                          {t.family === 'audio'
                            ? `${sp.lufsTarget} LUFS · ${sp.truePeakDbtp} dBTP`
                            : `${(sp.format || []).join('/')}${sp.maxB ? ' · ' + fmtBytes(sp.maxB) : ''}${sp.codec ? ' · ' + sp.codec : ''}`}
                        </td>
                        <td style={{ padding: '10px 12px', fontSize: 11, color: 'var(--ink-3)' }}>{sp.notes}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </div>
          );
        })}
      </div>
    );
  }

  // ── KPI strip ───────────────────────────────────────────────────────
  function KpiStrip({ rows }) {
    const total = rows.length;
    const totalBytes = rows.reduce((s, r) => s + (r.bytes || 0), 0);
    let pass = 0, warn = 0, fail = 0, checks = 0;
    rows.forEach(r => {
      Object.keys(SPECS[r.type].dsps).forEach(d => {
        const v = validate(r, d);
        checks++;
        if (v.status === 'pass') pass++;
        else if (v.status === 'warn') warn++;
        else if (v.status === 'fail') fail++;
      });
    });
    const score = checks ? Math.round((pass / checks) * 100) : 100;
    const KPIS = [
      { l: 'ASSETS', v: total, s: 'in library' },
      { l: 'STORAGE', v: fmtBytes(totalBytes), s: 'aggregate' },
      { l: 'AVG READINESS', v: score + '%', s: 'pass / total checks', tone: score < 70 ? '#a04432' : score < 90 ? '#c79538' : '#3a8a52' },
      { l: 'PASSING', v: pass.toLocaleString(), s: 'DSP-ready', tone: '#3a8a52' },
      { l: 'WARNINGS', v: warn.toLocaleString(), s: 'soft issues', tone: warn ? '#c79538' : null },
      { l: 'FAILING', v: fail.toLocaleString(), s: 'reject if shipped', tone: fail ? '#a04432' : null },
    ];
    return (
      <div style={{ display: 'grid', gridTemplateColumns: `repeat(${KPIS.length}, 1fr)`, gap: 1, background: 'var(--rule-soft)', marginBottom: 16, border: '1px solid var(--rule-soft)' }}>
        {KPIS.map((k, i) => (
          <div key={i} style={{ background: 'var(--paper)', padding: '14px 16px' }}>
            <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', fontWeight: 600 }}>{k.l}</div>
            <div style={{ fontSize: 26, fontWeight: 300, marginTop: 4, fontVariantNumeric: 'tabular-nums', color: k.tone || 'var(--ink-1)' }}>{k.v}</div>
            <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 2 }}>{k.s}</div>
          </div>
        ))}
      </div>
    );
  }

  // ── main screen ─────────────────────────────────────────────────────
  const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
    "view": "grid",
    "showOnlyIssues": false,
    "groupByParent": false
  }/*EDITMODE-END*/;

  function ScreenMam({ go, payload }) {
    const [library] = useState(seedLibrary);
    const [tab, setTab] = useState(payload?.tab || 'library');
    const [search, setSearch] = useState('');
    const [filterType, setFilterType] = useState('all');
    const [filterDsp, setFilterDsp]   = useState('all');
    const [filterStatus, setFilterStatus] = useState('all');
    const [selectedId, setSelectedId] = useState(payload?.assetId || null);
    const [tweaksOpen, setTweaksOpen] = useState(false);
    const [t, setT] = useTweaks(TWEAK_DEFAULTS);

    useEffect(() => {
      const onMsg = (e) => {
        if (e.data?.type === '__activate_edit_mode') setTweaksOpen(true);
        if (e.data?.type === '__deactivate_edit_mode') setTweaksOpen(false);
      };
      window.addEventListener('message', onMsg);
      window.parent.postMessage({ type: '__edit_mode_available' }, '*');
      return () => window.removeEventListener('message', onMsg);
    }, []);

    const filtered = useMemo(() => {
      let out = library;
      if (tab === 'releases') out = out.filter(a => a.parentKind === 'release');
      else if (tab === 'artists')  out = out.filter(a => a.parentKind === 'artist');
      else if (tab === 'audio')    out = out.filter(a => SPECS[a.type].family === 'audio');
      else if (tab === 'video')    out = out.filter(a => SPECS[a.type].family === 'video' || a.type === 'music-video');
      if (filterType !== 'all') out = out.filter(a => a.type === filterType);
      if (search.trim()) {
        const q = search.trim().toLowerCase();
        out = out.filter(a => (a.name + ' ' + a.parent + ' ' + a.format + ' ' + a.owner).toLowerCase().includes(q));
      }
      if (filterDsp !== 'all') {
        out = out.filter(a => SPECS[a.type].dsps[filterDsp]);
      }
      if (filterStatus !== 'all') {
        out = out.filter(a => {
          const dsps = filterDsp === 'all' ? Object.keys(SPECS[a.type].dsps) : [filterDsp];
          const statuses = dsps.map(d => validate(a, d).status);
          if (filterStatus === 'fail') return statuses.includes('fail');
          if (filterStatus === 'warn') return statuses.includes('warn') && !statuses.includes('fail');
          if (filterStatus === 'pass') return statuses.every(s => s === 'pass' || s === 'na');
          return true;
        });
      }
      if (t.showOnlyIssues) {
        out = out.filter(a => {
          const dsps = Object.keys(SPECS[a.type].dsps);
          return dsps.some(d => validate(a, d).status !== 'pass' && validate(a, d).status !== 'na');
        });
      }
      return out;
    }, [library, tab, filterType, filterDsp, filterStatus, search, t.showOnlyIssues]);

    const selected = library.find(a => a.id === selectedId) || null;

    const TABS = [
      { v: 'library',   l: 'Library',         n: library.length },
      { v: 'releases',  l: 'Releases',        n: library.filter(a => a.parentKind === 'release').length },
      { v: 'artists',   l: 'Artists',         n: library.filter(a => a.parentKind === 'artist').length },
      { v: 'audio',     l: 'Audio',           n: library.filter(a => SPECS[a.type].family === 'audio').length },
      { v: 'video',     l: 'Video',           n: library.filter(a => SPECS[a.type].family === 'video' || a.type === 'music-video').length },
      { v: 'matrix',    l: 'DSP matrix',      n: null },
      { v: 'specs',     l: 'Specs',           n: ASSET_TYPES.length },
    ];

    // dsp list for matrix tab — union of all dsps
    const allDsps = useMemo(() => {
      const s = new Set();
      Object.values(SPECS).forEach(t => Object.keys(t.dsps).forEach(d => s.add(d)));
      return [...s];
    }, []);

    return (
      <div style={{ padding: '20px 28px 60px', minHeight: '100vh', background: 'var(--bg)' }}>
        {/* header */}
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 16, marginBottom: 18 }}>
          <div style={{ flex: 1 }}>
            <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.12em', color: 'var(--ink-3)', fontWeight: 600 }}>OPERATIONS · MEDIA</div>
            <h1 style={{ fontSize: 26, fontWeight: 400, margin: '4px 0 2px', letterSpacing: '-.01em' }}>Multimedia asset manager</h1>
            <div style={{ fontSize: 13, color: 'var(--ink-3)' }}>
              Release artwork · motion artwork · stereo &amp; spatial masters · music videos · artist profiles &amp; page motion — validated against each DSP's published spec.
            </div>
          </div>
          <button className="ff-mono upper" style={{
            fontSize: 10, letterSpacing: '.08em', padding: '8px 14px', border: 0,
            background: 'var(--ink-1)', color: '#fff', cursor: 'pointer', fontWeight: 600,
          }}>+ UPLOAD ASSETS</button>
        </div>

        <KpiStrip rows={library} />

        {/* tabs */}
        <div style={{ display: 'flex', gap: 24, borderBottom: '1px solid var(--rule)', marginBottom: 18 }}>
          {TABS.map(tb => (
            <button key={tb.v} onClick={() => setTab(tb.v)} className="ff-mono upper" style={{
              background: 'transparent', border: 0, padding: '8px 0', cursor: 'pointer',
              fontSize: 11, letterSpacing: '.08em', fontWeight: 600,
              color: tab === tb.v ? 'var(--ink-1)' : 'var(--ink-3)',
              borderBottom: tab === tb.v ? '2px solid var(--ink-1)' : '2px solid transparent',
              marginBottom: -1,
            }}>
              {tb.l}{tb.n != null && <span className="ff-mono" style={{ marginLeft: 6, color: 'var(--ink-3)', fontWeight: 400 }}>{tb.n}</span>}
            </button>
          ))}
        </div>

        {tab !== 'specs' && tab !== 'matrix' && (
          <>
            {/* filters */}
            <div style={{ display: 'flex', gap: 8, marginBottom: 14, alignItems: 'center', flexWrap: 'wrap' }}>
              <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search name, parent, owner, format…"
                style={{ flex: '1 1 280px', padding: '8px 12px', border: '1px solid var(--rule)', background: 'var(--paper)', fontSize: 13 }} />
              <select value={filterType} onChange={e => setFilterType(e.target.value)} className="ff-mono"
                style={{ padding: '8px 10px', border: '1px solid var(--rule)', background: 'var(--paper)', fontSize: 11 }}>
                <option value="all">All types</option>
                {ASSET_TYPES.map(a => <option key={a.k} value={a.k}>{a.label}</option>)}
              </select>
              <select value={filterDsp} onChange={e => setFilterDsp(e.target.value)} className="ff-mono"
                style={{ padding: '8px 10px', border: '1px solid var(--rule)', background: 'var(--paper)', fontSize: 11 }}>
                <option value="all">All DSPs</option>
                {allDsps.map(d => <option key={d} value={d}>{DSP_LABEL[d]}</option>)}
              </select>
              <select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="ff-mono"
                style={{ padding: '8px 10px', border: '1px solid var(--rule)', background: 'var(--paper)', fontSize: 11 }}>
                <option value="all">All statuses</option>
                <option value="pass">Passing</option>
                <option value="warn">Warning</option>
                <option value="fail">Failing</option>
              </select>
              <button onClick={() => { setSearch(''); setFilterType('all'); setFilterDsp('all'); setFilterStatus('all'); }}
                className="ff-mono upper" style={{ fontSize: 10, letterSpacing: '.08em', padding: '8px 12px', border: '1px solid var(--rule)', background: 'transparent', color: 'var(--ink-3)', cursor: 'pointer', fontWeight: 600 }}>RESET</button>
            </div>
            <LibraryGrid rows={filtered} onPick={setSelectedId} selectedId={selectedId} />
            {filtered.length === 0 && <div style={{ padding: '40px 20px', textAlign: 'center', color: 'var(--ink-3)', fontSize: 13 }}>No assets match these filters.</div>}
          </>
        )}

        {tab === 'matrix' && <ReadinessMatrix rows={library} dsps={allDsps} />}
        {tab === 'specs'  && <SpecSheet />}

        {selected && tab !== 'specs' && tab !== 'matrix' && (
          <AssetDrawer asset={selected} onClose={() => setSelectedId(null)} />
        )}

        {tweaksOpen && TweaksPanel && (
          <TweaksPanel onClose={() => setTweaksOpen(false)}>
            <TweakSection title="Display">
              <TweakToggle value={t.showOnlyIssues} onChange={v => setT('showOnlyIssues', v)} label="Issues only" />
              <TweakToggle value={t.groupByParent} onChange={v => setT('groupByParent', v)} label="Group by parent" />
            </TweakSection>
          </TweaksPanel>
        )}
      </div>
    );
  }

  Object.assign(window, {
    ScreenMam,
    MamSpecs: { SPECS, validate, fmtBytes, fmtDur, DSP_LABEL, DSP_TONE, ASSET_TYPES },
  });
})();
