<!DOCTYPE html>
<html lang="sv">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ThorgrensGrenar Hub</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f4f0; color: #1a1a1a; min-height: 100vh; }
.topbar { background: white; border-bottom: 1px solid #e8e5de; padding: 14px 24px; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 10; }
.logo { font-size: 18px; font-weight: 600; letter-spacing: -0.3px; }
.logo span { color: #1D9E75; }
.topbar-right { display: flex; align-items: center; gap: 10px; }
.lager-count { font-size: 12px; color: #888; background: #f5f4f0; padding: 4px 10px; border-radius: 20px; }
.btn-new { background: #1D9E75; color: white; border: none; padding: 7px 14px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit; }
.btn-new:hover { background: #0F6E56; }
.hub { max-width: 900px; margin: 0 auto; padding: 2rem 1.5rem; }
/* LAGERLISTA */
.inventory { display: none; }
.inventory.active { display: block; }
.inv-filters { display: flex; gap: 8px; margin-bottom: 1.25rem; flex-wrap: wrap; align-items: center; }
.inv-filters input { flex: 1; min-width: 180px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; font-family: inherit; }
.inv-filters input:focus { outline: none; border-color: #1D9E75; }
.filter-btn { padding: 7px 14px; border-radius: 20px; font-size: 12px; font-weight: 600; cursor: pointer; border: 1px solid #ddd; background: white; color: #555; font-family: inherit; transition: all 0.1s; }
.filter-btn.on { background: #1D9E75; color: white; border-color: #1D9E75; }
.inv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; }
.inv-card { background: white; border: 1px solid #e8e5de; border-radius: 10px; overflow: hidden; cursor: pointer; transition: all 0.15s; }
.inv-card:hover { border-color: #1D9E75; transform: translateY(-1px); }
.inv-thumb { aspect-ratio: 4/3; background: #f0ede5; overflow: hidden; position: relative; }
.inv-thumb img { width: 100%; height: 100%; object-fit: cover; }
.inv-thumb-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 28px; }
.inv-body { padding: 10px 12px; }
.inv-title { font-size: 13px; font-weight: 600; margin-bottom: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.inv-meta { font-size: 11px; color: #888; }
.inv-price { font-size: 13px; font-weight: 600; color: #1D9E75; margin-top: 4px; }
.status-badge { display: inline-block; font-size: 10px; font-weight: 600; padding: 2px 7px; border-radius: 10px; margin-top: 4px; }
.status-active { background: #E1F5EE; color: #0F6E56; }
.status-sold { background: #f0ede5; color: #888; }
.status-draft { background: #FFF4E6; color: #7A4500; }
.empty-state { text-align: center; padding: 4rem 2rem; color: #aaa; }
.empty-state p { font-size: 15px; margin-bottom: 8px; }
.empty-state span { font-size: 13px; }
/* EDITOR */
.editor { display: none; }
.editor.active { display: block; }
.editor-header { display: flex; align-items: center; gap: 12px; margin-bottom: 1.5rem; }
.back-btn { background: none; border: none; font-size: 13px; color: #1D9E75; cursor: pointer; font-family: inherit; font-weight: 600; padding: 0; display: flex; align-items: center; gap: 4px; }
.editor-title { font-size: 16px; font-weight: 600; }
.step-bar { display: flex; background: white; border-radius: 12px; border: 1px solid #e8e5de; overflow: hidden; margin-bottom: 2rem; }
.step { flex: 1; padding: 12px 8px; font-size: 12px; color: #999; text-align: center; cursor: pointer; border-right: 1px solid #e8e5de; transition: all 0.15s; user-select: none; }
.step:last-child { border-right: none; }
.step.active { color: #0F6E56; font-weight: 600; background: #f0faf6; }
.step.done { color: #1D9E75; }
.step-num { display: block; font-size: 18px; font-weight: 700; margin-bottom: 2px; }
.step.active .step-num { color: #1D9E75; }
.step.done .step-num { color: #9FE1CB; }
.panel { display: none; animation: fadeIn 0.2s ease; }
.panel.active { display: block; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
.card { background: white; border-radius: 12px; border: 1px solid #e8e5de; padding: 1.5rem; margin-bottom: 1.25rem; }
.card-title { font-size: 13px; font-weight: 600; color: #555; margin-bottom: 1rem; text-transform: uppercase; letter-spacing: 0.5px; }
.upload-zone { border: 2px dashed #c8e6de; border-radius: 10px; padding: 2.5rem 2rem; text-align: center; cursor: pointer; transition: all 0.2s; background: #f8fdfb; }
.upload-zone:hover { border-color: #1D9E75; background: #f0faf6; }
.upload-icon { width: 48px; height: 48px; margin: 0 auto 0.75rem; background: #E1F5EE; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
.upload-label { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
.upload-sub { font-size: 12px; color: #888; }
.thumb-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; margin-top: 1rem; }
.thumb { aspect-ratio: 1; border-radius: 8px; border: 1px solid #e8e5de; display: flex; align-items: center; justify-content: center; font-size: 11px; color: #999; position: relative; overflow: hidden; cursor: grab; transition: opacity 0.15s, transform 0.15s; }
.thumb:active { cursor: grabbing; }
.thumb.dragging { opacity: 0.4; transform: scale(0.96); }
.thumb.drag-over { border: 2px solid #1D9E75; }
.thumb .del { position: absolute; top: 3px; right: 3px; width: 18px; height: 18px; background: rgba(0,0,0,0.35); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 11px; }
.thumb .order-num { position: absolute; bottom: 3px; left: 5px; font-size: 10px; font-weight: 700; color: white; text-shadow: 0 1px 2px rgba(0,0,0,0.6); }
.field-grid { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 10px; }
.field-grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; }
.field-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.field { display: flex; flex-direction: column; gap: 5px; }
.field label { font-size: 12px; color: #777; font-weight: 500; }
.field.full { grid-column: 1 / -1; }
input[type=number], input[type=text], textarea, select { width: 100%; padding: 9px 11px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; font-family: inherit; background: white; color: #1a1a1a; transition: border-color 0.15s; }
input:focus, textarea:focus, select:focus { outline: none; border-color: #1D9E75; }
textarea { resize: vertical; min-height: 90px; line-height: 1.6; }
.ai-box { background: #f0faf6; border: 1px solid #9FE1CB; border-radius: 10px; padding: 1rem 1.25rem; margin-bottom: 1.25rem; }
.ai-label { font-size: 12px; font-weight: 600; color: #0F6E56; display: flex; align-items: center; gap: 5px; }
.ai-label::before { content: '✦'; font-size: 11px; color: #1D9E75; }
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.chip { padding: 5px 12px; border-radius: 20px; font-size: 12px; cursor: pointer; border: 1px solid #9FE1CB; background: white; color: #1a1a1a; transition: all 0.1s; }
.chip.on { background: #1D9E75; color: white; border-color: #1D9E75; }
.chip-label { font-size: 11px; color: #777; margin-bottom: 5px; margin-top: 6px; }
.designer-block { background: #f8fdfb; border: 1px solid #c8e6de; border-radius: 8px; padding: 12px 14px; margin-top: 10px; }
.designer-name { font-weight: 600; font-size: 14px; margin-bottom: 4px; }
.designer-text { font-size: 13px; color: #555; line-height: 1.6; }
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; margin-bottom: 1.25rem; }
.text-panel { background: white; border: 1px solid #e8e5de; border-radius: 12px; padding: 1.25rem; }
.text-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.text-title { font-size: 13px; font-weight: 600; }
.lang { font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 8px; }
.lang-se { background: #E6F1FB; color: #0C447C; }
.lang-en { background: #FFF4E6; color: #7A4500; }
.text-area-clean { width: 100%; min-height: 240px; border: none; background: transparent; font-size: 13px; line-height: 1.65; font-family: inherit; color: #1a1a1a; resize: vertical; outline: none; }
.pub-options { background: white; border: 1px solid #e8e5de; border-radius: 12px; padding: 1.25rem; margin-bottom: 1.25rem; }
.pub-row { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid #f0ede5; font-size: 14px; }
.pub-row:last-child { border-bottom: none; }
.pub-row input[type=checkbox] { width: 16px; height: 16px; accent-color: #1D9E75; cursor: pointer; }
.pub-note { font-size: 12px; color: #aaa; margin-left: auto; }
.nav { display: flex; justify-content: flex-end; gap: 8px; margin-top: 1.5rem; }
.btn { padding: 10px 18px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit; transition: all 0.15s; }
.btn-ghost { background: white; border: 1px solid #ddd; color: #555; }
.btn-ghost:hover { border-color: #bbb; }
.btn-primary { background: #1D9E75; color: white; border: none; }
.btn-primary:hover { background: #0F6E56; }
.btn-danger { background: white; border: 1px solid #f7c1c1; color: #a32d2d; }
.btn-danger:hover { background: #fff0f0; }
.btn-big { padding: 12px 24px; font-size: 14px; }
.info-box { background: #fffbf0; border: 1px solid #fad787; border-radius: 8px; padding: 11px 14px; font-size: 13px; color: #7A4500; margin-bottom: 1.25rem; line-height: 1.6; }
.loading { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 3rem 2rem; gap: 1rem; }
.spinner { width: 36px; height: 36px; border: 3px solid #c8e6de; border-top-color: #1D9E75; border-radius: 50%; animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.loading-text { font-size: 14px; color: #0F6E56; font-weight: 500; }
.loading-sub { font-size: 12px; color: #888; }
.error-box { background: #fff0f0; border: 1px solid #f7c1c1; border-radius: 8px; padding: 12px 14px; font-size: 13px; color: #a32d2d; margin-bottom: 1.25rem; line-height: 1.6; }
.extra-fields { margin-top: 12px; padding-top: 12px; border-top: 1px solid #f0ede5; }
.extra-label { font-size: 11px; font-weight: 600; color: #1D9E75; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 10px; }
.btn-settings { background: white; border: 1px solid #ddd; color: #555; padding: 7px 14px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit; }
.btn-settings:hover { border-color: #bbb; }
.btn-deploy { background: #0F6E56; color: white; border: none; padding: 7px 14px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit; transition: all 0.15s; }
.btn-deploy:hover { background: #085041; }
.btn-deploy:disabled { background: #9FE1CB; cursor: default; }
.settings-panel { display: none; background: white; border: 1px solid #e8e5de; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; }
.settings-panel.open { display: block; }
.ai-chat { background: #f8fdfb; border: 1px solid #9FE1CB; border-radius: 10px; padding: 1rem 1.25rem; margin-bottom: 1.25rem; }
.ai-chat-input-row { display: flex; gap: 8px; margin-top: 10px; }
.ai-chat-input-row input { flex: 1; }
.ai-chat-input-row button { background: #1D9E75; color: white; border: none; padding: 9px 16px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit; white-space: nowrap; }
.ai-chat-input-row button:hover { background: #0F6E56; }
.ai-chat-input-row button:disabled { background: #9FE1CB; cursor: default; }
.ai-chat-history { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
.ai-chat-msg { font-size: 13px; line-height: 1.5; padding: 7px 10px; border-radius: 8px; max-width: 90%; }
.ai-chat-msg.user { background: #1D9E75; color: white; align-self: flex-end; }
.ai-chat-msg.ai { background: white; border: 1px solid #c8e6de; color: #1a1a1a; align-self: flex-start; }
.lightbox { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.85); z-index:1000; align-items:center; justify-content:center; padding:1rem; }
.lightbox.open { display:flex; }
.lightbox img { max-width:100%; max-height:90vh; border-radius:8px; object-fit:contain; box-shadow:0 8px 40px rgba(0,0,0,0.5); }
.lightbox-close { position:fixed; top:16px; right:20px; color:white; font-size:28px; cursor:pointer; line-height:1; background:rgba(0,0,0,0.4); width:36px; height:36px; border-radius:50%; display:flex; align-items:center; justify-content:center; }
.lightbox-prev, .lightbox-next { position:fixed; top:50%; transform:translateY(-50%); color:white; font-size:22px; cursor:pointer; background:rgba(0,0,0,0.4); width:40px; height:40px; border-radius:50%; display:flex; align-items:center; justify-content:center; user-select:none; }
.lightbox-prev { left:12px; }
.lightbox-next { right:12px; }
.lightbox-counter { position:fixed; bottom:20px; left:50%; transform:translateX(-50%); color:white; font-size:13px; background:rgba(0,0,0,0.4); padding:4px 12px; border-radius:20px; }
.thumb { cursor:zoom-in; }
@media (max-width: 600px) {
.two-col { grid-template-columns: 1fr; }
.field-grid { grid-template-columns: 1fr 1fr; }
.thumb-grid { grid-template-columns: repeat(4, 1fr); }
.inv-grid { grid-template-columns: repeat(2, 1fr); }
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
</head>
<body>
<div class="topbar">
<div class="logo">Thorgrens<span>Grenar</span> Hub</div>
<div class="topbar-right">
<span id="api-status" style="font-size:11px;color:#888;">● Lokalt</span>
<span class="lager-count" id="lager-count">0 varor</span>
<button class="btn-settings" onclick="toggleSettings()">⚙ Inställningar</button>
<button class="btn-deploy" id="deploy-btn" onclick="deployToNetlify()" title="Publicera senaste versionen live">↑ Publicera uppdatering</button>
<button class="btn-new" onclick="nyVara()">+ Ny vara</button>
</div>
</div>
<div class="hub">
<!-- INSTÄLLNINGAR -->
<div class="settings-panel" id="settings-panel">
<div class="card-title" style="margin-bottom:1rem;">Inställningar</div>
<div class="field" style="margin-bottom:1rem;">
<label style="font-size:13px;font-weight:600;color:#333;">Stilguide för annonsstexter</label>
<div style="font-size:12px;color:#888;margin-bottom:6px;margin-top:2px;">Skriv hur ni vill att era texter ska låta. AI:n följer dessa instruktioner varje gång den genererar text.</div>
<textarea id="stilguide" style="min-height:100px;" placeholder="Exempel: Använd inte tecknet |. Börja titeln med märket. Håll texten kortfattad och saklig. Inga utropstecken. Skriv priser utan decimaler."></textarea>
</div>
<div style="display:flex;gap:8px;justify-content:flex-end;">
<button class="btn btn-ghost" onclick="toggleSettings()">Stäng</button>
<button class="btn btn-primary" onclick="saveSettings()">Spara</button>
</div>
</div>
<!-- LAGERLISTA -->
<div class="inventory active" id="view-inventory">
<div class="inv-filters">
<input type="text" placeholder="Sök på titel, märke..." id="search-input" oninput="renderInventory()">
<button class="filter-btn on" data-filter="alla" onclick="setFilter(this)">Alla</button>
<button class="filter-btn" data-filter="aktiv" onclick="setFilter(this)">Aktiva</button>
<button class="filter-btn" data-filter="såld" onclick="setFilter(this)">Sålda</button>
<button class="filter-btn" data-filter="utkast" onclick="setFilter(this)">Utkast</button>
</div>
<div class="inv-grid" id="inv-grid"></div>
<div class="empty-state" id="empty-state">
<p>Inga varor i lagret än</p>
<span>Klicka på "+ Ny vara" för att lägga till din första vara</span>
</div>
</div>
<!-- EDITOR -->
<div class="editor" id="view-editor">
<div class="editor-header">
<button class="back-btn" onclick="goInventory()">← Lagret</button>
<div class="editor-title" id="editor-title">Ny vara</div>
</div>
<div class="step-bar">
<div class="step active" id="s1"><span class="step-num">1</span>Bilder & mått</div>
<div class="step" id="s2"><span class="step-num">2</span>AI-identifiering</div>
<div class="step" id="s3"><span class="step-num">3</span>Texter</div>
<div class="step" id="s4"><span class="step-num">4</span>Publicera</div>
</div>
<!-- STEG 1 -->
<div class="panel active" id="p1">
<div class="card">
<div class="card-title">Bilder</div>
<input type="file" id="fileInput" multiple accept="image/*" style="display:none" onchange="handleFiles(this.files)">
<div class="upload-zone" onclick="document.getElementById('fileInput').click()" id="dropzone">
<div class="upload-icon">
<svg width="22" height="22" fill="none" stroke="#0F6E56" stroke-width="1.8" viewBox="0 0 24 24">
<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>
<div class="upload-label">Ladda upp bilder</div>
<div class="upload-sub">Klicka eller dra & släpp — JPG/PNG</div>
</div>
<div class="thumb-grid" id="thumbs"></div>
</div>
<div class="card">
<div class="card-title">Mått & vikt <span style="font-size:11px;color:#aaa;font-weight:400;text-transform:none;">(valfritt — AI fyller i om känt)</span></div>
<div class="field-grid">
<div class="field"><label>Bredd (cm)</label><input type="number" id="f-bredd" placeholder="—"></div>
<div class="field"><label>Höjd (cm)</label><input type="number" id="f-hojd" placeholder="—"></div>
<div class="field"><label>Djup (cm)</label><input type="number" id="f-djup" placeholder="—"></div>
<div class="field"><label>Vikt (gram)</label><input type="number" id="f-vikt" placeholder="—"></div>
</div>
</div>
<div class="nav">
<button class="btn btn-primary btn-big" onclick="startAI()">Identifiera med AI →</button>
</div>
</div>
<!-- STEG 2 -->
<div class="panel" id="p2">
<div id="ai-loading" class="card loading">
<div class="spinner"></div>
<div class="loading-text">AI analyserar bilderna...</div>
<div class="loading-sub">Tar 5–15 sekunder</div>
</div>
<div id="ai-result" style="display:none;">
<div class="ai-box">
<div class="ai-label" style="margin-bottom:10px;">AI-förslag — klicka för att markera/avmarkera</div>
<div class="chip-label">Märke / Tillverkare</div>
<div class="chips" id="chips-marke"></div>
<div class="chip-label">Typ av vara</div>
<div class="chips" id="chips-typ"></div>
<div class="chip-label">Tidsepok</div>
<div class="chips" id="chips-epok"></div>
</div>
<div class="ai-chat">
<div style="font-size:12px;font-weight:600;color:#0F6E56;margin-bottom:6px;">✦ Korrigera AI-förslaget</div>
<div style="font-size:12px;color:#555;margin-bottom:8px;">Skriv om något är fel — t.ex. "detta är en bok", "märket är Rörstrand" eller "epoken är 1950-tal".</div>
<div class="ai-chat-history" id="ai-chat-history"></div>
<div class="ai-chat-input-row">
<input type="text" id="ai-chat-input" placeholder="Skriv en korrigering..." onkeydown="if(event.key==='Enter')sendAiChat()">
<button id="ai-chat-btn" onclick="sendAiChat()">Uppdatera</button>
</div>
</div>
<div class="card">
<div class="card-title">Varuinfo</div>
<div class="field" style="margin-bottom:12px;">
<label>Titel <span style="font-size:11px;color:#1D9E75;font-weight:600;">✦ AI-förslag</span></label>
<input type="text" id="f-titel">
</div>
<div class="field-grid-2" style="margin-bottom:12px;">
<div class="field"><label>Kategori</label><input type="text" id="f-kategori"></div>
<div class="field"><label>Pris (SEK)</label><input type="number" id="f-pris" placeholder="0"></div>
</div>
<div class="field" style="margin-bottom:12px;">
<label>Beskriv skicket med egna ord</label>
<textarea id="f-skick" placeholder="t.ex. Mycket gott skick. Inga sprickor eller flagningar..."></textarea>
</div>
<!-- DYNAMISKA EXTRAFÄLT -->
<div class="extra-fields" id="extra-fields" style="display:none;">
<div class="extra-label" id="extra-label">Extra info</div>
<div id="extra-content"></div>
</div>
</div>
<div class="card">
<div class="card-title">Designerbiblioteket <span style="font-size:11px;color:#1D9E75;font-weight:600;">✦ AI-info — redigera gärna</span></div>
<div class="designer-section" id="ds-marke-section">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
<div style="font-size:12px;font-weight:600;color:#333;">Märke / Tillverkare</div>
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:#1D9E75;font-weight:600;">
<input type="checkbox" id="ds-marke-on" checked style="width:14px;height:14px;accent-color:#1D9E75;"> Inkludera i text
</label>
</div>
<div id="ds-marke-fields">
<div class="field" style="margin-bottom:8px;">
<label>Namn</label>
<input type="text" id="d-namn" placeholder="t.ex. Orrefors">
</div>
<div class="field">
<label>Beskrivning</label>
<textarea id="d-text" style="min-height:60px;" placeholder="Kort info om märket..."></textarea>
</div>
</div>
</div>
<div style="border-top:1px solid #f0ede5;margin:14px 0;"></div>
<div class="designer-section" id="ds-designer-section">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
<div style="font-size:12px;font-weight:600;color:#333;">Designer / Formgivare</div>
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:#1D9E75;font-weight:600;">
<input type="checkbox" id="ds-designer-on" checked style="width:14px;height:14px;accent-color:#1D9E75;"> Inkludera i text
</label>
</div>
<div id="ds-designer-fields">
<div class="field" style="margin-bottom:8px;">
<label>Namn</label>
<input type="text" id="d-designer-namn" placeholder="t.ex. Lars Hellsten">
</div>
<div class="field">
<label>Beskrivning</label>
<textarea id="d-designer-text" style="min-height:60px;" placeholder="Kort info om designern..."></textarea>
</div>
</div>
</div>
<div style="margin-top:10px;font-size:12px;color:#aaa;">Sparas i biblioteket för nästa gång ni listar samma märke eller designer.</div>
</div>
<div class="card" style="padding:1rem 1.25rem;">
<div style="display:flex;align-items:center;justify-content:space-between;gap:1rem;">
<div>
<div style="font-size:13px;font-weight:600;">Publicera på Etsy?</div>
<div style="font-size:12px;color:#888;margin-top:3px;">Avmarkera om varan inte passar den internationella marknaden — ingen engelsk text genereras då.</div>
</div>
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;white-space:nowrap;font-size:13px;font-weight:600;color:#1D9E75;flex-shrink:0;">
<input type="checkbox" id="etsy-toggle" checked style="width:16px;height:16px;accent-color:#1D9E75;"> Etsy på
</label>
</div>
</div>
<div class="nav">
<button class="btn btn-ghost" onclick="aiDone=false;nyVara()">← Ny vara</button>
<button class="btn btn-primary btn-big" onclick="generateTexter()">Generera texter →</button>
</div>
</div>
<div id="ai-error" style="display:none;">
<div class="error-box" id="ai-error-msg"></div>
<div class="nav"><button class="btn btn-ghost" onclick="go(1)">← Tillbaka</button></div>
</div>
</div>
<!-- STEG 3 -->
<div class="panel" id="p3">
<div id="text-loading" class="card loading">
<div class="spinner"></div>
<div class="loading-text">Skriver annonsstexter...</div>
<div class="loading-sub">Svenska + engelska</div>
</div>
<div id="text-result" style="display:none;">
<div class="two-col" id="text-panels">
<div class="text-panel">
<div class="text-header">
<div class="text-title">Tradera & Vinted</div>
<span class="lang lang-se">SV</span>
</div>
<textarea class="text-area-clean" id="text-sv"></textarea>
</div>
<div class="text-panel" id="etsy-panel">
<div class="text-header">
<div class="text-title">Etsy (internationell)</div>
<span class="lang lang-en">EN</span>
</div>
<textarea class="text-area-clean" id="text-en"></textarea>
</div>
</div>
<div class="nav">
<button class="btn btn-ghost" onclick="go(2)">← Tillbaka</button>
<button class="btn btn-primary btn-big" onclick="go(4)">Gå till publicering →</button>
</div>
</div>
<div id="text-error" style="display:none;">
<div class="error-box" id="text-error-msg"></div>
<div class="nav"><button class="btn btn-ghost" onclick="go(2)">← Tillbaka</button></div>
</div>
</div>
<!-- STEG 4 -->
<div class="panel" id="p4">
<div class="pub-options">
<div class="card-title" style="margin-bottom:.75rem;">Välj plattformar</div>
<div class="pub-row">
<input type="checkbox" id="cb1" checked>
<label for="cb1" style="cursor:pointer;font-weight:500;">Tradera</label>
<span class="pub-note">Via Sello — svenska texten</span>
</div>
<div class="pub-row">
<input type="checkbox" id="cb2" checked>
<label for="cb2" style="cursor:pointer;font-weight:500;">Etsy</label>
<span class="pub-note">Engelska texten</span>
</div>
<div class="pub-row">
<input type="checkbox" id="cb3" checked>
<label for="cb3" style="cursor:pointer;font-weight:500;">Vinted</label>
<span class="pub-note">Svenska texten</span>
</div>
</div>
<div class="info-box">
<strong>När varan säljs:</strong> Markera den som såld — tas bort från alla plattformar automatiskt.
</div>
<div class="nav">
<button class="btn btn-ghost" onclick="go(3)">← Tillbaka</button>
<button class="btn btn-danger" onclick="markSold()">Markera som såld</button>
<button class="btn btn-ghost btn-big" onclick="sparaVara('utkast')">Spara utkast</button>
<button class="btn btn-primary btn-big" onclick="sparaVara('aktiv')">Spara & publicera ↗</button>
</div>
</div>
</div>
</div>
<!-- LIGHTBOX -->
<div class="lightbox" id="lightbox" onclick="closeLightbox(event)">
<span class="lightbox-close" onclick="closeLightbox()">×</span>
<span class="lightbox-prev" onclick="lightboxNav(-1);event.stopPropagation()">‹</span>
<img id="lightbox-img" src="" alt="">
<span class="lightbox-next" onclick="lightboxNav(1);event.stopPropagation()">›</span>
<div class="lightbox-counter" id="lightbox-counter"></div>
</div>
<script>
// ── STATE ──────────────────────────────────────────
const API_URL = 'https://script.google.com/macros/s/AKfycbw2JfLKZ5VX1sqRP6s6PkUTYp1JP-qq1D9pME4vzDH7bBj-fF8LSZy6zJibCRJDqvPLtA/exec';
let inventory = JSON.parse(localStorage.getItem('tg-inventory') || '[]');
let apiConnected = false;
let currentId = null;
let cur = 1;
let uploadedImages = [];
let aiDone = false;
let textDone = false;
let currentFilter = 'alla';
// ── LAGERLISTA ──────────────────────────────────────
function saveInventory() {
localStorage.setItem('tg-inventory', JSON.stringify(inventory));
document.getElementById('lager-count').textContent = inventory.length + ' varor';
}
async function syncToSheets(vara) {
try {
// Convert local image dataUrls to send to backend
const varaToSend = {
...vara,
markeNamn: document.getElementById('d-namn')?.value || vara.markeNamn || '',
markeBeskr: document.getElementById('d-text')?.value || vara.markeBeskr || '',
designerNamn: document.getElementById('d-designer-namn')?.value || vara.designerNamn || '',
designerBeskr: document.getElementById('d-designer-text')?.value || vara.designerBeskr || '',
};
const resp = await fetch(API_URL, {
method: 'POST',
body: JSON.stringify({ action: 'spara', vara: varaToSend })
});
const result = await resp.json();
if (result.success && result.bildUrls && result.bildUrls.length > 0) {
// Replace local dataUrls with Drive URLs
const idx = inventory.findIndex(v => v.id === vara.id);
if (idx >= 0) {
inventory[idx].images = result.bildUrls;
localStorage.setItem('tg-inventory', JSON.stringify(inventory));
}
}
return result;
} catch(err) {
console.warn('Sheets sync failed:', err.message);
return null;
}
}
async function loadFromSheets() {
try {
const resp = await fetch(API_URL + '?action=hamta');
const result = await resp.json();
if (result.varor && result.varor.length > 0) {
inventory = result.varor;
localStorage.setItem('tg-inventory', JSON.stringify(inventory));
apiConnected = true;
document.getElementById('api-status').textContent = '● Sheets';
document.getElementById('api-status').style.color = '#1D9E75';
renderInventory();
}
} catch(err) {
document.getElementById('api-status').textContent = '● Lokalt';
document.getElementById('api-status').style.color = '#888';
}
}
function renderInventory() {
const q = document.getElementById('search-input').value.toLowerCase();
const grid = document.getElementById('inv-grid');
const empty = document.getElementById('empty-state');
let items = inventory.filter(v => {
if (currentFilter !== 'alla' && v.status !== currentFilter) return false;
if (q && !v.titel.toLowerCase().includes(q) && !(v.marke||'').toLowerCase().includes(q)) return false;
return true;
});
grid.innerHTML = '';
if (!items.length) { empty.style.display = 'block'; grid.style.display = 'none'; return; }
empty.style.display = 'none'; grid.style.display = 'grid';
items.forEach(vara => {
const card = document.createElement('div');
card.className = 'inv-card';
const thumb = vara.images && vara.images[0]
? `<img src="${vara.images[0]}" alt="">`
: `<div class="inv-thumb-placeholder">📦</div>`;
const statusLabel = { aktiv: 'Aktiv', såld: 'Såld', utkast: 'Utkast' }[vara.status] || 'Utkast';
const statusClass = { aktiv: 'status-active', såld: 'status-sold', utkast: 'status-draft' }[vara.status] || 'status-draft';
card.innerHTML = `
<div class="inv-thumb">${thumb}</div>
<div class="inv-body">
<div class="inv-title">${vara.titel || 'Utan titel'}</div>
<div class="inv-meta">${vara.marke || '—'} · ${vara.epok || '—'}</div>
<div class="inv-price">${vara.pris ? vara.pris + ' kr' : '—'}</div>
<span class="status-badge ${statusClass}">${statusLabel}</span>
</div>`;
card.onclick = () => openVara(vara.id);
grid.appendChild(card);
});
document.getElementById('lager-count').textContent = inventory.length + ' varor';
}
function setFilter(btn) {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('on'));
btn.classList.add('on');
currentFilter = btn.dataset.filter;
renderInventory();
}
function goInventory() {
document.getElementById('view-editor').classList.remove('active');
document.getElementById('view-inventory').classList.add('active');
renderInventory();
}
function openVara(id) {
currentId = id;
const vara = inventory.find(v => v.id === id);
if (!vara) return;
resetEditor();
document.getElementById('editor-title').textContent = vara.titel || 'Redigera vara';
// Restore images
uploadedImages = (vara.images || []).map(url => ({ dataUrl: url, base64: url.split(',')[1], mediaType: 'image/jpeg' }));
const g = document.getElementById('thumbs');
uploadedImages.forEach((img, idx) => {
const d = document.createElement('div');
d.className = 'thumb';
d.innerHTML = `<img src="${img.dataUrl}" style="width:100%;height:100%;object-fit:cover;border-radius:7px;" onclick="openLightbox(${idx})"><span class="del" onclick="event.stopPropagation();uploadedImages[${idx}]=null;this.parentNode.remove()">×</span>`;
g.appendChild(d);
});
// Restore fields
if (vara.bredd) document.getElementById('f-bredd').value = vara.bredd;
if (vara.hojd) document.getElementById('f-hojd').value = vara.hojd;
if (vara.djup) document.getElementById('f-djup').value = vara.djup;
if (vara.vikt) document.getElementById('f-vikt').value = vara.vikt;
if (vara.aiData) {
renderChips('chips-marke', vara.aiData.marke);
renderChips('chips-typ', vara.aiData.typ);
renderChips('chips-epok', vara.aiData.epok);
document.getElementById('f-titel').value = vara.titel || '';
document.getElementById('f-kategori').value = vara.kategori || '';
document.getElementById('f-pris').value = vara.pris || '';
document.getElementById('f-skick').value = vara.skick || '';
document.getElementById('d-namn').value = vara.aiData.marke_namn || vara.aiData.designer_namn || '';
document.getElementById('d-text').value = vara.aiData.marke_info || vara.aiData.designer_info || '';
document.getElementById('d-designer-namn').value = vara.aiData.designer_extern_namn || '';
document.getElementById('d-designer-text').value = vara.aiData.designer_extern_info || '';
if (vara.dsMarkeOn !== undefined) { document.getElementById('ds-marke-on').checked = vara.dsMarkeOn; toggleDesignerSection('marke', vara.dsMarkeOn); }
if (vara.dsDesignerOn !== undefined) { document.getElementById('ds-designer-on').checked = vara.dsDesignerOn; toggleDesignerSection('designer', vara.dsDesignerOn); }
renderExtraFields(vara.aiData.varutyp || '', vara.extraData || {});
window._lastAiData = vara.aiData;
document.getElementById('ai-loading').style.display = 'none';
document.getElementById('ai-result').style.display = 'block';
document.getElementById('ai-error').style.display = 'none';
aiDone = true;
}
if (vara.textSv) {
document.getElementById('text-sv').value = vara.textSv;
document.getElementById('text-loading').style.display = 'none';
document.getElementById('text-result').style.display = 'block';
document.getElementById('text-error').style.display = 'none';
textDone = true;
}
if (vara.textEn) { document.getElementById('text-en').value = vara.textEn; }
showEditor();
}
function nyVara() {
currentId = null;
resetEditor();
document.getElementById('editor-title').textContent = 'Ny vara';
showEditor();
}
function showEditor() {
document.getElementById('view-inventory').classList.remove('active');
document.getElementById('view-editor').classList.add('active');
window.scrollTo({top:0});
}
function resetEditor() {
uploadedImages = [];
aiDone = false;
textDone = false;
cur = 1;
document.getElementById('thumbs').innerHTML = '';
document.getElementById('fileInput').value = '';
['f-bredd','f-hojd','f-djup','f-vikt','f-titel','f-kategori','f-pris','f-skick'].forEach(id => {
const el = document.getElementById(id);
if (el) el.value = '';
});
['chips-marke','chips-typ','chips-epok'].forEach(id => document.getElementById(id).innerHTML = '');
document.getElementById('d-namn').textContent = '';
document.getElementById('d-text').textContent = '';
document.getElementById('text-sv').value = '';
document.getElementById('text-en').value = '';
document.getElementById('extra-fields').style.display = 'none';
document.getElementById('extra-content').innerHTML = '';
document.getElementById('d-designer-namn').value = '';
document.getElementById('d-designer-text').value = '';
document.getElementById('ds-marke-on').checked = true;
document.getElementById('ds-designer-on').checked = true;
toggleDesignerSection('marke', true);
toggleDesignerSection('designer', true);
document.getElementById('ai-chat-history').innerHTML = '';
document.getElementById('ai-chat-input').value = '';
aiChatHistory = [];
document.getElementById('ai-loading').style.display = 'flex';
document.getElementById('ai-result').style.display = 'none';
document.getElementById('ai-error').style.display = 'none';
document.getElementById('text-loading').style.display = 'flex';
document.getElementById('text-result').style.display = 'none';
document.getElementById('text-error').style.display = 'none';
[1,2,3,4].forEach(n => {
document.getElementById('s'+n).classList.remove('active','done');
});
document.getElementById('p1').classList.add('active');
document.getElementById('s1').classList.add('active');
[2,3,4].forEach(n => document.getElementById('p'+n).classList.remove('active'));
}
// ── NAVIGATION ──────────────────────────────────────
function go(n) {
document.getElementById('s'+cur).classList.remove('active');
if (n > cur) document.getElementById('s'+cur).classList.add('done');
else document.getElementById('s'+cur).classList.remove('done');
document.getElementById('p'+cur).classList.remove('active');
cur = n;
document.getElementById('s'+cur).classList.add('active');
document.getElementById('s'+cur).classList.remove('done');
document.getElementById('p'+cur).classList.add('active');
window.scrollTo({top:0, behavior:'smooth'});
}
function navTo(n) {
if (n === 1) { go(1); return; }
if (n === 2) { if (!uploadedImages.filter(Boolean).length) { alert('Ladda upp bilder först!'); return; } startAI(); return; }
if (n === 3) { if (!aiDone) { alert('Kör AI-identifieringen först (steg 2).'); return; } generateTexter(); return; }
if (n === 4) { if (!textDone) { alert('Generera texter först (steg 3).'); return; } go(4); return; }
}
// ── BILDER ──────────────────────────────────────────
function compressImage(dataUrl, cb) {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const max = 1600;
let w = img.width, h = img.height;
if (w > max || h > max) {
if (w > h) { h = Math.round(h*max/w); w = max; }
else { w = Math.round(w*max/h); h = max; }
}
canvas.width = w; canvas.height = h;
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
cb(canvas.toDataURL('image/jpeg', 0.82));
};
img.src = dataUrl;
}
function handleFiles(files) {
const g = document.getElementById('thumbs');
Array.from(files).forEach(file => {
if (!file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = e => {
compressImage(e.target.result, compressed => {
const idx = uploadedImages.length;
uploadedImages.push({ dataUrl: compressed, base64: compressed.split(',')[1], mediaType: 'image/jpeg' });
const d = document.createElement('div');
d.className = 'thumb';
d.draggable = true;
d.dataset.idx = idx;
d.innerHTML = `<img src="${compressed}" style="width:100%;height:100%;object-fit:cover;border-radius:7px;" onclick="openLightbox(${idx})"><span class="del" onclick="event.stopPropagation();uploadedImages[${idx}]=null;this.parentNode.remove();updateThumbOrder()">×</span><span class="order-num"></span>`;
addThumbDragEvents(d);
g.appendChild(d);
updateThumbOrder();
});
};
reader.readAsDataURL(file);
});
}
const dz = document.getElementById('dropzone');
dz.addEventListener('dragover', e => { e.preventDefault(); dz.style.borderColor='#1D9E75'; });
dz.addEventListener('dragleave', () => { dz.style.borderColor=''; });
dz.addEventListener('drop', e => { e.preventDefault(); dz.style.borderColor=''; handleFiles(e.dataTransfer.files); });
// ── DYNAMISKA EXTRAFÄLT ─────────────────────────────
function renderExtraFields(varutyp, existing) {
const container = document.getElementById('extra-content');
const wrapper = document.getElementById('extra-fields');
const label = document.getElementById('extra-label');
container.innerHTML = '';
const typ = (varutyp || '').toLowerCase();
let fields = [];
if (typ.includes('klä') || typ.includes('jacka') || typ.includes('byxor') || typ.includes('kjol') || typ.includes('tröja') || typ.includes('skjorta') || typ.includes('kappa') || typ.includes('plagg')) {
label.textContent = 'Klädinformation';
fields = [
{ id: 'f-storlek', label: 'Storlek', type: 'text', placeholder: 'M / 38 / EU 40' },
{ id: 'f-material', label: 'Material', type: 'text', placeholder: 'Ull, Bomull...' },
{ id: 'f-farg', label: 'Färg', type: 'text', placeholder: 'Mörkblå, randig...' },
];
} else if (typ.includes('bok') || typ.includes('roman') || typ.includes('tidskrift') || typ.includes('album') || typ.includes('magazine')) {
label.textContent = 'Bokinformation';
fields = [
{ id: 'f-isbn', label: 'ISBN', type: 'text', placeholder: '978-...' },
{ id: 'f-forfattare', label: 'Författare', type: 'text', placeholder: 'För- och efternamn' },
{ id: 'f-forlag', label: 'Förlag', type: 'text', placeholder: 'Bonniers, Norstedts...' },
{ id: 'f-utgivningsar', label: 'Utgivningsår', type: 'number', placeholder: '1985' },
{ id: 'f-sprak', label: 'Språk', type: 'text', placeholder: 'Svenska' },
];
} else if (typ.includes('dvd') || typ.includes('blu') || typ.includes('vhs') || typ.includes('film') || typ.includes('serie') || typ.includes('skiva') || typ.includes('vinyl') || typ.includes('cd') || typ.includes('kassett')) {
label.textContent = 'Mediainformation';
fields = [
{ id: 'f-format', label: 'Format', type: 'select', options: ['DVD', 'Blu-ray', 'VHS', 'CD', 'Vinyl LP', 'Vinyl singel', 'Kassettband'] },
{ id: 'f-ar', label: 'År', type: 'number', placeholder: '1998' },
{ id: 'f-region', label: 'Region (film)', type: 'text', placeholder: 'Region 2 / PAL' },
];
} else if (typ.includes('sko') || typ.includes('stövel') || typ.includes('sandal') || typ.includes('sneaker')) {
label.textContent = 'Skoinformation';
fields = [
{ id: 'f-storlek', label: 'Storlek (EU)', type: 'text', placeholder: '42' },
{ id: 'f-farg', label: 'Färg', type: 'text', placeholder: 'Svart' },
{ id: 'f-material', label: 'Material', type: 'text', placeholder: 'Läder, Textil...' },
];
}
if (!fields.length) { wrapper.style.display = 'none'; return; }
wrapper.style.display = 'block';
const grid = document.createElement('div');
grid.className = 'field-grid-3';
fields.forEach(f => {
const div = document.createElement('div');
div.className = 'field';
if (f.type === 'select') {
div.innerHTML = `<label>${f.label}</label><select id="${f.id}">${f.options.map(o => `<option${existing[f.id]===o?' selected':''}>${o}</option>`).join('')}</select>`;
} else {
div.innerHTML = `<label>${f.label}</label><input type="${f.type}" id="${f.id}" placeholder="${f.placeholder||''}" value="${existing[f.id]||''}">`;
}
grid.appendChild(div);
});
container.appendChild(grid);
}
function getExtraData() {
const data = {};
['f-storlek','f-material','f-farg','f-isbn','f-forfattare','f-forlag','f-utgivningsar','f-sprak','f-format','f-ar','f-region'].forEach(id => {
const el = document.getElementById(id);
if (el) data[id] = el.value;
});
return data;
}
// ── AI ──────────────────────────────────────────────
async function callClaude(messages, maxTokens) {
const resp = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"anthropic-version": "2023-06-01",
"anthropic-dangerous-allow-browser": "true"
},
body: JSON.stringify({ model: "claude-sonnet-4-20250514", max_tokens: maxTokens, messages })
});
const data = await resp.json();
if (!resp.ok) throw new Error(data.error?.message || `HTTP ${resp.status}`);
return data.content.map(b => b.text || '').join('').trim();
}
function parseJSON(text) {
return JSON.parse(text.replace(/```json|```/g, '').trim());
}
function renderChips(id, items) {
const c = document.getElementById(id);
c.innerHTML = '';
(items || []).forEach((item, i) => {
const s = document.createElement('span');
s.className = 'chip' + (i === 0 ? ' on' : '');
s.textContent = item;
s.onclick = () => { s.classList.toggle('on'); updateTitelFromChips(); };
c.appendChild(s);
});
}
function updateTitelFromChips() {
const marke = [...document.querySelectorAll('#chips-marke .chip.on')].map(c=>c.textContent)[0] || '';
const typ = [...document.querySelectorAll('#chips-typ .chip.on')].map(c=>c.textContent)[0] || '';
const epok = [...document.querySelectorAll('#chips-epok .chip.on')].map(c=>c.textContent)[0] || '';
const parts = [marke, typ, epok].filter(Boolean);
if (parts.length) document.getElementById('f-titel').value = parts.join(' ');
// Also update designer name field if marke changed
const dNamn = document.getElementById('d-namn');
if (marke && (!dNamn.value || dNamn.dataset.lastAi === dNamn.value)) {
// only auto-update if user hasn't manually edited it
}
textDone = false; // title changed, texts need regeneration
}
async function startAI() {
const imgs = uploadedImages.filter(Boolean);
if (!imgs.length) { alert('Ladda upp minst en bild först!'); return; }
go(2);
if (aiDone) {
document.getElementById('ai-loading').style.display = 'none';
document.getElementById('ai-result').style.display = 'block';
return;
}
document.getElementById('ai-loading').style.display = 'flex';
document.getElementById('ai-result').style.display = 'none';
document.getElementById('ai-error').style.display = 'none';
const bredd = document.getElementById('f-bredd').value;
const hojd = document.getElementById('f-hojd').value;
const djup = document.getElementById('f-djup').value;
const vikt = document.getElementById('f-vikt').value;
const matsInfo = [bredd&&`Bredd:${bredd}cm`, hojd&&`Höjd:${hojd}cm`, djup&&`Djup:${djup}cm`, vikt&&`Vikt:${vikt}g`].filter(Boolean).join(', ');
const imageBlocks = imgs.slice(0,4).map(img => ({
type: "image", source: { type: "base64", media_type: img.mediaType, data: img.base64 }
}));
try {
const raw = await callClaude([{ role: "user", content: [
...imageBlocks,
{ type: "text", text: `Du är expert på vintage och second hand: skandinavisk design, porslin, keramik, glas, textil, kläder, böcker, film/media, antikviteter.
Returnera BARA JSON, inga backticks:
{
"marke": ["Mest troligt märke","Alt 2","Alt 3"],
"typ": ["Varutyp 1","Alt 2","Alt 3"],
"epok": ["1970-tal","Alt"],
"titel": "Märke | Varutyp | Epok",
"kategori": "Hem & Hushåll",
"varutyp": "EN av: generell|kläder|bok|media|skor",
"marke_namn": "Tillverkarens/märkets namn (t.ex. Orrefors)",
"marke_info": "2-3 meningar om märket/tillverkaren på svenska.",
"designer_namn": "Formgivarens/designerns namn om känt, annars tom sträng",
"designer_info": "2-3 meningar om designern på svenska om känt, annars tom sträng."
}
varutyp-regler: "kläder" om det är ett klädesplagg, "bok" om det är en bok/tidskrift, "media" om det är dvd/blu-ray/cd/vinyl/vhs, "skor" om det är skodon, annars "generell".
Kategori: Hem & Hushåll, Antikt & Design, Kök, Textil & Kläder, Glas, Keramik, Böcker, Media, Konst, Samlarsaker, Övrigt.
${matsInfo ? `Mått: ${matsInfo}` : ''}` }
]}], 1000);
const json = parseJSON(raw);
renderChips('chips-marke', json.marke);
renderChips('chips-typ', json.typ);
renderChips('chips-epok', json.epok);
document.getElementById('f-titel').value = json.titel || '';
document.getElementById('f-kategori').value = json.kategori || '';
document.getElementById('d-namn').value = json.marke_namn || json.designer_namn || '';
document.getElementById('d-text').value = json.marke_info || json.designer_info || '';
document.getElementById('d-designer-namn').value = json.designer_namn && json.marke_namn ? json.designer_namn : '';
document.getElementById('d-designer-text').value = json.designer_namn && json.marke_namn ? (json.designer_info || '') : '';
const hasDesigner = !!(json.designer_namn && json.marke_namn);
document.getElementById('ds-designer-on').checked = hasDesigner;
toggleDesignerSection('designer', hasDesigner);
renderExtraFields(json.varutyp || json.typ?.[0] || '', {});
window._lastAiData = json;
aiDone = true;
document.getElementById('ai-loading').style.display = 'none';
document.getElementById('ai-result').style.display = 'block';
} catch(err) {
document.getElementById('ai-loading').style.display = 'none';
document.getElementById('ai-error').style.display = 'block';
document.getElementById('ai-error-msg').textContent = 'Något gick fel: ' + err.message;
}
}
async function generateTexter() {
go(3);
if (textDone) {
document.getElementById('text-loading').style.display = 'none';
document.getElementById('text-result').style.display = 'block';
return;
}
document.getElementById('text-loading').style.display = 'flex';
document.getElementById('text-result').style.display = 'none';
document.getElementById('text-error').style.display = 'none';
const titel = document.getElementById('f-titel').value;
const skick = document.getElementById('f-skick').value;
const kategori = document.getElementById('f-kategori').value;
const markeOn = document.getElementById('ds-marke-on').checked;
const designerOn = document.getElementById('ds-designer-on').checked;
const markeNamn = document.getElementById('d-namn').value;
const markeBeskr = document.getElementById('d-text').value;
const designerNamn = document.getElementById('d-designer-namn').value;
const designerBeskr = document.getElementById('d-designer-text').value;
const dInfo = [
markeOn && markeBeskr ? `Märke: ${markeNamn} — ${markeBeskr}` : '',
designerOn && designerBeskr ? `Designer: ${designerNamn} — ${designerBeskr}` : ''
].filter(Boolean).join('\n');
const wantEtsy = document.getElementById('etsy-toggle').checked;
const stilguide = localStorage.getItem('tg-stilguide') || '';
const marke = [...document.querySelectorAll('#chips-marke .chip.on')].map(c=>c.textContent).join(', ');
const typ = [...document.querySelectorAll('#chips-typ .chip.on')].map(c=>c.textContent).join(', ');
const epok = [...document.querySelectorAll('#chips-epok .chip.on')].map(c=>c.textContent).join(', ');
const b = document.getElementById('f-bredd').value;
const h = document.getElementById('f-hojd').value;
const d = document.getElementById('f-djup').value;
const v = document.getElementById('f-vikt').value;
const mats = [b&&h&&d?`${b}×${h}×${d} cm`:'', v?`Vikt: ${v} g`:''].filter(Boolean).join(' | ');
const extra = getExtraData();
const extraStr = Object.entries(extra).filter(([,v])=>v).map(([k,v])=>`${k.replace('f-','')}: ${v}`).join(', ');
try {
const etsyField = wantEtsy
? `"engelska": "Listing in English for Etsy. Evocative title, description (2-3 sentences), maker info (1-2 sentences), condition, measurements/size. Warm tone for Scandinavian design lovers."`
: `"engelska": ""`;
const raw = await callClaude([{ role: "user", content: `Du skriver annonsstexter för en svensk second hand-butik, ThorgrensGrenar.
${stilguide ? `\nStilinstruktioner (följ alltid dessa):\n${stilguide}` : ''}
Vara: ${titel}
Märke: ${marke} | Typ: ${typ} | Epok: ${epok} | Kategori: ${kategori}
Skick: ${skick || 'ej angivet'}
Mått: ${mats || 'ej angivet'}
${extraStr ? `Extra: ${extraStr}` : ''}
Om märket: ${dInfo}
Returnera BARA JSON, inga backticks:
{
"svenska": "Annonstext på svenska för Tradera och Vinted. Rubrik, beskrivning (2-3 meningar), info om märket (1-2 meningar), skick, mått/storlek. Säljande men ärlig.",
${etsyField}
}` }], 1500);
const json = parseJSON(raw);
textDone = true;
document.getElementById('text-sv').value = json.svenska || '';
document.getElementById('text-en').value = wantEtsy ? (json.engelska || '') : '';
document.getElementById('etsy-panel').style.display = wantEtsy ? '' : 'none';
document.getElementById('text-panels').style.gridTemplateColumns = wantEtsy ? '1fr 1fr' : '1fr';
document.getElementById('text-loading').style.display = 'none';
document.getElementById('text-result').style.display = 'block';
} catch(err) {
document.getElementById('text-loading').style.display = 'none';
document.getElementById('text-error').style.display = 'block';
document.getElementById('text-error-msg').textContent = 'Något gick fel: ' + err.message;
}
}
// ── SPARA / PUBLICERA ───────────────────────────────
function sparaVara(status) {
const vara = {
id: currentId || ('v' + Date.now()),
status: status || 'utkast',
titel: document.getElementById('f-titel').value || 'Utan titel',
kategori: document.getElementById('f-kategori').value,
pris: document.getElementById('f-pris').value,
skick: document.getElementById('f-skick').value,
marke: document.querySelectorAll('#chips-marke .chip.on')[0]?.textContent || '',
epok: document.querySelectorAll('#chips-epok .chip.on')[0]?.textContent || '',
bredd: document.getElementById('f-bredd').value,
hojd: document.getElementById('f-hojd').value,
djup: document.getElementById('f-djup').value,
vikt: document.getElementById('f-vikt').value,
images: uploadedImages.filter(Boolean).map(i => i.dataUrl),
textSv: document.getElementById('text-sv').value,
textEn: document.getElementById('text-en').value,
aiData: window._lastAiData || null,
dsMarkeOn: document.getElementById('ds-marke-on').checked,
dsDesignerOn: document.getElementById('ds-designer-on').checked,
designer_extern_namn: document.getElementById('d-designer-namn').value,
designer_extern_info: document.getElementById('d-designer-text').value,
extraData: getExtraData(),
sparadDatum: new Date().toLocaleDateString('sv-SE'),
};
if (currentId) {
const idx = inventory.findIndex(v => v.id === currentId);
if (idx >= 0) inventory[idx] = vara;
} else {
currentId = vara.id;
inventory.unshift(vara);
}
saveInventory();
document.getElementById('editor-title').textContent = vara.titel;
syncToSheets(vara);
goInventory();
}
function markSold() {
if (!currentId) { alert('Spara varan först.'); return; }
if (!confirm('Markera varan som såld? Den tas bort från alla aktiva plattformar.')) return;
const idx = inventory.findIndex(v => v.id === currentId);
if (idx >= 0) {
inventory[idx].status = 'såld';
saveInventory();
fetch(API_URL, { method: 'POST', body: JSON.stringify({ action: 'sold', id: currentId }) });
}
alert('✓ Markerad som såld!');
goInventory();
}
// ── AI CHAT ─────────────────────────────────────────
let aiChatHistory = [];
function addChatMsg(text, role) {
aiChatHistory.push({ role, text });
const h = document.getElementById('ai-chat-history');
const d = document.createElement('div');
d.className = 'ai-chat-msg ' + (role === 'user' ? 'user' : 'ai');
d.textContent = text;
h.appendChild(d);
h.scrollTop = h.scrollHeight;
}
async function sendAiChat() {
const input = document.getElementById('ai-chat-input');
const btn = document.getElementById('ai-chat-btn');
const msg = input.value.trim();
if (!msg) return;
input.value = '';
addChatMsg(msg, 'user');
btn.disabled = true;
btn.textContent = '...';
const imgs = uploadedImages.filter(Boolean);
const imageBlocks = imgs.slice(0,4).map(img => ({
type: "image", source: { type: "base64", media_type: img.mediaType, data: img.base64 }
}));
const currentMarke = [...document.querySelectorAll('#chips-marke .chip.on')].map(c=>c.textContent);
const currentTyp = [...document.querySelectorAll('#chips-typ .chip.on')].map(c=>c.textContent);
const currentEpok = [...document.querySelectorAll('#chips-epok .chip.on')].map(c=>c.textContent);
const currentTitel = document.getElementById('f-titel').value;
try {
const raw = await callClaude([{ role: "user", content: [
...imageBlocks,
{ type: "text", text: `Du hjälper att korrigera ett AI-förslag för en second hand-vara.
Nuvarande förslag:
- Märke: ${currentMarke.join(', ')}
- Typ: ${currentTyp.join(', ')}
- Epok: ${currentEpok.join(', ')}
- Titel: ${currentTitel}
Tidigare konversation: ${aiChatHistory.slice(0,-1).map(m=>m.role+': '+m.text).join(' | ')}
Användarens korrigering: "${msg}"
Uppdatera förslagen baserat på korrigeringen. Returnera BARA JSON, inga backticks:
{
"marke": ["Korrekt märke","Alt 2"],
"typ": ["Korrekt typ","Alt 2"],
"epok": ["Korrekt epok"],
"titel": "Uppdaterad titel utan |",
"kategori": "Kategori",
"varutyp": "generell|kläder|bok|media|skor",
"marke_namn": "Tillverkarens namn",
"marke_info": "2-3 meningar om märket/tillverkaren på svenska.",
"designer_namn": "Formgivarens namn om känt, annars tom sträng",
"designer_info": "2-3 meningar om designern på svenska om känt, annars tom sträng.",
"svar": "Kort bekräftelse på svenska av vad du ändrade."
}` }
]}], 800);
const json = parseJSON(raw);
renderChips('chips-marke', json.marke);
renderChips('chips-typ', json.typ);
renderChips('chips-epok', json.epok);
if (json.titel) document.getElementById('f-titel').value = json.titel;
if (json.kategori) document.getElementById('f-kategori').value = json.kategori;
if (json.marke_namn) document.getElementById('d-namn').value = json.marke_namn;
else if (json.designer_namn) document.getElementById('d-namn').value = json.designer_namn;
if (json.marke_info) document.getElementById('d-text').value = json.marke_info;
else if (json.designer_info) document.getElementById('d-text').value = json.designer_info;
if (json.designer_namn && json.marke_namn) {
document.getElementById('d-designer-namn').value = json.designer_namn;
document.getElementById('d-designer-text').value = json.designer_info || '';
}
if (json.varutyp) renderExtraFields(json.varutyp, {});
window._lastAiData = { ...window._lastAiData, ...json };
addChatMsg(json.svar || 'Uppdaterat!', 'ai');
textDone = false;
} catch(err) {
addChatMsg('Något gick fel: ' + err.message, 'ai');
}
btn.disabled = false;
btn.textContent = 'Uppdatera';
}
// ── DRAG REORDER THUMBNAILS ─────────────────────────
let dragSrcIdx = null;
function addThumbDragEvents(el) {
el.addEventListener('dragstart', e => {
dragSrcIdx = parseInt(el.dataset.idx);
el.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
});
el.addEventListener('dragend', () => {
el.classList.remove('dragging');
document.querySelectorAll('.thumb').forEach(t => t.classList.remove('drag-over'));
});
el.addEventListener('dragover', e => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
document.querySelectorAll('.thumb').forEach(t => t.classList.remove('drag-over'));
el.classList.add('drag-over');
});
el.addEventListener('drop', e => {
e.preventDefault();
const destIdx = parseInt(el.dataset.idx);
if (dragSrcIdx === null || dragSrcIdx === destIdx) return;
// Swap in uploadedImages array
const tmp = uploadedImages[dragSrcIdx];
uploadedImages[dragSrcIdx] = uploadedImages[destIdx];
uploadedImages[destIdx] = tmp;
// Rebuild thumbnails
rebuildThumbs();
dragSrcIdx = null;
});
}
function rebuildThumbs() {
const g = document.getElementById('thumbs');
g.innerHTML = '';
uploadedImages.forEach((img, idx) => {
if (!img) return;
const d = document.createElement('div');
d.className = 'thumb';
d.draggable = true;
d.dataset.idx = idx;
d.innerHTML = `<img src="${img.dataUrl}" style="width:100%;height:100%;object-fit:cover;border-radius:7px;" onclick="openLightbox(${idx})"><span class="del" onclick="event.stopPropagation();uploadedImages[${idx}]=null;this.parentNode.remove();updateThumbOrder()">×</span><span class="order-num"></span>`;
addThumbDragEvents(d);
g.appendChild(d);
});
updateThumbOrder();
}
function updateThumbOrder() {
const thumbs = document.querySelectorAll('.thumb');
thumbs.forEach((t, i) => {
const num = t.querySelector('.order-num');
if (num) num.textContent = i + 1;
});
}
// ── LIGHTBOX ─────────────────────────────────────────
let lightboxIdx = 0;
function openLightbox(idx) {
lightboxIdx = idx;
showLightboxImage();
document.getElementById('lightbox').classList.add('open');
document.addEventListener('keydown', lightboxKey);
}
function showLightboxImage() {
const imgs = uploadedImages.filter(Boolean);
const validIdx = uploadedImages.slice(0, lightboxIdx + 1).filter(Boolean).length - 1;
const allValid = uploadedImages.filter(Boolean);
const current = uploadedImages[lightboxIdx] || allValid[0];
if (!current) return;
document.getElementById('lightbox-img').src = current.dataUrl;
const pos = allValid.indexOf(current);
document.getElementById('lightbox-counter').textContent = `${pos + 1} / ${allValid.length}`;
}
function lightboxNav(dir) {
const imgs = uploadedImages.map((img, i) => img ? i : null).filter(i => i !== null);
if (!imgs.length) return;
const cur = imgs.indexOf(lightboxIdx);
const next = (cur + dir + imgs.length) % imgs.length;
lightboxIdx = imgs[next];
showLightboxImage();
}
function closeLightbox(e) {
if (e && e.target !== document.getElementById('lightbox') && !e.target.classList.contains('lightbox-close')) return;
document.getElementById('lightbox').classList.remove('open');
document.removeEventListener('keydown', lightboxKey);
}
function lightboxKey(e) {
if (e.key === 'ArrowLeft') lightboxNav(-1);
if (e.key === 'ArrowRight') lightboxNav(1);
if (e.key === 'Escape') { document.getElementById('lightbox').classList.remove('open'); document.removeEventListener('keydown', lightboxKey); }
}
// ── DESIGNER TOGGLES ────────────────────────────────
function toggleDesignerSection(section, on) {
const fields = document.getElementById('ds-' + section + '-fields');
if (fields) fields.style.opacity = on ? '1' : '0.4';
}
document.addEventListener('change', function(e) {
if (e.target.id === 'ds-marke-on') toggleDesignerSection('marke', e.target.checked);
if (e.target.id === 'ds-designer-on') toggleDesignerSection('designer', e.target.checked);
});
// ── INSTÄLLNINGAR ──────────────────────────────────
function toggleSettings() {
const p = document.getElementById('settings-panel');
p.classList.toggle('open');
if (p.classList.contains('open')) {
document.getElementById('stilguide').value = localStorage.getItem('tg-stilguide') || '';
}
}
function saveSettings() {
localStorage.setItem('tg-stilguide', document.getElementById('stilguide').value);
document.getElementById('settings-panel').classList.remove('open');
alert('✓ Stilguide sparad!');
}
// ── AUTO DEPLOY ────────────────────────────────────
const NETLIFY_TOKEN = 'nfp_mWGwgPp3qtpCh4zJu1dC2QsiAFDm2dHif46c';
const NETLIFY_SITE_ID = '90b19acf-7646-41dd-b25e-d36182c49b62';
async function deployToNetlify() {
const btn = document.getElementById('deploy-btn');
btn.disabled = true;
btn.textContent = '↑ Deployer...';
try {
// Fetch the current page HTML
const resp = await fetch(window.location.href);
const html = await resp.text();
// Create a zip with index.html using JSZip
const zip = new JSZip();
zip.file('index.html', html);
const zipBlob = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE' });
// Deploy to Netlify
const deployResp = await fetch(`https://api.netlify.com/api/v1/sites/${NETLIFY_SITE_ID}/deploys`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${NETLIFY_TOKEN}`,
'Content-Type': 'application/zip'
},
body: zipBlob
});
const result = await deployResp.json();
if (result.id) {
btn.textContent = '✓ Live!';
btn.style.background = '#1D9E75';
setTimeout(() => { btn.textContent = '↑ Publicera uppdatering'; btn.disabled = false; btn.style.background = ''; }, 3000);
} else {
throw new Error(result.message || 'Deploy misslyckades');
}
} catch(err) {
btn.textContent = '✗ Fel';
btn.style.background = '#a32d2d';
setTimeout(() => { btn.textContent = '↑ Publicera uppdatering'; btn.disabled = false; btn.style.background = ''; }, 3000);
console.error('Deploy error:', err);
}
}
// ── INIT ────────────────────────────────────────────
[1,2,3,4].forEach(n => {
document.getElementById('s'+n).addEventListener('click', () => navTo(n));
});
loadFromSheets();
renderInventory();
</script>
</body>
</html>