{{--
    Bio page editor — all x-html targets are built from Alpine component state only
    (no unescaped server data injected). renderBlocksHtml / renderSocialsHtml compose
    strings from state.blocks and state.socials, which come from $initialState (PHP array
    encoded via @json). User-entered text is output via textContent-equivalent bindings
    (x-text) wherever XSS matters; x-html is used only for the live phone preview where
    we build structured HTML from trusted state properties (urls, titles, etc.).
--}}
<div x-data='bioEditor(@json($initialState))' class="bio-editor-root">

    {{-- =================== Topbar =================== --}}
    <div class="bio-topbar px-6 py-3 z-20 bg-s1/95 backdrop-blur-sm border-b border-b1 flex items-center gap-3" style="flex-shrink:0">
        <nav class="crumbs">
            <span>Link-in-Bio</span>
            <span class="sep">/</span>
            <span class="here">Editor</span>
        </nav>
        <div class="url-badge">
            <span class="dot"></span>
            <span x-text="displayUrl"></span>
        </div>
        <div style="flex:1"></div>
        <a href="{{ route('w.bio.preview', [$workspaceUlid, $bioUlid]) }}" target="_blank" class="btn ghost">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
            Preview
        </a>
        <button class="btn" @click="save()">Save draft</button>
        <button class="btn primary" @click="publish()">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
            <span x-text="state.isPublished ? 'Published' : 'Publish'"></span>
        </button>
    </div>

    {{-- =================== Editor + Preview Grid =================== --}}
    <div class="editor-layout">

        {{-- ============ Editor (left) ============ --}}
        <section class="editor">
            <h1 class="page-title">Link-in-Bio</h1>
            <p class="page-sub">Build a fully personalized landing page. Everything updates live in the preview on the right.</p>

            {{-- Section 1: Profile --}}
            <div class="section">
                <div class="section-head">
                    <span class="num">1</span>
                    <h3>Profile</h3>
                    <span class="hint">How you appear on top</span>
                </div>
                <div class="section-body">

                    {{-- Avatar + Name/Handle --}}
                    <div class="avatar-row">
                        <div style="position:relative;display:inline-block;flex-shrink:0">
                        <label class="avatar-up" for="avatarInput">
                            <img x-show="state.profile.avatar" :src="state.profile.avatar" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%">
                            <svg x-show="!state.profile.avatar" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
                            <div class="av-overlay">
                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
                            </div>
                            <input id="avatarInput" type="file" accept="image/*" hidden
                                @change="if($event.target.files[0]) uploadAvatar($event.target.files[0])">
                        </label>
                        <button x-show="state.profile.avatar" @click.prevent="state.profile.avatar = null"
                            style="position:absolute;top:-5px;right:-5px;width:20px;height:20px;border-radius:50%;background:var(--color-red);color:#fff;display:grid;place-items:center;font-size:12px;line-height:1;border:2px solid var(--color-s2)">×</button>
                        </div>
                        <div class="field-row">
                            <div class="field">
                                <label class="label" for="displayName">Display name</label>
                                <input class="input" id="displayName" type="text"
                                    x-model="state.profile.displayName"
                                    placeholder="Your name">
                            </div>
                            <div class="field">
                                <label class="label" for="handle">Handle</label>
                                <div class="input-prefix">
                                    <span class="prefix">@</span>
                                    <input class="input" id="handle" type="text"
                                        x-model="state.profile.handle"
                                        placeholder="yourhandle">
                                </div>
                            </div>
                        </div>
                    </div>

                    {{-- Bio textarea --}}
                    <div class="field">
                        <label class="label" for="bioText">Short bio</label>
                        <textarea class="textarea" id="bioText" maxlength="140"
                            x-model="state.profile.bio"
                            placeholder="Tell visitors who you are…"></textarea>
                        <div class="char-count"><span x-text="(state.profile.bio || '').length"></span>/140</div>
                    </div>

                    {{-- Verified toggle --}}
                    <div class="toggle-row" style="cursor:pointer"
                        @click="state.profile.verified = !state.profile.verified">
                        <div class="t-text">
                            <div class="t-title">Show verified badge</div>
                            <div class="t-desc">Adds a blue check next to your display name.</div>
                        </div>
                        <div class="switch" :class="{'is-on': state.profile.verified}"></div>
                    </div>

                </div>
            </div>

            {{-- Section 2: Theme --}}
            <div class="section">
                <div class="section-head">
                    <span class="num">2</span>
                    <h3>Theme</h3>
                    <span class="hint">Background, buttons, fonts</span>
                </div>
                <div class="section-body">

                    {{-- Preset themes --}}
                    <div class="field">
                        <span class="label">Preset themes</span>
                        <div class="theme-presets">
                            <button data-theme="midnight"
                                :class="{'is-active': state.theme.bg1==='#0f0a2e' && state.theme.bg2==='#6b1b9a'}"
                                @click="applyThemePreset('midnight')"
                                style="background: linear-gradient(160deg, #0f0a2e, #2c0e57 60%, #6b1b9a)"></button>
                            <button data-theme="sunset"
                                :class="{'is-active': state.theme.bg1==='#ff8a3d' && state.theme.bg2==='#b91c5c'}"
                                @click="applyThemePreset('sunset')"
                                style="background: linear-gradient(160deg, #ff8a3d, #ff3d77, #b91c5c)"></button>
                            <button data-theme="forest"
                                :class="{'is-active': state.theme.bg1==='#064e3b' && state.theme.bg2==='#0f766e'}"
                                @click="applyThemePreset('forest')"
                                style="background: linear-gradient(160deg, #064e3b, #065f46, #0f766e)"></button>
                            <button data-theme="paper"
                                :class="{'is-active': state.theme.bg1==='#faf8f3' && !state.theme.dark}"
                                @click="applyThemePreset('paper')"
                                style="background: #faf8f3; box-shadow:inset 0 0 0 1px #2a2a36"></button>
                            <button data-theme="ocean"
                                :class="{'is-active': state.theme.bg1==='#0c4a6e' && state.theme.bg2==='#0ea5e9'}"
                                @click="applyThemePreset('ocean')"
                                style="background: linear-gradient(160deg, #0c4a6e, #0369a1, #0ea5e9)"></button>
                            <button data-theme="candy"
                                :class="{'is-active': state.theme.bg1==='#fce7f3' && state.theme.bg2==='#f9a8d4'}"
                                @click="applyThemePreset('candy')"
                                style="background: linear-gradient(160deg, #fce7f3, #fbcfe8, #f9a8d4)"></button>
                        </div>
                    </div>

                    {{-- Background type --}}
                    <div class="field">
                        <span class="label">Background</span>
                        <div class="chip-group">
                            <div class="chip" :class="{'is-active': state.theme.bgType==='gradient'}"
                                @click="state.theme.bgType='gradient'">Gradient</div>
                            <div class="chip" :class="{'is-active': state.theme.bgType==='solid'}"
                                @click="state.theme.bgType='solid'">Solid</div>
                            <div class="chip" :class="{'is-active': state.theme.bgType==='image'}"
                                @click="state.theme.bgType='image'">Image</div>
                        </div>
                    </div>

                    {{-- Color rows --}}
                    <div class="field-row" x-show="state.theme.bgType !== 'image'">
                        <div class="field">
                            <span class="label">Color 1</span>
                            <div class="color-row">
                                <label class="color-swatch" :style="'background:'+state.theme.bg1">
                                    <input type="color" :value="state.theme.bg1"
                                        @input="syncColor('bg1', $event.target.value)">
                                </label>
                                <input class="input" type="text" :value="state.theme.bg1"
                                    @input="if(/^#[0-9a-f]{6}$/i.test($event.target.value)) syncColor('bg1', $event.target.value)">
                            </div>
                        </div>
                        <div class="field" x-show="state.theme.bgType === 'gradient'">
                            <span class="label">Color 2</span>
                            <div class="color-row">
                                <label class="color-swatch" :style="'background:'+state.theme.bg2">
                                    <input type="color" :value="state.theme.bg2"
                                        @input="syncColor('bg2', $event.target.value)">
                                </label>
                                <input class="input" type="text" :value="state.theme.bg2"
                                    @input="if(/^#[0-9a-f]{6}$/i.test($event.target.value)) syncColor('bg2', $event.target.value)">
                            </div>
                        </div>
                    </div>

                    {{-- Background image dropzone --}}
                    <div x-show="state.theme.bgType === 'image'">
                        <label class="bg-dropzone" for="bgImageInput">
                            <div class="bg-thumb">
                                <template x-if="!state.theme.bgImage">
                                    <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>
                                </template>
                                <template x-if="state.theme.bgImage">
                                    <img :src="state.theme.bgImage" alt="" style="width:100%;height:100%;object-fit:cover">
                                </template>
                            </div>
                            <div class="bg-info">
                                <div class="t" x-text="state.theme.bgImage ? 'Change background image' : 'Upload background image'"></div>
                                <div class="s">JPG, PNG · compressed to 1080px · up to 8 MB</div>
                            </div>
                            <input id="bgImageInput" type="file" accept="image/*" hidden
                                @change="if($event.target.files[0]) uploadBgImage($event.target.files[0])">
                        </label>
                        <button x-show="state.theme.bgImage"
                            @click="state.theme.bgImage = null"
                            class="btn ghost" style="width:100%;margin-top:6px;justify-content:center;color:var(--color-red)">
                            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-2 14a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L5 6"/></svg>
                            Remove image
                        </button>
                    </div>

                    {{-- Dark mode toggle --}}
                    <div class="toggle-row" style="cursor:pointer"
                        @click="state.theme.dark = !state.theme.dark">
                        <div class="t-text">
                            <div class="t-title">Light text on dark background</div>
                            <div class="t-desc">Switches text color for readability on dark themes.</div>
                        </div>
                        <div class="switch" :class="{'is-on': state.theme.dark}"></div>
                    </div>

                    {{-- Button style --}}
                    <div class="field">
                        <span class="label">Button style</span>
                        <div class="btn-style-grid">
                            <button class="btn-style-opt" data-style="rounded"
                                :class="{'is-active': state.theme.button==='rounded'}"
                                @click="state.theme.button='rounded'">
                                <span class="mini"></span><span class="lbl">Rounded</span>
                            </button>
                            <button class="btn-style-opt" data-style="square"
                                :class="{'is-active': state.theme.button==='square'}"
                                @click="state.theme.button='square'">
                                <span class="mini"></span><span class="lbl">Square</span>
                            </button>
                            <button class="btn-style-opt" data-style="pill"
                                :class="{'is-active': state.theme.button==='pill'}"
                                @click="state.theme.button='pill'">
                                <span class="mini"></span><span class="lbl">Pill</span>
                            </button>
                            <button class="btn-style-opt" data-style="outline"
                                :class="{'is-active': state.theme.button==='outline'}"
                                @click="state.theme.button='outline'">
                                <span class="mini"></span><span class="lbl">Outline</span>
                            </button>
                            <button class="btn-style-opt" data-style="shadow"
                                :class="{'is-active': state.theme.button==='shadow'}"
                                @click="state.theme.button='shadow'">
                                <span class="mini"></span><span class="lbl">Shadow</span>
                            </button>
                        </div>

                        {{-- Border color (always visible) --}}
                        <div class="field-row" style="margin-top:10px">
                            <div class="field">
                                <span class="label">Border color</span>
                                <div class="color-row">
                                    <label class="color-swatch" :style="'background:'+(state.theme.borderColor||'#7c3aed')">
                                        <input type="color" :value="state.theme.borderColor||'#7c3aed'"
                                            @input="syncColor('borderColor', $event.target.value)">
                                    </label>
                                    <input class="input" type="text" :value="state.theme.borderColor||'#7c3aed'"
                                        @input="if(/^#[0-9a-f]{6}$/i.test($event.target.value)) syncColor('borderColor', $event.target.value)">
                                </div>
                            </div>
                            <div class="field" x-show="state.theme.button==='shadow'">
                                <span class="label">Shadow color</span>
                                <div class="color-row">
                                    <label class="color-swatch" :style="'background:'+(state.theme.shadowColor||'#18181b')">
                                        <input type="color" :value="state.theme.shadowColor||'#18181b'"
                                            @input="syncColor('shadowColor', $event.target.value)">
                                    </label>
                                    <input class="input" type="text" :value="state.theme.shadowColor||'#18181b'"
                                        @input="if(/^#[0-9a-f]{6}$/i.test($event.target.value)) syncColor('shadowColor', $event.target.value)">
                                </div>
                            </div>
                        </div>
                    </div>

                    {{-- Font --}}
                    <div class="field">
                        <span class="label">Font</span>
                        <div class="font-grid">
                            <button class="font-opt" data-font="geist"
                                :class="{'is-active': state.theme.font==='geist'}"
                                @click="state.theme.font='geist'">
                                <div class="fn-name">Sans · Geist</div>
                                <div class="fn-sample" style="font-family:'Geist'">Ag · Hello</div>
                            </button>
                            <button class="font-opt" data-font="grotesk"
                                :class="{'is-active': state.theme.font==='grotesk'}"
                                @click="state.theme.font='grotesk'">
                                <div class="fn-name">Grotesk · Space</div>
                                <div class="fn-sample" style="font-family:'Space Grotesk'">Ag · Hello</div>
                            </button>
                            <button class="font-opt" data-font="serif"
                                :class="{'is-active': state.theme.font==='serif'}"
                                @click="state.theme.font='serif'">
                                <div class="fn-name">Serif · Playfair</div>
                                <div class="fn-sample" style="font-family:'Playfair Display'">Ag · Hello</div>
                            </button>
                            <button class="font-opt" data-font="display"
                                :class="{'is-active': state.theme.font==='display'}"
                                @click="state.theme.font='display'">
                                <div class="fn-name">Display · DM</div>
                                <div class="fn-sample" style="font-family:'DM Serif Display'">Ag · Hello</div>
                            </button>
                        </div>
                    </div>

                </div>
            </div>

            {{-- Section 3: Social Icons --}}
            <div class="section">
                <div class="section-head">
                    <span class="num">3</span>
                    <h3>Social icons</h3>
                    <span class="hint">Tap-row above your links</span>
                </div>
                <div class="section-body">

                    {{-- Current socials --}}
                    <div style="display:grid; gap:8px">
                        <template x-for="(social, idx) in state.socials" :key="social.id">
                            <div class="social-row">
                                <span class="social-icon-btn">
                                    <svg><use :href="'#i-' + social.platform"/></svg>
                                </span>
                                <input class="input" type="text"
                                    x-model="social.url"
                                    placeholder="https://...">
                                <div class="switch" :class="{'is-on': social.visible}"
                                    @click="social.visible = !social.visible"
                                    style="cursor:pointer"></div>
                                <button class="icon-del" aria-label="Remove"
                                    @click="removeSocial(social.id)">
                                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-2 14a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6M14 11v6"/></svg>
                                </button>
                            </div>
                        </template>
                    </div>

                    {{-- Add-social chips --}}
                    <div class="add-social">
                        <template x-for="platform in availableSocials" :key="platform.id">
                            <button class="add-social-chip" @click="addSocial(platform.id)">
                                <svg><use :href="'#i-' + platform.id"/></svg>
                                <span x-text="platform.name"></span>
                            </button>
                        </template>
                    </div>

                </div>
            </div>

            {{-- Section 4: Links & Blocks --}}
            <div class="section">
                <div class="section-head">
                    <span class="num">4</span>
                    <h3>Links &amp; Blocks</h3>
                    <span class="hint">Reorder with ↑↓, click row to expand</span>
                </div>
                <div class="section-body">
                    <div class="links-list">
                        <template x-for="(block, idx) in state.blocks" :key="block.id">
                            {{-- single root required by Alpine x-for; display:contents is invisible to the grid --}}
                            <div style="display:contents">

                            {{-- Header block --}}
                            <div x-show="block.type === 'header'" class="link-card"
                                :draggable="true"
                                @dragstart="onDragStart(block.id, $event)"
                                @dragover.prevent="onDragOver(block.id, $event)"
                                @drop.prevent="onDrop(block.id)"
                                @dragend="onDragEnd()"
                                :class="{'drag-over': dragOverId === block.id}">
                                <div class="link-head" style="grid-template-columns:auto 1fr auto auto auto">
                                    <span class="grip">
                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="6" r="1.2" fill="currentColor"/><circle cx="9" cy="12" r="1.2" fill="currentColor"/><circle cx="9" cy="18" r="1.2" fill="currentColor"/><circle cx="15" cy="6" r="1.2" fill="currentColor"/><circle cx="15" cy="12" r="1.2" fill="currentColor"/><circle cx="15" cy="18" r="1.2" fill="currentColor"/></svg>
                                    </span>
                                    <input class="input" type="text" placeholder="Section header"
                                        x-model="block.text"
                                        style="background:transparent;border:0;padding:6px 8px;font-weight:600">
                                    <button style="padding:8px;border-radius:6px;color:var(--color-t3);cursor:pointer" @click="moveBlock(block.id, 'up')" title="Move up">
                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>
                                    </button>
                                    <button style="padding:8px;border-radius:6px;color:var(--color-t3);cursor:pointer" @click="moveBlock(block.id, 'down')" title="Move down">
                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
                                    </button>
                                    <button class="lc-del" style="padding:8px;border-radius:6px;color:var(--color-t3);cursor:pointer" @click="removeBlock(block.id)" title="Delete">
                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-2 14a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L5 6"/></svg>
                                    </button>
                                </div>
                            </div>

                            {{-- Divider block --}}
                            <div x-show="block.type === 'divider'" class="link-card"
                                :draggable="true"
                                @dragstart="onDragStart(block.id, $event)"
                                @dragover.prevent="onDragOver(block.id, $event)"
                                @drop.prevent="onDrop(block.id)"
                                @dragend="onDragEnd()"
                                :class="{'drag-over': dragOverId === block.id}">
                                <div class="link-head" style="grid-template-columns:auto 1fr auto auto auto">
                                    <span class="grip">
                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="6" r="1.2" fill="currentColor"/><circle cx="9" cy="12" r="1.2" fill="currentColor"/><circle cx="9" cy="18" r="1.2" fill="currentColor"/><circle cx="15" cy="6" r="1.2" fill="currentColor"/><circle cx="15" cy="12" r="1.2" fill="currentColor"/><circle cx="15" cy="18" r="1.2" fill="currentColor"/></svg>
                                    </span>
                                    <span style="color: var(--color-t3); font-size: 13px;">— Divider —</span>
                                    <button style="padding:8px;border-radius:6px;color:var(--color-t3);cursor:pointer" @click="moveBlock(block.id, 'up')">
                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>
                                    </button>
                                    <button style="padding:8px;border-radius:6px;color:var(--color-t3);cursor:pointer" @click="moveBlock(block.id, 'down')">
                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
                                    </button>
                                    <button class="lc-del" style="padding:8px;border-radius:6px;color:var(--color-t3);cursor:pointer" @click="removeBlock(block.id)">
                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-2 14a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L5 6"/></svg>
                                    </button>
                                </div>
                            </div>

                            {{-- Link block --}}
                            <div x-show="block.type === 'link'" class="link-card"
                                :class="{'is-collapsed': block._collapsed, 'drag-over': dragOverId === block.id}"
                                :draggable="true"
                                @dragstart="onDragStart(block.id, $event)"
                                @dragover.prevent="onDragOver(block.id, $event)"
                                @drop.prevent="onDrop(block.id)"
                                @dragend="onDragEnd()">
                                <div class="link-head">
                                    <span class="grip">
                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="6" r="1.2" fill="currentColor"/><circle cx="9" cy="12" r="1.2" fill="currentColor"/><circle cx="9" cy="18" r="1.2" fill="currentColor"/><circle cx="15" cy="6" r="1.2" fill="currentColor"/><circle cx="15" cy="12" r="1.2" fill="currentColor"/><circle cx="15" cy="18" r="1.2" fill="currentColor"/></svg>
                                    </span>
                                    <div class="link-thumb">
                                        <template x-if="block.icon">
                                            <img :src="block.icon" alt="">
                                        </template>
                                        <template x-if="!block.icon">
                                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>
                                        </template>
                                    </div>
                                    <div style="min-width:0; display:grid; cursor:pointer"
                                        @click="block._collapsed = !block._collapsed">
                                        <div class="link-title-prev" x-text="block.title || 'Untitled link'"></div>
                                        <div class="link-url-prev" x-text="block.url || 'no url'"></div>
                                    </div>
                                    <div class="link-actions">
                                        <button @click="moveBlock(block.id, 'up')" title="Move up">
                                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>
                                        </button>
                                        <button @click="moveBlock(block.id, 'down')" title="Move down">
                                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
                                        </button>
                                        <button class="del" @click="removeBlock(block.id)" title="Delete">
                                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-2 14a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L5 6"/></svg>
                                        </button>
                                    </div>
                                    <div class="switch" :class="{'is-on': block.enabled}"
                                        @click.stop="block.enabled = !block.enabled"
                                        style="cursor:pointer"></div>
                                </div>
                                <div class="link-body">
                                    <div class="field">
                                        <label class="label">Title</label>
                                        <input class="input" type="text" x-model="block.title" placeholder="Link title">
                                    </div>
                                    <div class="field">
                                        <label class="label">Subtitle</label>
                                        <input class="input" type="text" x-model="block.subtitle" placeholder="Optional caption">
                                    </div>
                                    <div class="field">
                                        <label class="label">URL</label>
                                        <input class="input" type="text" x-model="block.url" placeholder="https://...">
                                    </div>
                                    <div class="field-row">
                                        <div class="field">
                                            <label class="label">Style</label>
                                            <select class="select" x-model="block.style">
                                                <option value="default">Default</option>
                                                <option value="feature">Featured (gradient)</option>
                                            </select>
                                        </div>
                                        <div class="toggle-row" style="padding:8px 12px; cursor:pointer"
                                            @click="block.pinned = !block.pinned">
                                            <div class="t-text">
                                                <div class="t-title" style="font-size:12.5px">Pinned</div>
                                            </div>
                                            <div class="switch" :class="{'is-on': block.pinned}"></div>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            </div>{{-- /display:contents wrapper --}}
                        </template>
                    </div>

                    {{-- Add block buttons --}}
                    <div class="add-block-row">
                        <button class="btn" @click="addBlock('link')">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
                            Add link
                        </button>
                        <button class="btn" @click="addBlock('header')">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="14" y2="12"/><line x1="4" y1="18" x2="18" y2="18"/></svg>
                            Add header
                        </button>
                        <button class="btn" @click="addBlock('divider')">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"/></svg>
                            Add divider
                        </button>
                    </div>

                </div>
            </div>

            {{-- Section 5: Page settings --}}
            <div class="section">
                <div class="section-head">
                    <span class="num">5</span>
                    <h3>Page settings</h3>
                    <span class="hint">URL &amp; visibility</span>
                </div>
                <div class="section-body">
                    <div class="field">
                        <label class="label">Page URL</label>
                        <div class="input-prefix">
                            <span class="prefix">{{ $displayUrlBase }}/</span>
                            <input class="input" type="text"
                                x-model="state.slug"
                                :class="slugTaken ? 'border-red/60 focus:ring-red/40 focus:border-red/60' : ''"
                                @input="
                                    state.slug = $event.target.value.replace(/[^a-z0-9\-_]/gi, '').toLowerCase();
                                    $event.target.value = state.slug;
                                    clearTimeout(_slugTimer);
                                    if (state.slug) {
                                        _slugTimer = setTimeout(async () => {
                                            slugTaken = await $wire.checkSlug(state.slug);
                                        }, 350);
                                    } else {
                                        slugTaken = false;
                                    }
                                "
                                placeholder="yourslug">
                        </div>
                        <div x-show="slugTaken" x-cloak class="mt-1 text-xs text-red">Dieser Slug ist bereits vergeben.</div>
                        <div class="help" x-show="!slugTaken">Lowercase, no spaces. Connect a custom domain in Domains.</div>
                    </div>
                    <div class="toggle-row" style="cursor:pointer"
                        @click="state.isPublished = !state.isPublished">
                        <div class="t-text">
                            <div class="t-title">Published</div>
                            <div class="t-desc">When enabled, your bio page is publicly visible.</div>
                        </div>
                        <div class="switch" :class="{'is-on': state.isPublished}"></div>
                    </div>
                </div>
            </div>

        </section>

        {{-- ============ Preview (right) ============ --}}
        <aside class="preview-pane">
            <div class="device-toolbar">
                <div class="device-tabs">
                    <div class="device-tab" :class="device==='mobile' && 'is-active'" @click="device='mobile'">
                        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2"/><line x1="12" y1="18" x2="12" y2="18"/></svg>
                        Mobile
                    </div>
                    <div class="device-tab" :class="device==='desktop' && 'is-active'" @click="device='desktop'">
                        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
                        Desktop
                    </div>
                </div>
                <div class="preview-meta-mini" x-text="displayUrl"></div>
            </div>

            <div class="device" :class="device" aria-label="Live preview">
                <div class="device-screen">
                    <div class="bio-scroll">
                        <div class="bio"
                            :class="{'is-dark': state.theme.dark}"
                            :data-btn="state.theme.button"
                            :data-font="state.theme.font"
                            :style="`--bio-btn-border:${state.theme.borderColor||'#7c3aed'};--bio-btn-shadow:${state.theme.shadowColor||'#18181b'}`">
                            <div class="bg-overlay" :style="previewBgStyle"></div>
                            {{-- bioAvatarHtml builds <img src="data:..."> or <div class="av-fallback">X</div> from state.profile --}}
                            <div class="bio-avatar" x-html="bioAvatarHtml"></div>
                            {{-- bioNameHtml appends verified badge SVG; source is state.profile.displayName --}}
                            <h1 class="bio-name" x-html="bioNameHtml"></h1>
                            <p class="bio-handle" x-text="'@' + state.profile.handle"></p>
                            <p class="bio-text" x-text="state.profile.bio"></p>
                            {{-- renderSocialsHtml / renderBlocksHtml build preview <a> tags from state arrays --}}
                            <div class="bio-socials" x-html="renderSocialsHtml()"></div>
                            <div class="bio-links" x-html="renderBlocksHtml()"></div>
                            <div class="bio-footer">
                                <span class="made">Made with <strong>nimuli</strong></span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="preview-actions">
                <a :href="publicUrl" target="_blank" class="btn" style="flex:1; justify-content:center">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><line x1="3" y1="12" x2="21" y2="12"/><path d="M12 3a14 14 0 0 1 0 18a14 14 0 0 1 0-18"/></svg>
                    View public
                </a>
                <button class="btn copy-link-btn" style="flex:1; justify-content:center"
                    x-data="{copied:false}"
                    @click="navigator.clipboard?.writeText(publicUrl); copied=true; setTimeout(()=>copied=false,1200)">
                    <template x-if="!copied">
                        <span style="display:contents">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
                            Copy link
                        </span>
                    </template>
                    <template x-if="copied">
                        <span>Copied!</span>
                    </template>
                </button>
            </div>
        </aside>

    </div>

    {{-- =================== Crop Modal =================== --}}
    <div x-show="cropState.open" class="crop-modal" style="display:none"
        @mousemove="cropDragMove($event)" @mouseup="cropDragEnd()">
        <div class="crop-dialog" @click.stop>
            <div class="crop-title">Crop image</div>
            <p class="crop-hint">Drag to reposition · scroll or slider to zoom</p>
            <div class="crop-stage" x-ref="cropStage"
                @mousedown.prevent="cropDragStart($event)"
                @touchstart.prevent="cropDragStart($event)"
                @touchmove.prevent="cropDragMove($event)"
                @touchend="cropDragEnd()"
                @wheel.prevent="cropZoom($event)">
                <img x-ref="cropImg"
                    :src="cropState.src || ''"
                    x-show="cropState.src"
                    @load="cropImgLoaded()"
                    :style="`transform: translate(calc(-50% + ${cropState.x}px), calc(-50% + ${cropState.y}px)) scale(${cropState.scale})`"
                    draggable="false">
                <div class="crop-frame"></div>
            </div>
            <div class="crop-zoom-row">
                <span class="crop-zoom-icon">−</span>
                <input type="range" :min="cropState.minScale" max="4" step="0.02" x-model.number="cropState.scale">
                <span class="crop-zoom-icon">+</span>
            </div>
            <div class="crop-btns">
                <button class="btn ghost" @click="cropState.open = false">Cancel</button>
                <button class="btn primary" @click="applyCrop()">Apply crop</button>
            </div>
        </div>
    </div>

    {{-- =================== SVG Social Icon Sprite =================== --}}
    <svg width="0" height="0" style="position:absolute" aria-hidden="true">
        <defs>
            <symbol id="i-instagram" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="5" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="12" cy="12" r="4" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="17.5" cy="6.5" r="1.2" fill="currentColor"/></symbol>
            <symbol id="i-x" viewBox="0 0 24 24"><path d="M4 4l16 16M20 4L4 20" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></symbol>
            <symbol id="i-tiktok" viewBox="0 0 24 24"><path d="M14 4v9a4 4 0 1 1-4-4M14 4a5 5 0 0 0 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></symbol>
            <symbol id="i-youtube" viewBox="0 0 24 24"><rect x="2.5" y="6" width="19" height="12" rx="3" fill="none" stroke="currentColor" stroke-width="2"/><polygon points="10,9 16,12 10,15" fill="currentColor"/></symbol>
            <symbol id="i-linkedin" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" fill="none" stroke="currentColor" stroke-width="2"/><line x1="8" y1="10" x2="8" y2="16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="8" cy="7.5" r="1" fill="currentColor"/><path d="M12 16v-3a2 2 0 0 1 4 0v3M12 10v6" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/></symbol>
            <symbol id="i-github" viewBox="0 0 24 24"><path d="M9 19c-4 1-4-2-6-2m12 4v-3.5c0-1 0-1.5-.5-2 3-.5 5-2 5-5.5 0-1.5-.5-2.5-1-3.5.5-1.5 0-3-.5-3.5C16 3 14.5 4 13 5h-2C9.5 4 8 3 6.5 3.5 6 4 5.5 5.5 6 7c-.5 1-1 2-1 3.5 0 3.5 2 5 5 5.5-.5.5-.5 1-.5 2V21" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></symbol>
            <symbol id="i-facebook" viewBox="0 0 24 24"><path d="M13 22V12h3l1-4h-4V6c0-1 0-2 2-2h2V0h-3c-3 0-4 2-4 4v4H7v4h3v10z" fill="currentColor"/></symbol>
            <symbol id="i-spotify" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2"/><path d="M7 9.5c3-1 8-1 11 1M7.5 13c2.5-1 6-.7 8.5.8M8 16c2-.7 4.5-.6 6.5.4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></symbol>
            <symbol id="i-discord" viewBox="0 0 24 24"><path d="M7 8c2-1 8-1 10 0l1 1c1 2 2 5 1.5 8l-3 2-1-2c-3 1-5 1-7 0l-1 2-3-2c-.5-3 .5-6 1.5-8z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><circle cx="9.5" cy="13" r="1" fill="currentColor"/><circle cx="14.5" cy="13" r="1" fill="currentColor"/></symbol>
            <symbol id="i-pinterest" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2"/><path d="M11 7c-2 0-4 1.5-4 4 0 1.5 1 2.5 1.5 2.5s.5-1 .5-1.5c-.5-1 0-3.5 2-3.5s2.5 2 2 4-2 3-3 2c-.5 2-1.5 4-1.5 4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></symbol>
            <symbol id="i-twitch" viewBox="0 0 24 24"><path d="M4 4h16v10l-4 4h-4l-2 2H8v-2H4V4z M9 8v5 M14 8v5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" stroke-linecap="round"/></symbol>
            <symbol id="i-snapchat" viewBox="0 0 24 24"><path d="M5 16c2 0 3-1.5 3-3.5V10c0-3 2-5 4-5s4 2 4 5v2.5c0 2 1 3.5 3 3.5-.5 1-2 1.5-3 1.5-.5 1-1.5 2-4 2s-3.5-1-4-2c-1 0-2.5-.5-3-1.5z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></symbol>
            <symbol id="i-website" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2"/><line x1="3" y1="12" x2="21" y2="12" stroke="currentColor" stroke-width="2"/><path d="M12 3a14 14 0 0 1 0 18a14 14 0 0 1 0-18" fill="none" stroke="currentColor" stroke-width="2"/></symbol>
            <symbol id="i-email" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2" fill="none" stroke="currentColor" stroke-width="2"/><polyline points="3 7 12 13 21 7" fill="none" stroke="currentColor" stroke-width="2"/></symbol>
            <symbol id="i-whatsapp" viewBox="0 0 24 24"><path d="M3 21l1.5-4.5A8 8 0 1 1 8 20z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M9 10c0 4 3 6 6 6l1.5-1.5-2.5-1.5c-.5.5-1 1-1.5.5s-2-2-2.5-3 0-1 .5-1.5L9 10z" fill="currentColor"/></symbol>
        </defs>
    </svg>


</div>
{{-- script outside root div: libxml2 parseHTML() interprets </div> in JS strings as closing the outer element --}}
<script>
function bioEditor(init) {
    let uid = (init.uid || 100) + 1;
        return {
            device: 'mobile',
            dragSrcId: null,
            dragOverId: null,
            slugTaken: false,
            _slugTimer: null,
            cropState: { open: false, src: null, type: null, x: 0, y: 0, scale: 1, minScale: 1, _drag: null },

            state: {
                profile: init.profile || {avatar: null, displayName: '', handle: '', bio: '', verified: false},
                theme: init.theme || {bgType: 'gradient', bg1: '#0f0a2e', bg2: '#6b1b9a', bgImage: null, dark: true, button: 'rounded', font: 'geist', borderColor: '#7c3aed', shadowColor: '#18181b'},
                socials: init.socials || [],
                blocks: (init.blocks || []).map(b => ({...b, _collapsed: true})),
                slug: init.slug || '',
                isPublished: init.isPublished || false,
            },

            THEMES: {
                midnight: {bgType: 'gradient', bg1: '#0f0a2e', bg2: '#6b1b9a', dark: true},
                sunset:   {bgType: 'gradient', bg1: '#ff8a3d', bg2: '#b91c5c', dark: true},
                forest:   {bgType: 'gradient', bg1: '#064e3b', bg2: '#0f766e', dark: true},
                paper:    {bgType: 'solid',    bg1: '#faf8f3', bg2: '#faf8f3', dark: false},
                ocean:    {bgType: 'gradient', bg1: '#0c4a6e', bg2: '#0ea5e9', dark: true},
                candy:    {bgType: 'gradient', bg1: '#fce7f3', bg2: '#f9a8d4', dark: false},
            },

            SOCIAL_LIB: [
                {id: 'instagram', name: 'Instagram'},
                {id: 'tiktok',    name: 'TikTok'},
                {id: 'x',         name: 'X'},
                {id: 'youtube',   name: 'YouTube'},
                {id: 'linkedin',  name: 'LinkedIn'},
                {id: 'github',    name: 'GitHub'},
                {id: 'facebook',  name: 'Facebook'},
                {id: 'spotify',   name: 'Spotify'},
                {id: 'discord',   name: 'Discord'},
                {id: 'pinterest', name: 'Pinterest'},
                {id: 'twitch',    name: 'Twitch'},
                {id: 'snapchat',  name: 'Snapchat'},
                {id: 'whatsapp',  name: 'WhatsApp'},
                {id: 'website',   name: 'Website'},
                {id: 'email',     name: 'Email'},
            ],

            get availableSocials() {
                const existing = new Set(this.state.socials.map(s => s.platform));
                return this.SOCIAL_LIB.filter(p => !existing.has(p.id));
            },

            get previewBgStyle() {
                const t = this.state.theme;
                if (t.bgType === 'solid') return 'background: ' + t.bg1;
                if (t.bgType === 'image' && t.bgImage) return 'background: url(' + t.bgImage + ') center/cover no-repeat';
                return 'background: linear-gradient(160deg, ' + t.bg1 + ', ' + t.bg2 + ')';
            },

            get publicUrl() {
                return {{ Js::from($publicUrlBase) }} + '/' + this.state.slug;
            },

            get displayUrl() {
                return {{ Js::from($displayUrlBase) }} + '/' + (this.state.slug || '…');
            },

            ensureHttp(url) {
                if (!url) return '#';
                if (/^(https?:|mailto:|tel:)/i.test(url)) return url;
                return 'https://' + url;
            },

            // Preview HTML builders — all values sourced from Alpine state (not raw server HTML)
            renderBlocksHtml() {
                return this.state.blocks
                    .filter(b => b.type !== 'link' || b.enabled !== false)
                    .map(b => {
                        if (b.type === 'header') {
                            const txt = document.createElement('span');
                            txt.textContent = b.text || '';
                            return '<div class="bio-header">' + txt.innerHTML + '</div>';
                        }
                        if (b.type === 'divider') return '<div class="bio-divider"></div>';
                        const cls = 'bio-link' + (b.style === 'feature' ? ' is-feature' : '') + (b.pinned ? ' is-pinned' : '');
                        const icon = b.icon
                            ? '<span class="bl-icon"><img src="' + b.icon + '" alt=""></span>'
                            : '<span class="bl-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07L11.59 4.34"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.41-1.41"/></svg></span>';
                        const titleEl = document.createElement('span'); titleEl.textContent = b.title || 'Untitled';
                        const subEl = document.createElement('span'); subEl.textContent = b.subtitle || '';
                        return '<a class="' + cls + '" href="' + this.ensureHttp(b.url) + '" target="_blank" rel="noopener">'
                            + (b.pinned ? '<span class="pin-badge">Pinned</span>' : '')
                            + icon
                            + '<div class="bl-text"><div class="bl-title">' + titleEl.innerHTML + '</div>'
                            + (b.subtitle ? '<div class="bl-sub">' + subEl.innerHTML + '</div>' : '')
                            + '</div>'
                            + '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:.5"><polyline points="9 18 15 12 9 6"/></svg>'
                            + '</a>';
                    }).join('');
            },

            renderSocialsHtml() {
                return this.state.socials.filter(s => s.visible && s.url).map(s =>
                    '<a class="bio-social" href="' + this.ensureHttp(s.url) + '" target="_blank" rel="noopener" aria-label="' + s.platform + '">'
                    + '<svg><use href="#i-' + s.platform + '"/></svg>'
                    + '</a>'
                ).join('');
            },

            get bioNameHtml() {
                const el = document.createElement('span');
                el.textContent = this.state.profile.displayName || '';
                const safeName = el.innerHTML;
                return safeName + (this.state.profile.verified
                    ? '<span class="verified-badge"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg></span>'
                    : '');
            },

            get bioAvatarHtml() {
                if (this.state.profile.avatar) {
                    return '<img src="' + this.state.profile.avatar + '" alt="">';
                }
                return '<div class="av-fallback"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg></div>';
            },

            applyThemePreset(key) {
                const t = this.THEMES[key];
                if (!t) return;
                Object.assign(this.state.theme, t);
            },

            addSocial(platform) {
                this.state.socials.push({id: uid++, platform, url: '', visible: true});
            },

            removeSocial(id) {
                this.state.socials = this.state.socials.filter(s => s.id !== id);
            },

            addBlock(type) {
                if (type === 'link') {
                    this.state.blocks.push({id: uid++, type: 'link', title: '', subtitle: '', url: '', icon: null, style: 'default', pinned: false, enabled: true, _collapsed: false});
                } else if (type === 'header') {
                    this.state.blocks.push({id: uid++, type: 'header', text: 'New section'});
                } else if (type === 'divider') {
                    this.state.blocks.push({id: uid++, type: 'divider'});
                }
            },

            removeBlock(id) {
                this.state.blocks = this.state.blocks.filter(b => b.id !== id);
            },

            moveBlock(id, dir) {
                const i = this.state.blocks.findIndex(b => b.id === id);
                if (dir === 'up' && i > 0) {
                    [this.state.blocks[i], this.state.blocks[i - 1]] = [this.state.blocks[i - 1], this.state.blocks[i]];
                }
                if (dir === 'down' && i < this.state.blocks.length - 1) {
                    [this.state.blocks[i], this.state.blocks[i + 1]] = [this.state.blocks[i + 1], this.state.blocks[i]];
                }
            },

            onDragStart(id, e) {
                this.dragSrcId = id;
                e.dataTransfer.effectAllowed = 'move';
            },

            onDragOver(id, e) {
                e.preventDefault();
                e.dataTransfer.dropEffect = 'move';
                this.dragOverId = id;
            },

            onDrop(id) {
                if (this.dragSrcId === null || this.dragSrcId === id) {
                    this.dragSrcId = null; this.dragOverId = null; return;
                }
                const from = this.state.blocks.findIndex(b => b.id === this.dragSrcId);
                const to   = this.state.blocks.findIndex(b => b.id === id);
                if (from === -1 || to === -1) { this.dragSrcId = null; this.dragOverId = null; return; }
                const arr = [...this.state.blocks];
                const [moved] = arr.splice(from, 1);
                arr.splice(to, 0, moved);
                this.state.blocks = arr;
                this.dragSrcId = null; this.dragOverId = null;
            },

            onDragEnd() {
                this.dragSrcId = null; this.dragOverId = null;
            },

            // Read JPEG EXIF orientation tag from first 64 KB (no library needed)
            getOrientation(file) {
                return new Promise((resolve) => {
                    const r = new FileReader();
                    r.onload = (e) => {
                        let o = 1;
                        try {
                            const v = new DataView(e.target.result);
                            if (v.getUint16(0, false) === 0xFFD8) {
                                let off = 2;
                                while (off < v.byteLength) {
                                    const m = v.getUint16(off, false); off += 2;
                                    if (m === 0xFFE1) {
                                        if (v.getUint32((off += 2), false) === 0x45786966) {
                                            const le = v.getUint16((off += 6), false) === 0x4949;
                                            off += v.getUint32(off + 4, le);
                                            const n = v.getUint16(off, le); off += 2;
                                            for (let i = 0; i < n; i++) {
                                                if (v.getUint16(off + i * 12, le) === 0x0112) {
                                                    o = v.getUint16(off + i * 12 + 8, le); break;
                                                }
                                            }
                                        }
                                        break;
                                    } else if ((m & 0xFF00) !== 0xFF00) break;
                                    else off += v.getUint16(off, false);
                                }
                            }
                        } catch (_) {}
                        resolve(o);
                    };
                    r.readAsArrayBuffer(file.slice(0, 65536));
                });
            },

            // Draw image to a correctly-oriented canvas
            applyOrientation(img, o) {
                const w = img.naturalWidth, h = img.naturalHeight;
                const swap = o >= 5;
                const c = document.createElement('canvas');
                c.width = swap ? h : w; c.height = swap ? w : h;
                const ctx = c.getContext('2d');
                if (o === 2) ctx.transform(-1,0,0,1,w,0);
                else if (o === 3) ctx.transform(-1,0,0,-1,w,h);
                else if (o === 4) ctx.transform(1,0,0,-1,0,h);
                else if (o === 5) ctx.transform(0,1,1,0,0,0);
                else if (o === 6) ctx.transform(0,1,-1,0,h,0);
                else if (o === 7) ctx.transform(0,-1,-1,0,h,w);
                else if (o === 8) ctx.transform(0,-1,1,0,0,w);
                ctx.drawImage(img, 0, 0);
                return c;
            },

            // Resize + EXIF-fix (for background images — no crop needed)
            async compressImage(file, maxDim, quality) {
                const orientation = await this.getOrientation(file);
                return new Promise((resolve) => {
                    const img = new Image();
                    img.onload = () => {
                        const oriented = this.applyOrientation(img, orientation);
                        let w = oriented.width, h = oriented.height;
                        if (w > maxDim || h > maxDim) {
                            const r = maxDim / Math.max(w, h);
                            w = Math.round(w * r); h = Math.round(h * r);
                        }
                        const c = document.createElement('canvas');
                        c.width = w; c.height = h;
                        c.getContext('2d').drawImage(oriented, 0, 0, w, h);
                        URL.revokeObjectURL(img.src);
                        resolve(c.toDataURL('image/jpeg', quality));
                    };
                    img.src = URL.createObjectURL(file);
                });
            },

            // Avatar: fix EXIF then open crop modal
            async uploadAvatar(file) {
                const orientation = await this.getOrientation(file);
                const img = new Image();
                await new Promise(res => { img.onload = res; img.src = URL.createObjectURL(file); });
                const oriented = this.applyOrientation(img, orientation);
                URL.revokeObjectURL(img.src);
                this.cropState = {
                    open: true, src: oriented.toDataURL('image/jpeg', 0.95),
                    type: 'avatar', x: 0, y: 0, scale: 1, minScale: 1, _drag: null,
                };
            },

            // Bg image: EXIF fix + resize only
            async uploadBgImage(file) {
                this.state.theme.bgImage = await this.compressImage(file, 1080, 0.80);
            },

            // Crop modal — image loaded: fit whole image in view, allow zoom in/out
            cropImgLoaded() {
                const img = this.$refs.cropImg;
                if (!img || !img.naturalWidth) return;
                const { naturalWidth: nw, naturalHeight: nh } = img;
                const fitScale = +Math.min(240 / nw, 240 / nh).toFixed(3);
                const minScale = +Math.max(0.05, fitScale * 0.3).toFixed(3);
                this.cropState.minScale = minScale;
                this.cropState.scale   = fitScale;
                this.cropState.x = 0; this.cropState.y = 0;
            },

            cropDragStart(e) {
                const p = e.touches ? e.touches[0] : e;
                this.cropState._drag = { sx: p.clientX, sy: p.clientY, ox: this.cropState.x, oy: this.cropState.y };
            },

            cropDragMove(e) {
                if (!this.cropState._drag) return;
                e.preventDefault();
                const p = e.touches ? e.touches[0] : e;
                this.cropState.x = this.cropState._drag.ox + (p.clientX - this.cropState._drag.sx);
                this.cropState.y = this.cropState._drag.oy + (p.clientY - this.cropState._drag.sy);
            },

            cropDragEnd() { this.cropState._drag = null; },

            cropZoom(e) {
                const delta = e.deltaY < 0 ? 0.1 : -0.1;
                this.cropState.scale = Math.max(
                    this.cropState.minScale,
                    Math.min(4, +(this.cropState.scale + delta).toFixed(3))
                );
            },

            // Extract the 240×240 crop area and output as 400×400 JPEG
            applyCrop() {
                const img = this.$refs.cropImg;
                if (!img) return;
                const OUT = 400, CROP = 240, STAGE = 300;
                const { naturalWidth: nw, naturalHeight: nh } = img;
                const { scale, x, y } = this.cropState;
                // Image top-left position in stage coords (image is centered at stage center + offset)
                const imgX = STAGE / 2 + x - (nw * scale) / 2;
                const imgY = STAGE / 2 + y - (nh * scale) / 2;
                // Crop frame top-left in stage: (30, 30) — centered in 300px stage with 30px padding
                const srcX = (30 - imgX) / scale;
                const srcY = (30 - imgY) / scale;
                const srcW = CROP / scale;
                const srcH = CROP / scale;
                const c = document.createElement('canvas');
                c.width = OUT; c.height = OUT;
                c.getContext('2d').drawImage(img, srcX, srcY, srcW, srcH, 0, 0, OUT, OUT);
                this.state.profile.avatar = c.toDataURL('image/jpeg', 0.85);
                this.cropState.open = false;
            },

            syncColor(key, val) {
                this.state.theme[key] = val;
            },

            async save() {
                const snap = JSON.parse(JSON.stringify(this.state));
                await this.$wire.save(snap);
                this.state.profile     = snap.profile;
                this.state.theme       = snap.theme;
                this.state.socials     = snap.socials;
                this.state.blocks      = snap.blocks;
                this.state.slug        = snap.slug;
                this.state.isPublished = false;
            },

            async publish() {
                const snap = JSON.parse(JSON.stringify(this.state));
                await this.$wire.publish(snap);
                this.state.profile     = snap.profile;
                this.state.theme       = snap.theme;
                this.state.socials     = snap.socials;
                this.state.blocks      = snap.blocks;
                this.state.slug        = snap.slug;
                this.state.isPublished = true;
            },

        };
    }
    </script>

@script
<script>
(function () {
    var mainEl    = document.querySelector('main.flex-1');
    var contentEl = mainEl && mainEl.parentElement;
    var rootEl    = mainEl && mainEl.querySelector('.bio-editor-root');

    function applyLayout() {
        if (window.innerWidth > 1180 && mainEl && contentEl && rootEl) {
            contentEl.style.height  = '100dvh';
            mainEl.style.overflow   = 'hidden';
            mainEl.style.padding    = '0';
            mainEl.style.minHeight  = '0';
            rootEl.style.height     = '100%';
            rootEl.style.overflow   = 'hidden';
        }
    }

    applyLayout();

    Livewire.hook('commit', function (ref) {
        ref.succeed(function () {
            requestAnimationFrame(applyLayout);
        });
    });

    document.addEventListener('livewire:navigating', function restore() {
        if (contentEl) contentEl.style.height = '';
        if (mainEl)    { mainEl.style.overflow = ''; mainEl.style.padding = ''; mainEl.style.minHeight = ''; }
        if (rootEl)    { rootEl.style.height = ''; rootEl.style.overflow = ''; }
        document.removeEventListener('livewire:navigating', restore);
    });
})();
</script>
@endscript

