<!DOCTYPE html>
<html lang="en"><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Dadia Ateliers — Curated Imports &amp; Design</title>
<meta name="description" content="One-of-one Anatolian antiques — olive jars, stone basins and architectural salvage — curated and delivered by Dadia Ateliers.">
<meta property="og:title" content="Dadia Ateliers — Curated Imports &amp; Design">
<meta property="og:description" content="One-of-one Anatolian antiques — olive jars, stone basins and architectural salvage — curated and delivered by Dadia Ateliers.">
<meta property="og:type" content="website">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;1,400;1,500&amp;family=Instrument+Sans:wght@400;500;600&amp;display=swap" rel="stylesheet">
<style>
/* ============================================================
   DADIA ATELIERS — DESIGN SYSTEM
   ============================================================ */
:root{
  --bg:#F4F2ED;
  --surface:#FFFFFF;
  --ink:#1C1917;
  --muted:#7A736A;
  --accent:#4A2E24;
  --hairline:#E3DFD7;
  --hairline-strong:#D8D2C6;

  --sp-1:8px;
  --sp-2:16px;
  --sp-3:24px;
  --sp-4:40px;
  --sp-5:64px;
  --sp-6:96px;

  --maxw:1280px;
  --ease:cubic-bezier(.16,.8,.3,1);
  --dur:500ms;

  --font-display:'EB Garamond', 'Times New Roman', serif;
  --font-body:'Instrument Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

*,*::before,*::after{ box-sizing:border-box; }
html{ -webkit-text-size-adjust:100%; scroll-behavior:smooth; }
@media (prefers-reduced-motion: reduce){ html{ scroll-behavior:auto; } }

body{
  margin:0;
  background:var(--bg);
  color:var(--ink);
  font-family:var(--font-body);
  font-weight:400;
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  min-width:320px;
  overflow-x:hidden;
}

img{ max-width:100%; display:block; }
a{ color:inherit; text-decoration:none; }
button{ font-family:inherit; cursor:pointer; }
input,textarea,select{ font-family:inherit; font-size:16px; }
ul{ margin:0; padding:0; list-style:none; }
h1,h2,h3,h4,p{ margin:0; }

.container{
  max-width:var(--maxw);
  margin:0 auto;
  padding-left:clamp(20px, 4vw, 64px);
  padding-right:clamp(20px, 4vw, 64px);
}

/* ---------- focus & a11y ---------- */
a:focus-visible,button:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible,[tabindex]:focus-visible{
  outline:2px solid var(--accent);
  outline-offset:3px;
}
.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0;
}
.skip-link{
  position:absolute; left:-9999px; top:0; background:var(--ink); color:var(--bg);
  padding:12px 20px; z-index:9999; font-family:var(--font-body); font-size:13px;
}
.skip-link:focus{ left:12px; top:12px; }

/* ---------- typography ---------- */
.eyebrow{
  font-family:var(--font-body);
  font-weight:600;
  font-size:11px;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--muted);
}
.display{ font-family:var(--font-display); font-weight:400; }
.display-italic{ font-family:var(--font-display); font-style:italic; }

h1,.h1{
  font-family:var(--font-display);
  font-weight:500;
  font-size:clamp(2.4rem, 3vw + 1.6rem, 4.6rem);
  line-height:1.05;
  letter-spacing:-.01em;
}
h2,.h2{
  font-family:var(--font-display);
  font-weight:500;
  font-size:clamp(1.7rem, 1.4vw + 1.3rem, 2.6rem);
  line-height:1.12;
}
h3,.h3{
  font-family:var(--font-display);
  font-weight:500;
  font-size:clamp(1.3rem, .6vw + 1.1rem, 1.7rem);
  line-height:1.2;
}
.lede{
  font-family:var(--font-display);
  font-size:clamp(1.05rem, .3vw + 1rem, 1.3rem);
  line-height:1.5;
  color:var(--ink);
}
.muted{ color:var(--muted); }

/* ============================================================
   LOAD / REVEAL MOTION
   ============================================================ */
.load-seq > *{
  opacity:0; transform:translateY(8px);
  animation:dadia-in var(--dur) var(--ease) forwards;
}
@keyframes dadia-in{ to{ opacity:1; transform:translateY(0); } }

.reveal{ opacity:0; transform:translateY(8px); transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease); }
.reveal.is-visible{ opacity:1; transform:translateY(0); }

@media (prefers-reduced-motion: reduce){
  .load-seq > *{ animation:none !important; opacity:1 !important; transform:none !important; }
  .reveal{ opacity:1 !important; transform:none !important; transition:none !important; }
  *{ animation-duration:.001ms !important; transition-duration:.001ms !important; }
}

/* ============================================================
   HEADER
   ============================================================ */
.site-header{
  background:var(--bg);
  border-bottom:1px solid var(--hairline);
  position:sticky; top:0; z-index:200;
}
.site-header .container{
  display:flex; align-items:center; justify-content:space-between;
  height:76px; gap:var(--sp-3);
}
.wordmark{
  font-family:var(--font-display);
  font-size:1.35rem;
  letter-spacing:.01em;
  white-space:nowrap;
  display:flex; align-items:baseline; gap:10px;
}
.wordmark img{ max-height:34px; width:auto; display:block; }
.wordmark .tag{
  font-family:var(--font-body); font-weight:600; font-size:10.5px;
  letter-spacing:.13em; text-transform:uppercase; color:var(--muted);
  display:none;
}
@media(min-width:640px){ .wordmark .tag{ display:inline; } }

.desktop-nav{ display:none; align-items:center; gap:var(--sp-3); }
@media(min-width:940px){ .desktop-nav{ display:flex; } }
.desktop-nav a, .desktop-nav button.navlink{
  font-family:var(--font-body); font-weight:500; font-size:14px;
  color:var(--ink); background:none; border:none; padding:6px 2px;
  position:relative;
}
.desktop-nav a::after, .desktop-nav button.navlink::after{
  content:''; position:absolute; left:0; right:100%; bottom:0; height:1px;
  background:var(--accent); transition:right var(--dur) var(--ease);
}
.desktop-nav a:hover::after, .desktop-nav a.active::after, .desktop-nav button.navlink:hover::after{ right:0; }
.desktop-nav a.active{ color:var(--accent); }

.more-wrap{ position:relative; }
.more-menu{
  position:absolute; top:calc(100% + 14px); right:0; background:var(--surface);
  border:1px solid var(--hairline); min-width:200px; padding:8px 0;
  box-shadow:0 12px 28px rgba(28,25,23,.08);
  opacity:0; visibility:hidden; transform:translateY(4px);
  transition:opacity .2s var(--ease), transform .2s var(--ease), visibility .2s;
}
.more-wrap:hover .more-menu, .more-wrap.open .more-menu{ opacity:1; visibility:visible; transform:translateY(0); }
.more-menu a{ display:block; padding:10px 18px; font-size:14px; }
.more-menu a:hover{ background:var(--bg); }

.contact-btn{
  border:1px solid var(--ink); background:var(--bg); color:var(--ink);
  padding:9px 18px; font-size:13px; font-weight:500; letter-spacing:.02em;
  transition:background .2s var(--ease), color .2s var(--ease);
}
.contact-btn:hover{ background:var(--ink); color:var(--bg); }

.hamburger{
  display:flex; flex-direction:column; justify-content:center; gap:5px;
  width:44px; height:44px; align-items:center; background:none; border:none;
}
@media(min-width:940px){ .hamburger{ display:none; } }
.hamburger span{ width:22px; height:1px; background:var(--ink); display:block; transition:transform .25s var(--ease), opacity .25s var(--ease); }
.hamburger[aria-expanded="true"] span:nth-child(1){ transform:translateY(6px) rotate(45deg); }
.hamburger[aria-expanded="true"] span:nth-child(2){ opacity:0; }
.hamburger[aria-expanded="true"] span:nth-child(3){ transform:translateY(-6px) rotate(-45deg); }

.mobile-menu{
  position:fixed; inset:0; background:var(--bg); z-index:300;
  display:flex; flex-direction:column;
  transform:translateY(-100%); transition:transform .35s var(--ease);
  padding:20px;
}
.mobile-menu.open{ transform:translateY(0); }
.mobile-menu-top{ display:flex; justify-content:space-between; align-items:center; height:56px; }
.mobile-menu-close{ width:44px; height:44px; background:none; border:none; position:relative; }
.mobile-menu-close::before, .mobile-menu-close::after{
  content:''; position:absolute; left:11px; right:11px; top:21px; height:1px; background:var(--ink);
}
.mobile-menu-close::before{ transform:rotate(45deg); }
.mobile-menu-close::after{ transform:rotate(-45deg); }
.mobile-menu-list{ margin-top:var(--sp-4); display:flex; flex-direction:column; }
.mobile-menu-list a{
  font-family:var(--font-display); font-size:1.9rem; padding:14px 2px;
  border-bottom:1px solid var(--hairline); min-height:44px; display:flex; align-items:center;
}
.mobile-menu-footer{ margin-top:auto; padding-top:var(--sp-3); }

/* ============================================================
   HEADER — additive (pass 2: logo-beside-name sizing)
   ============================================================ */
.wordmark img.brand-mark{ max-height:none; }

/* ============================================================
   BUTTONS / CTA
   ============================================================ */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  border:1px solid var(--ink); background:var(--bg); color:var(--ink);
  padding:13px 24px; font-size:13.5px; font-weight:500; letter-spacing:.03em;
  min-height:44px; transition:background .2s var(--ease), color .2s var(--ease), opacity .2s;
  text-transform:uppercase;
}
.btn:hover{ background:var(--ink); color:var(--bg); }
.btn.btn-quiet{ border-color:var(--hairline-strong); }
.btn-row{ display:flex; flex-wrap:wrap; gap:12px; }
.textlink{
  font-family:var(--font-body); font-weight:500; font-size:14px; color:var(--ink);
  border-bottom:1px solid var(--accent); padding-bottom:2px; display:inline-block;
}

/* ============================================================
   MUSEUM PLAQUE — signature element
   ============================================================ */
.plaque{ margin-top:14px; }
.plaque-title-row{ display:flex; align-items:baseline; justify-content:space-between; gap:12px; }
.plaque-title{
  font-family:var(--font-display); font-style:italic; font-weight:500;
  font-size:1.15rem; color:var(--ink);
}
.plaque-title .era{ font-style:italic; color:var(--muted); }
.plaque-meta{
  margin-top:5px; font-family:var(--font-body); font-weight:600; font-size:11px;
  letter-spacing:.11em; text-transform:uppercase; color:var(--muted);
  display:flex; align-items:center; justify-content:space-between; gap:6px 10px; flex-wrap:wrap;
}
.plaque-ref{ font-weight:600; color:var(--muted); letter-spacing:.08em; white-space:nowrap; }
.status-flag{ display:inline-flex; align-items:center; gap:6px; font-weight:600; font-size:11px; letter-spacing:.11em; text-transform:uppercase; }
.status-dot{ width:6px; height:6px; border-radius:50%; background:var(--accent); display:inline-block; }
.status-flag.status-available{ color:var(--muted); }
.status-flag.status-reserved{ color:var(--accent); }
.status-flag.status-sold{ color:var(--muted); }

/* ============================================================
   PIECE GRID / CARD
   ============================================================ */
.piece-grid{
  display:grid; grid-template-columns:1fr; gap:var(--sp-5) var(--sp-3);
}
@media(min-width:700px){ .piece-grid{ grid-template-columns:1fr 1fr; column-gap:var(--sp-4); } }
@media(min-width:1080px){ .piece-grid{ grid-template-columns:1fr 1fr 1fr; column-gap:var(--sp-4); } }

.piece-card{ display:block; }
.frame{
  position:relative; width:100%; aspect-ratio:4/5; overflow:hidden;
  background:var(--surface); border:1px solid var(--hairline);
}
.frame img{ width:100%; height:100%; object-fit:cover; }
.frame.is-sold img{ opacity:.88; filter:saturate(.55); }

.piece-card .plaque-meta{ margin-top:6px; }

/* ============================================================
   HERO
   ============================================================ */
.hero{ padding:var(--sp-5) 0 var(--sp-6); border-bottom:1px solid var(--hairline); }
.hero-grid{
  display:grid; grid-template-columns:1fr; gap:var(--sp-4); align-items:center;
}
@media(min-width:900px){ .hero-grid{ grid-template-columns:1.15fr .85fr; gap:var(--sp-5); } }
.hero-copy .eyebrow{ margin-bottom:var(--sp-2); }
.hero-copy h1{ margin-bottom:var(--sp-3); }
.hero-copy .hero-sub{ font-family:var(--font-display); font-size:1.15rem; color:var(--muted); max-width:44ch; margin-bottom:var(--sp-3); }
.hero-image .frame{ aspect-ratio:4/5; }
@media(min-width:900px){ .hero-image{ transform:translateY(24px); } }

/* ============================================================
   HERO — additive (pass 2: gallery carousel A/B double buffer)
   ============================================================ */
.hero-carousel{ touch-action:pan-y; }
.hero-carousel .hc-layer{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  opacity:0; transition:opacity 700ms var(--ease);
}
.hc-layer.hc-active{ opacity:1; }
.hc-controls{ display:flex; align-items:center; justify-content:flex-end; gap:10px; margin-top:12px; }
.hc-btn{
  width:40px; height:40px; display:inline-flex; align-items:center; justify-content:center;
  background:transparent; border:1px solid var(--hairline-strong); color:var(--ink); font-size:16px;
  transition:border-color .2s var(--ease);
}
.hc-btn:hover, .hc-btn:focus-visible{ border-color:var(--ink); }
.hc-counter{
  font-family:var(--font-body); font-weight:600; font-size:11px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted); font-variant-numeric:tabular-nums;
}

/* ============================================================
   SECTION SPACING
   ============================================================ */
.section{ padding:var(--sp-6) 0; }
.section + .section{ border-top:1px solid var(--hairline); }
.section-head{ display:flex; align-items:flex-end; justify-content:space-between; gap:var(--sp-3); margin-bottom:var(--sp-4); flex-wrap:wrap; }

/* ============================================================
   CATEGORY EDITORIAL ROW
   ============================================================ */
.cat-row{
  display:grid; grid-template-columns:1fr; gap:var(--sp-3);
  padding:var(--sp-4) 0; border-top:1px solid var(--hairline);
  align-items:center;
}
.cat-row:first-child{ border-top:none; }
@media(min-width:760px){ .cat-row{ grid-template-columns:220px 1fr auto; gap:var(--sp-4); } }
.cat-row .frame{ aspect-ratio:4/5; width:100%; max-width:220px; }
.cat-row-body h3{ margin-bottom:6px; }
.cat-row-body p{ color:var(--muted); font-family:var(--font-display); font-size:1rem; max-width:56ch; }
.cat-row-cta{ justify-self:start; }
@media(min-width:760px){ .cat-row-cta{ justify-self:end; } }

/* ============================================================
   STORY
   ============================================================ */
.story-col{ max-width:68ch; margin:0 auto; }
.story-col p{ font-family:var(--font-display); font-size:1.15rem; line-height:1.7; margin-bottom:var(--sp-3); color:var(--ink); }
.story-col p:last-child{ margin-bottom:0; }

/* ============================================================
   PRODUCT PAGE
   ============================================================ */
.product-layout{ display:grid; grid-template-columns:1fr; gap:var(--sp-5); }
@media(min-width:900px){ .product-layout{ grid-template-columns:1.1fr .9fr; gap:var(--sp-5); } }
.gallery-main{ position:relative; cursor:zoom-in; }
.gallery-main .frame{ aspect-ratio:4/5; }
.gallery-thumbs{ display:flex; gap:10px; margin-top:10px; flex-wrap:wrap; }
.gallery-thumbs button{
  width:64px; height:80px; padding:0; border:1px solid var(--hairline); background:var(--surface);
  overflow:hidden; opacity:.6; transition:opacity .2s;
}
.gallery-thumbs button.active,.gallery-thumbs button:hover{ opacity:1; border-color:var(--ink); }
.gallery-thumbs img{ width:100%; height:100%; object-fit:cover; }

.product-info .eyebrow{ margin-bottom:8px; }
.product-info .plaque-title{ font-size:2rem; }
.product-info .plaque-meta{ font-size:12px; margin-top:8px; }
.product-status-row{ margin:var(--sp-2) 0; }
.product-story{ font-family:var(--font-display); font-size:1.1rem; line-height:1.7; margin:var(--sp-3) 0; color:var(--ink); }

.provenance{ border-top:1px solid var(--hairline); border-bottom:1px solid var(--hairline); padding:var(--sp-3) 0; margin:var(--sp-3) 0; }
.provenance-row{ display:flex; justify-content:space-between; padding:6px 0; font-size:14px; gap:10px; flex-wrap:wrap; }
.provenance-row .k{ color:var(--muted); }
.provenance-row .v{ text-align:right; }

.enquiry-context{ font-family:var(--font-body); font-size:12px; color:var(--muted); margin-bottom:10px; letter-spacing:.02em; }

/* ============================================================
   LIGHTBOX
   ============================================================ */
.lightbox{
  position:fixed; inset:0; background:rgba(28,25,23,.94); z-index:500;
  display:flex; align-items:center; justify-content:center;
  opacity:0; visibility:hidden; transition:opacity .25s var(--ease), visibility .25s;
}
.lightbox.open{ opacity:1; visibility:visible; }
.lightbox img{ max-width:88vw; max-height:82vh; object-fit:contain; touch-action:pan-y; }
.lightbox-close{ position:absolute; top:20px; right:20px; width:48px; height:48px; background:none; border:1px solid rgba(244,242,237,.4); color:var(--bg); font-size:22px; }
.lightbox-arrow{ position:absolute; top:50%; transform:translateY(-50%); width:52px; height:52px; background:none; border:1px solid rgba(244,242,237,.4); color:var(--bg); font-size:22px; }
.lightbox-arrow.prev{ left:16px; } .lightbox-arrow.next{ right:16px; }
.lightbox-caption{ position:absolute; bottom:24px; left:0; right:0; text-align:center; color:var(--bg); font-family:var(--font-display); font-style:italic; font-size:14px; }

/* ============================================================
   FOOTER
   ============================================================ */
.site-footer{ border-top:1px solid var(--hairline); padding:var(--sp-5) 0 var(--sp-4); }
.footer-grid{ display:grid; grid-template-columns:1fr; gap:var(--sp-4); }
@media(min-width:760px){ .footer-grid{ grid-template-columns:1.3fr 1fr 1fr; } }
.footer-col h4{ font-family:var(--font-body); font-weight:600; font-size:11px; letter-spacing:.12em; text-transform:uppercase; color:var(--muted); margin-bottom:var(--sp-2); }
.footer-col a{ display:block; padding:5px 0; font-size:14px; }
.footer-bottom{ margin-top:var(--sp-5); padding-top:var(--sp-3); border-top:1px solid var(--hairline); font-size:12px; color:var(--muted); display:flex; justify-content:space-between; flex-wrap:wrap; gap:10px; }

/* ============================================================
   FOOTER — additive (pass 2: launch mode has no grid above
   footer-bottom, so its own top hairline would double up
   right under the .site-footer border)
   ============================================================ */
.site-footer.launch-mode .footer-bottom{ border-top:none; }

/* ============================================================
   404 / EMPTY
   ============================================================ */
.empty-state{ padding:var(--sp-6) 0; text-align:center; }

/* ============================================================
   ADMIN PANEL
   ============================================================ */
.admin-overlay{
  position:fixed; inset:0; background:var(--bg); z-index:1000; overflow-y:auto;
}
.admin-notice{
  background:var(--accent); color:#F4F2ED; font-size:13px; padding:10px clamp(20px,4vw,64px);
  line-height:1.5;
}
.admin-topbar{
  display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3);
  padding:16px clamp(20px,4vw,64px); border-bottom:1px solid var(--hairline); flex-wrap:wrap;
}
.admin-topbar h1{ font-size:1.4rem; }
.admin-exit{ border:1px solid var(--ink); background:transparent; padding:10px 18px; font-size:13px; font-weight:500; }
.admin-exit:hover{ background:var(--ink); color:var(--bg); }

.admin-tabs{ display:flex; gap:0; padding:0 clamp(20px,4vw,64px); border-bottom:1px solid var(--hairline); overflow-x:auto; }
.admin-tab{
  background:none; border:none; padding:14px 18px; font-size:13px; font-weight:600;
  letter-spacing:.04em; text-transform:uppercase; color:var(--muted); white-space:nowrap;
  border-bottom:2px solid transparent; margin-bottom:-1px;
}
.admin-tab.active{ color:var(--ink); border-bottom-color:var(--accent); }

.admin-body{ padding:var(--sp-4) clamp(20px,4vw,64px) var(--sp-6); max-width:1100px; }
.admin-section-title{ font-family:var(--font-display); font-size:1.5rem; margin-bottom:var(--sp-1); }
.admin-section-sub{ color:var(--muted); font-size:13px; margin-bottom:var(--sp-3); }

.field{ margin-bottom:var(--sp-3); max-width:560px; }
.field.full{ max-width:none; }
.field label{ display:block; font-size:12px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); margin-bottom:6px; }
.field input[type=text], .field input[type=email], .field input[type=tel], .field input[type=url], .field input[type=number], .field textarea, .field select{
  width:100%; border:1px solid var(--hairline-strong); background:var(--surface); padding:11px 12px; font-size:14px; color:var(--ink);
}
.field textarea{ resize:vertical; min-height:90px; font-family:var(--font-body); }
.field .hint{ font-size:12px; color:var(--muted); margin-top:5px; }
.field .hint.warn{ color:#8a4a2e; }
.field .hint.err{ color:#a3341f; }
.field-row{ display:flex; gap:var(--sp-2); flex-wrap:wrap; }
.field-row .field{ flex:1; min-width:140px; }

.admin-btn{ border:1px solid var(--ink); background:var(--bg); color:var(--ink); padding:11px 20px; font-size:13px; font-weight:500; }
.admin-btn:hover{ background:var(--ink); color:var(--bg); }
.admin-btn.primary{ background:var(--ink); color:var(--bg); }
.admin-btn.primary:hover{ opacity:.85; }
.admin-btn.danger{ border-color:#a3341f; color:#a3341f; }
.admin-btn.danger:hover{ background:#a3341f; color:#fff; }
.admin-btn.small{ padding:7px 12px; font-size:12px; }
.admin-btn[disabled]{ opacity:.4; cursor:not-allowed; }

.admin-table{ width:100%; min-width:620px; border-collapse:collapse; margin-top:var(--sp-2); }
.table-scroll{ overflow-x:auto; -webkit-overflow-scrolling:touch; }
.admin-table th{ text-align:left; font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--muted); padding:8px 10px; border-bottom:1px solid var(--hairline-strong); }
.admin-table td{ padding:10px; border-bottom:1px solid var(--hairline); font-size:13.5px; vertical-align:middle; }
.admin-thumb{ width:48px; height:60px; object-fit:cover; border:1px solid var(--hairline); background:var(--surface); }
.admin-row-actions{ display:flex; gap:6px; flex-wrap:wrap; }
.sample-tag{ display:inline-block; background:var(--bg); border:1px solid var(--hairline-strong); color:var(--muted); font-size:10px; padding:2px 6px; letter-spacing:.05em; text-transform:uppercase; margin-left:6px; }
.status-pill{ font-size:11px; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); }

.admin-card{ border:1px solid var(--hairline); background:var(--surface); padding:var(--sp-3); margin-bottom:var(--sp-3); }
.admin-imgs{ display:flex; gap:10px; flex-wrap:wrap; margin-top:10px; }
.admin-img-item{ width:90px; }
.admin-img-item img{ width:90px; height:112px; object-fit:cover; border:1px solid var(--hairline); }
.admin-img-item .meta{ font-size:10px; color:var(--muted); margin-top:4px; word-break:break-word; }
.admin-img-item .row{ display:flex; gap:4px; margin-top:4px; }
.admin-img-item button{ font-size:10px; padding:3px 5px; border:1px solid var(--hairline-strong); background:var(--bg); }
.cover-badge{ font-size:9px; background:var(--accent); color:#fff; padding:1px 5px; display:inline-block; margin-top:3px; }

.modal-backdrop{ position:fixed; inset:0; background:rgba(28,25,23,.55); z-index:2000; display:flex; align-items:center; justify-content:center; padding:20px; }
.modal-box{ background:var(--surface); max-width:640px; width:100%; max-height:86vh; overflow-y:auto; padding:var(--sp-4); border:1px solid var(--hairline); }
.modal-box h3{ margin-bottom:var(--sp-2); }
.modal-box textarea{ width:100%; height:240px; font-family:monospace; font-size:11px; border:1px solid var(--hairline-strong); padding:10px; }
.modal-close-row{ display:flex; justify-content:flex-end; gap:10px; margin-top:var(--sp-3); flex-wrap:wrap; }

.confirm-box p{ margin-bottom:var(--sp-3); font-size:14px; }

.size-meter{ margin-top:10px; }
.size-meter-bar{ height:6px; background:var(--hairline); position:relative; overflow:hidden; }
.size-meter-fill{ height:100%; background:var(--accent); transition:width .3s var(--ease); }

.toast{
  position:fixed; bottom:24px; left:50%; transform:translateX(-50%) translateY(10px); background:var(--ink); color:var(--bg);
  padding:12px 22px; font-size:13px; z-index:3000; opacity:0; transition:opacity .25s, transform .25s; pointer-events:none;
}
.toast.show{ opacity:1; transform:translateX(-50%) translateY(0); }

.admin-explainer{ background:var(--surface); border:1px solid var(--hairline); padding:var(--sp-3); font-size:13.5px; line-height:1.65; max-width:640px; }
.admin-explainer ol{ padding-left:20px; margin-top:8px; }
.admin-explainer li{ margin-bottom:6px; }

/* ============================================================
   ADMIN — additive (pass 2: Galeri tab)
   ============================================================ */
.gallery-dropzone{
  border:1px solid var(--hairline-strong); background:var(--surface);
  padding:var(--sp-3); text-align:center; margin:var(--sp-2) 0 var(--sp-3);
  transition:border-color .2s var(--ease), background .2s var(--ease);
}
.gallery-dropzone.drag-active{ border-color:var(--accent); background:var(--bg); }
.admin-table input[type=text]{
  width:100%; border:1px solid var(--hairline-strong); background:var(--surface);
  padding:8px 10px; font-size:13px; color:var(--ink);
}

/* ============================================================
   PRINT
   ============================================================ */
@media print{
  .site-header, .site-footer .footer-col:nth-child(2), .btn-row, .hamburger, .mobile-menu, .lightbox, .admin-overlay, .cat-row-cta, .textlink, .contact-btn{ display:none !important; }
  body{ background:#fff; color:#000; }
  .frame{ border:1px solid #999; break-inside:avoid; }
  .piece-card, .cat-row, .product-layout{ break-inside:avoid; page-break-inside:avoid; }
  a{ color:#000; }
  .section{ padding:20px 0; }
}

@media(max-width:380px){
  .container{ padding-left:20px; padding-right:20px; }
}
</style>
</head>
<body>
<div id="app"></div>
<div id="admin-root"></div>
<script id="dadia-data" type="application/json">{
  "settings": {
    "brandName": "Dadia Ateliers",
    "tagline": "Curated Imports & Design",
    "logo": null,
    "heroTitle": "A new chapter of architectural heritage is unfolding. ",
    "heroSubtitle": "We are renewing our curation for the world’s finest estates. Objects with history, for spaces without noise. New site arriving soon!",
    "storyTitle": "Dadia Ateliers: The Global Curators of Architectural Heritage / From the Heart of History to the World’s Finest Estates",
    "storyParagraphs": [
      "At Dadia Ateliers, we believe that a space only becomes a sanctuary when it tells a story. We are not merely importers; we are curators of time, specializing in the sourcing and integration of rare, certified artifacts into modern architectural masterpieces across the globe.",
      "A Legacy of Excellence From Türkiye to the World: Our journey is rooted in decades of expertise. Dadia Ateliers has long been the silent partner behind Turkey’s most iconic boutique hotels and prestigious private residences. From the Aegean coastline to the Mediterranean’s most exclusive estates, our curation has defined the aesthetic of high-end living.",
      "This deep-rooted experience in the Turkish market—a land where history is etched into every stone—has provided us with an unparalleled network of private collectors and heritage depots. Today, we bring this mastery to a global stage, serving a sophisticated international clientele that demands authenticity over mass production.",
      "Elevating Large-Scale Developments: We understand that for major architectural projects and luxury developments, a piece of decor is more than an object; it is a strategic asset. Our mission is to elevate large-scale developments into cultural landmarks.",
      "By integrating 1-of-1 artifacts—from monumental Dagar pottery to ancient stonework and handcrafted timber—we provide the \"soul\" that distinguishes a premium project from a timeless legacy. We work hand-in-hand with developers and visionary architects to ensure that every artifact is a deliberate statement of prestige and character.",
      "Managing the movement of history requires precision, legal mastery, and a global vision. Dadia Ateliers offers a specialized consultancy and sourcing service designed for the elite market: ",
      "Global Bespoke Sourcing: We utilize our vast network of private collectors to find specific eras, scales, and textures for our clients’ unique requirements.",
      "Legal Heritage & Peace of Mind: We manage the entire legal complexity of antiquity certification and global export-import documentation, ensuring full compliance for every artifact we deliver worldwide.",
      "Architectural Ambiance Consulting: Beyond supply, we provide professional placement and curation proposals. We analyze your project’s scale to propose how our artifacts can best elevate the property’s market value and aesthetic impact.",
      "Our Vision: Dadia Ateliers is a bridge between the craftsmanship of the past and the architecture of the future. We invite you to partner with us as we continue to transform the world’s most prestigious developments into living galleries of human history.",
      "Dadia Ateliers\nCurating History. Elevating Modernity. Globally."
    ],
    "whatsapp": "38267198155",
    "email": "hello@dadiaateliers.com",
    "instagram": "https://instagram.com/dadia_ateliers",
    "siteUrl": "https://dadiaateliers.com",
    "metaDescription": "Dadia Ateliers is the premier global curator of architectural antiques and certified heritage artifacts. Specializing in one-of-a-kind antique pots, Dagar pottery, Enez vessels, and hand-carved limestone basins, we bring an ancient soul to modern luxury villas, boutique hotels, and yacht interiors in Montenegro, Tivat, Porto Montenegro, and Budva. From reclaimed wood decor to Mediterranean stoneware, our bespoke sourcing and interior design consultancy services provide authentic textures for the world's most prestigious architectural landmarks. Experience the art of the artifact with Dadia Ateliers – your professional partner for high-end antique imports and heritage curation.",
    "adminPath": "dadia-atolye",
    "footerNote": "Dadia Ateliers · Curated Imports & Design · Tivat, Montenegro",
    "launchMode": true,
    "logoHeight": 34,
    "logoDisplay": "mark-and-name",
    "launchEyebrow": "Opening soon",
    "launchCtaLabel": "Contact",
    "gallery": [],
    "galleryIntervalMs": 5200,
    "seoKeywords": "",
    "geoLocality": "Tivat",
    "geoRegion": "Montenegro",
    "foundingYear": "2026"
  },
  "categories": [
    {
      "id": "cat-vessels",
      "slug": "vessels-jars",
      "name": "Vessels & Jars",
      "type": "standard",
      "intro": "",
      "order": 1
    },
    {
      "id": "cat-courtyard",
      "slug": "the-courtyard-edit",
      "name": "The Courtyard Edit",
      "type": "concept",
      "intro": "A composed selection for gardens, terraces and entrances — pieces that anchor a space the way trees do.",
      "order": 2
    }
  ],
  "products": [
    {
      "id": "prod-001",
      "ref": "DA-001",
      "categoryId": "cat-vessels",
      "title": "Enez Jar",
      "origin": "Aegean Anatolia",
      "era": "c. 1930",
      "h": 112,
      "w": 58,
      "d": 58,
      "story": "Found in a warehouse outside Enez, on Turkey's northern Aegean coast, this terracotta olive jar was hand-built in the early twentieth century for the storage of oil and grain. Its surface carries the fire-marks and the pooled glaze of a wood-fired kiln, and the slight lean of its shoulder that only comes from being thrown by hand. Reglazed nowhere, repaired nowhere — it is sold exactly as it was found.",
      "status": "available",
      "featured": true,
      "order": 1,
      "sample": true,
      "images": [
        {
          "data": "data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20800%201000%22%3E%3Crect%20width%3D%22800%22%20height%3D%221000%22%20fill%3D%22%23F4F2ED%22%2F%3E%3Cpath%20d%3D%22M400%20130%20C%20435%20130%20452%20156%20452%20190%20L%20452%20224%20C%20505%20254%20542%20328%20542%20452%20L%20542%20760%20C%20542%20826%20480%20868%20400%20868%20C%20320%20868%20258%20826%20258%20760%20L%20258%20452%20C%20258%20328%20295%20254%20348%20224%20L%20348%20190%20C%20348%20156%20365%20130%20400%20130%20Z%22%20fill%3D%22none%22%20stroke%3D%22%23C9C2B4%22%20stroke-width%3D%222%22%2F%3E%3Cline%20x1%3D%22348%22%20y1%3D%22190%22%20x2%3D%22452%22%20y2%3D%22190%22%20stroke%3D%22%23C9C2B4%22%20stroke-width%3D%222%22%2F%3E%3C%2Fsvg%3E",
          "alt": "Enez Jar, terracotta olive jar, Aegean Anatolia"
        }
      ]
    },
    {
      "id": "prod-002",
      "ref": "DA-002",
      "categoryId": "cat-courtyard",
      "title": "Kütahya Stone Basin",
      "origin": "Central Anatolia",
      "era": "Early 20th century",
      "h": 46,
      "w": 96,
      "d": 96,
      "story": "Carved from a single block of limestone in the workshops outside Kütahya, this shallow basin once collected rainwater and washwater in a village courtyard. Decades of use have worn its interior to a soft, tactile hollow, and its rim carries the faint chisel marks of the mason who shaped it. Set on a terrace or beside a doorway, it reads as sculpture as much as vessel.",
      "status": "reserved",
      "featured": true,
      "order": 2,
      "sample": true,
      "images": [
        {
          "data": "data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20800%201000%22%3E%3Crect%20width%3D%22800%22%20height%3D%221000%22%20fill%3D%22%23F4F2ED%22%2F%3E%3Cpath%20d%3D%22M180%20420%20C%20180%20400%20200%20388%20400%20388%20C%20600%20388%20620%20400%20620%20420%20L%20600%20620%20C%20596%20660%20520%20690%20400%20690%20C%20280%20690%20204%20660%20200%20620%20Z%22%20fill%3D%22none%22%20stroke%3D%22%23C9C2B4%22%20stroke-width%3D%222%22%2F%3E%3Cellipse%20cx%3D%22400%22%20cy%3D%22420%22%20rx%3D%22220%22%20ry%3D%2226%22%20fill%3D%22none%22%20stroke%3D%22%23C9C2B4%22%20stroke-width%3D%222%22%2F%3E%3C%2Fsvg%3E",
          "alt": "Kütahya carved stone basin, Central Anatolia"
        }
      ]
    },
    {
      "id": "prod-003",
      "ref": "DA-003",
      "categoryId": "cat-courtyard",
      "title": "Carved Limestone Trough",
      "origin": "Southeastern Anatolia",
      "era": "19th century",
      "h": 52,
      "w": 140,
      "d": 60,
      "story": "Originally cut for a stable in southeastern Anatolia, this long limestone trough has the heft and quiet authority of true architectural salvage. Its surfaces are weathered rather than finished, marked by a century of livestock and weather. Reset in a garden, it holds water, plants, or nothing at all — its presence is the point.",
      "status": "sold",
      "featured": false,
      "order": 3,
      "sample": true,
      "images": [
        {
          "data": "data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20800%201000%22%3E%3Crect%20width%3D%22800%22%20height%3D%221000%22%20fill%3D%22%23F4F2ED%22%2F%3E%3Cpath%20d%3D%22M120%20460%20L%20680%20460%20C%20690%20460%20696%20468%20694%20478%20L%20664%20640%20C%20660%20660%20640%20674%20616%20674%20L%20184%20674%20C%20160%20674%20140%20660%20136%20640%20L%20106%20478%20C%20104%20468%20110%20460%20120%20460%20Z%22%20fill%3D%22none%22%20stroke%3D%22%23C9C2B4%22%20stroke-width%3D%222%22%2F%3E%3Cline%20x1%3D%22150%22%20y1%3D%22460%22%20x2%3D%22650%22%20y2%3D%22460%22%20stroke%3D%22%23C9C2B4%22%20stroke-width%3D%222%22%2F%3E%3C%2Fsvg%3E",
          "alt": "Carved limestone trough, Southeastern Anatolia"
        }
      ]
    }
  ]
}</script>
<script>
(function(){
'use strict';

/* ===========================================================
   0. PRISTINE SNAPSHOT — must run before any DOM mutation
   =========================================================== */
var PRISTINE = '<!DOCTYPE html>\n' + document.documentElement.outerHTML;

/* ===========================================================
   1. DATA LOAD
   =========================================================== */
var DATA_SCRIPT_ID = 'dadia-data';
var RAW_DEFAULT = document.getElementById(DATA_SCRIPT_ID).textContent;
var DEFAULT_DATA = JSON.parse(RAW_DEFAULT);
var DATA = deepClone(DEFAULT_DATA);
ensureSettingsDefaults(DATA);

function deepClone(o){ return JSON.parse(JSON.stringify(o)); }

// Forward-compatible settings migration: fills in keys introduced by later
// passes without ever overwriting a value that's already present, so old
// backups (and the seed data before this pass) keep loading cleanly.
function ensureSettingsDefaults(data){
  if(!data || typeof data !== 'object') return;
  if(!data.settings || typeof data.settings !== 'object') data.settings = {};
  var s = data.settings;
  var defaults = {
    launchMode: true,
    logoHeight: 34,
    logoDisplay: 'mark-and-name',
    launchEyebrow: 'Opening soon',
    launchCtaLabel: 'Enquiries',
    gallery: [],
    galleryIntervalMs: 5200,
    seoKeywords: '',
    geoLocality: 'Tivat',
    geoRegion: 'Montenegro',
    foundingYear: '2026'
  };
  for(var k in defaults){
    if(s[k] === undefined) s[k] = defaults[k];
  }
}

/* ===========================================================
   2. APP STATE
   =========================================================== */
var STATE = {
  mobileMenuOpen: false,
  moreOpen: false,
  contactOpen: false,
  lightbox: null,           // { images:[{data,alt}], index, title }
  adminDirty: false
};

var ADMIN = {
  active: false,
  tab: 'urunler',
  productView: 'list',      // 'list' | 'form'
  productForm: null,        // draft product
  categoryView: 'list',
  categoryForm: null,
  categoryDeleteMoveTarget: '',
  confirm: null,            // { title, message, confirmLabel, danger, onConfirm }
  publishModal: null,       // string source for fallback textarea
  toast: null,
  adminPathDraft: null,
  restoreError: null,
  zipBusy: false,            // pass 3: folder ZIP publish in progress
  zipProgress: null,         // pass 3: { current, total } while packaging
  zipMissing: null,          // pass 3: filenames that couldn't be sourced for the last ZIP
  publishedGallerySnapshot: null // pass 3: JSON snapshot of settings.gallery as of the last full ZIP publish
};

/* ===========================================================
   3. GENERIC HELPERS
   =========================================================== */
function qs(sel, ctx){ return (ctx||document).querySelector(sel); }
function qsa(sel, ctx){ return Array.prototype.slice.call((ctx||document).querySelectorAll(sel)); }

function escapeHtml(str){
  if(str === null || str === undefined) return '';
  return String(str)
    .replace(/&/g,'&amp;')
    .replace(/</g,'&lt;')
    .replace(/>/g,'&gt;')
    .replace(/"/g,'&quot;')
    .replace(/'/g,'&#39;');
}
function escapeAttr(str){ return escapeHtml(str); }

function slugify(str){
  return String(str||'')
    .toLowerCase()
    .trim()
    .replace(/[^a-z0-9\u00c0-\u017f\s-]/g,'')
    .replace(/\s+/g,'-')
    .replace(/-+/g,'-')
    .replace(/^-|-$/g,'') || 'koleksiyon';
}

function uniqueSlug(base, ignoreId){
  var s = slugify(base);
  var candidate = s;
  var n = 2;
  while(DATA.categories.some(function(c){ return c.slug === candidate && c.id !== ignoreId; })){
    candidate = s + '-' + n;
    n++;
  }
  return candidate;
}

function byOrder(a,b){ return (a.order||0) - (b.order||0); }

function findCategory(id){ return DATA.categories.find(function(c){ return c.id === id; }); }
function categoryBySlug(slug){ return DATA.categories.find(function(c){ return c.slug === slug; }); }
function productByRef(ref){ return DATA.products.find(function(p){ return p.ref === ref; }); }
function productsInCategory(catId){
  return DATA.products.filter(function(p){ return p.categoryId === catId; }).sort(byOrder);
}

function nextRef(){
  var max = 0;
  DATA.products.forEach(function(p){
    var m = /^DA-(\d+)$/.exec(p.ref||'');
    if(m){ max = Math.max(max, parseInt(m[1],10)); }
  });
  var n = max + 1;
  return 'DA-' + String(n).padStart(3,'0');
}

function cmToIn(cm){ return (cm * 0.393701).toFixed(1); }

function uid(prefix){
  return prefix + '-' + Date.now().toString(36) + Math.random().toString(36).slice(2,7);
}

function genId(){ return uid('id'); }

function statusLabel(status){
  if(status === 'available') return 'Available';
  if(status === 'reserved') return 'Reserved';
  if(status === 'sold') return 'Sold';
  return status;
}

function formatDims(p){
  return p.h + '\u00d7' + p.w + '\u00d7' + p.d + ' cm';
}

function firstImage(p){
  return (p.images && p.images[0]) ? p.images[0] : null;
}

function coverSrc(p){
  var img = firstImage(p);
  return img ? img.data : '';
}

function showToast(msg){
  ADMIN.toast = msg;
  renderAdmin();
  setTimeout(function(){
    var t = qs('.toast');
    if(t) t.classList.add('show');
  }, 10);
  clearTimeout(showToast._t);
  showToast._t = setTimeout(function(){
    ADMIN.toast = null;
    var t = qs('.toast');
    if(t) t.classList.remove('show');
  }, 2600);
}

/* ===========================================================
   4. ROUTING
   =========================================================== */
if('scrollRestoration' in history){ try{ history.scrollRestoration = 'manual'; } catch(e){} }
var SCROLL_POS = {};   // scrollY per route hash, keyed on the way out
var NAV_INTENT = null; // 'push' | 'top' | null(=browser back/forward) — reset at the end of every router() run

function currentAdminPath(){
  return (DATA.settings.adminPath || 'dadia-atolye').replace(/^\/+|\/+$/g,'');
}

function parseHash(){
  var h = window.location.hash || '';
  h = h.replace(/^#/, '');
  if(h.indexOf('/') !== 0) h = '/' + h;
  return h.split('/').filter(Boolean);
}

function navigate(hash){
  if(window.location.hash === hash){ router(); }
  else { window.location.hash = hash; }
}

function router(){
  teardownHeroCarousel();
  var parts = parseHash();
  STATE.mobileMenuOpen = false;
  STATE.moreOpen = false;

  if(parts.length && parts[0] === currentAdminPath()){
    ADMIN.active = true;
    renderAdmin();
    window.scrollTo(0,0);
    NAV_INTENT = null;
    return;
  }
  ADMIN.active = false;
  var adminRoot = document.getElementById('admin-root');
  if(adminRoot) adminRoot.innerHTML = '';

  var app = document.getElementById('app');
  var launchMode = !!DATA.settings.launchMode;

  if(parts.length === 0){
    app.innerHTML = shellHTML(homeHTML());
  } else if(parts[0] === 'collection' && parts[1]){
    if(launchMode){ window.location.hash = '#/'; return; }
    var cat = categoryBySlug(decodeURIComponent(parts[1]));
    app.innerHTML = shellHTML(cat ? collectionHTML(cat) : notFoundHTML());
  } else if(parts[0] === 'piece' && parts[1]){
    if(launchMode){ window.location.hash = '#/'; return; }
    var prod = productByRef(decodeURIComponent(parts[1]));
    app.innerHTML = shellHTML(prod ? productHTML(prod) : notFoundHTML());
  } else if(parts[0] === 'story'){
    app.innerHTML = shellHTML(storyHTML());
  } else {
    app.innerHTML = shellHTML(notFoundHTML());
  }

  applyScrollForRoute();
  afterPublicRender();
  NAV_INTENT = null;
}

function applyScrollForRoute(){
  var hash = window.location.hash || '#/';
  if(NAV_INTENT === null && Object.prototype.hasOwnProperty.call(SCROLL_POS, hash)){
    var pos = SCROLL_POS[hash];
    requestAnimationFrame(function(){ window.scrollTo(0, pos); });
  } else {
    window.scrollTo(0,0);
  }
}

function onHashChange(event){
  var oldHash = '#/';
  if(event && event.oldURL){
    var idx = event.oldURL.indexOf('#');
    oldHash = idx >= 0 ? event.oldURL.slice(idx) : '#/';
  }
  SCROLL_POS[oldHash] = window.scrollY;
  router();
}
window.addEventListener('hashchange', onHashChange);

/* ===========================================================
   5. PUBLIC SHELL — header + main + footer
   =========================================================== */
function shellHTML(mainInner){
  return headerHTML() + '<main id="main-content">' + mainInner + '</main>' + footerHTML() + mobileMenuHTML() + contactModalHTML() + lightboxHTML();
}

function sortedCategories(){
  return DATA.categories.slice().sort(byOrder);
}

function currentRouteInfo(){
  var parts = parseHash();
  return parts;
}

function brandInnerHTML(){
  var s = DATA.settings;
  var display = s.logoDisplay || 'mark-and-name';
  var hasLogo = !!s.logo;
  var logoH = Math.min(96, Math.max(16, Number(s.logoHeight) || 34));
  var imgHTML = hasLogo ? '<img class="brand-mark" style="height:' + logoH + 'px;" src="' + escapeAttr(s.logo) + '" alt="' + escapeAttr(s.brandName) + '">' : '';
  var tagHTML = '<span class="tag">' + escapeHtml(s.tagline) + '</span>';

  if(hasLogo && display === 'mark-only'){
    return imgHTML + '<span class="sr-only">' + escapeHtml(s.brandName) + '</span>' + tagHTML;
  }
  if(hasLogo && display !== 'name-only'){
    return imgHTML + '<span>' + escapeHtml(s.brandName) + '</span>' + tagHTML;
  }
  return '<span>' + escapeHtml(s.brandName) + '</span>' + tagHTML;
}

function headerHTML(){
  var s = DATA.settings;
  var launchMode = !!s.launchMode;
  var cats = sortedCategories();
  var visible = cats.slice(0,4);
  var overflow = cats.slice(4);
  var parts = currentRouteInfo();

  function isActiveCat(c){ return parts[0] === 'collection' && decodeURIComponent(parts[1]||'') === c.slug; }

  var navLinks = launchMode ? '' : visible.map(function(c){
    return '<a href="#/collection/' + encodeURIComponent(c.slug) + '"' + (isActiveCat(c) ? ' class="active"' : '') + '>' + escapeHtml(c.name) + '</a>';
  }).join('');

  var moreHTML = '';
  if(!launchMode && overflow.length){
    moreHTML = '<div class="more-wrap' + (STATE.moreOpen ? ' open' : '') + '">' +
      '<button type="button" class="navlink" data-action="toggle-more" aria-haspopup="true" aria-expanded="' + (STATE.moreOpen?'true':'false') + '">More \u25be</button>' +
      '<div class="more-menu" role="menu">' +
        overflow.map(function(c){
          return '<a href="#/collection/' + encodeURIComponent(c.slug) + '" role="menuitem">' + escapeHtml(c.name) + '</a>';
        }).join('') +
      '</div></div>';
  }

  var storyActive = parts[0] === 'story';

  return '' +
  '<a class="skip-link" href="#main-content">Skip to content</a>' +
  '<header class="site-header' + (launchMode ? ' launch-mode' : '') + '">' +
    '<div class="container">' +
      '<a class="wordmark" href="#/" data-action="go-home-top">' + brandInnerHTML() + '</a>' +
      '<nav class="desktop-nav" aria-label="Primary">' +
        navLinks +
        moreHTML +
        '<a href="#/story"' + (storyActive ? ' class="active"' : '') + '>Story</a>' +
        '<button type="button" class="contact-btn" data-action="open-contact">Contact</button>' +
      '</nav>' +
      '<button type="button" class="hamburger" data-action="toggle-mobile-menu" aria-label="Open menu" aria-expanded="' + (STATE.mobileMenuOpen?'true':'false') + '">' +
        '<span></span><span></span><span></span>' +
      '</button>' +
    '</div>' +
  '</header>';
}

function mobileMenuHTML(){
  var launchMode = !!DATA.settings.launchMode;
  var cats = sortedCategories();
  var links = launchMode ? '' : cats.map(function(c){
    return '<a href="#/collection/' + encodeURIComponent(c.slug) + '" data-action="close-mobile-menu">' + escapeHtml(c.name) + '</a>';
  }).join('');
  return '' +
  '<div class="mobile-menu' + (STATE.mobileMenuOpen ? ' open' : '') + '" role="dialog" aria-modal="true" aria-label="Site menu">' +
    '<div class="mobile-menu-top">' +
      '<span class="eyebrow">Menu</span>' +
      '<button type="button" class="mobile-menu-close" data-action="close-mobile-menu" aria-label="Close menu"></button>' +
    '</div>' +
    '<nav class="mobile-menu-list">' +
      links +
      '<a href="#/story" data-action="close-mobile-menu">Story</a>' +
      '<a href="#" data-action="open-contact-mobile">Contact</a>' +
    '</nav>' +
    '<div class="mobile-menu-footer muted eyebrow">' + escapeHtml(DATA.settings.footerNote) + '</div>' +
  '</div>';
}

function enquiryHref(kind, product){
  var s = DATA.settings;
  if(kind === 'whatsapp'){
    if(!s.whatsapp) return '';
    var text = product
      ? "Hello Dadia Ateliers \u2014 I'm interested in " + product.title + " (Ref " + product.ref + "). Could you share pricing and availability?"
      : "Hello Dadia Ateliers \u2014 I'd like to know more about your collection.";
    return 'https://wa.me/' + s.whatsapp.replace(/[^0-9]/g,'') + '?text=' + encodeURIComponent(text);
  }
  if(kind === 'email'){
    if(!s.email) return '';
    var subject = product ? ('Enquiry \u2014 ' + product.title + ' (Ref ' + product.ref + ')') : 'Enquiry \u2014 Dadia Ateliers';
    return 'mailto:' + s.email + '?subject=' + encodeURIComponent(subject);
  }
  return '';
}

function contactModalHTML(){
  var s = DATA.settings;
  var wa = enquiryHref('whatsapp', null);
  var em = enquiryHref('email', null);

  if(!STATE.contactOpen){
    // modal just closed: return focus to whatever opened it
    if(STATE.contactOpener){
      var opener = STATE.contactOpener;
      STATE.contactOpener = null;
      requestAnimationFrame(function(){ if(opener && typeof opener.focus === 'function') opener.focus(); });
    }
    return '<div class="modal-backdrop" style="display:none"></div>';
  }

  if(!STATE.contactOpener){
    // modal just opened: remember the opener, move focus inside once rendered
    STATE.contactOpener = document.activeElement;
    requestAnimationFrame(function(){
      var box = qs('.contact-modal');
      if(!box) return;
      var focusables = qsa('button, a[href], input, textarea, select, [tabindex]:not([tabindex="-1"])', box);
      if(focusables.length) focusables[0].focus();
    });
  }

  return '' +
  '<div class="modal-backdrop" data-action="close-contact-backdrop">' +
    '<div class="modal-box contact-modal" role="dialog" aria-modal="true" aria-labelledby="contact-modal-title">' +
      '<span class="eyebrow">Contact</span>' +
      '<h3 id="contact-modal-title" style="margin-top:8px;">Get in touch</h3>' +
      '<p class="muted" style="margin-bottom:16px;font-size:14px;">Enquiries are answered directly \u2014 no listings, no checkout, just conversation.</p>' +
      '<div class="btn-row">' +
        (wa ? '<a class="btn" href="' + escapeAttr(wa) + '" target="_blank" rel="noopener">WhatsApp</a>' : '') +
        (em ? '<a class="btn" href="' + escapeAttr(em) + '">Email</a>' : '') +
        (s.instagram ? '<a class="btn btn-quiet" href="' + escapeAttr(s.instagram) + '" target="_blank" rel="noopener">Instagram</a>' : '') +
      '</div>' +
      (!wa && !em && !s.instagram ? '<p class="muted" style="margin-top:14px;font-size:13px;">Contact details coming soon.</p>' : '') +
      (s.email ? '<p class="muted" style="margin-top:14px;font-size:12.5px;">' +
        '<a href="mailto:' + escapeAttr(s.email) + '">' + escapeHtml(s.email) + '</a> \u00b7 ' +
        '<button type="button" class="admin-btn small" data-action="copy-email" style="margin-left:4px;">Copy</button>' +
      '</p>' : '') +
      (STATE.publicToast ? '<div class="toast show">' + escapeHtml(STATE.publicToast) + '</div>' : '') +
      '<div class="modal-close-row"><button type="button" class="admin-btn" data-action="close-contact">Close</button></div>' +
    '</div>' +
  '</div>';
}

// Tab-trap for the contact modal, in its own listener so it never has to
// touch the shared keydown handler that already owns Escape.
document.addEventListener('keydown', function(e){
  if(!STATE.contactOpen || e.key !== 'Tab') return;
  var box = qs('.contact-modal');
  if(!box) return;
  var focusables = qsa('button, a[href], input, textarea, select, [tabindex]:not([tabindex="-1"])', box)
    .filter(function(el){ return !el.disabled; });
  if(!focusables.length) return;
  var first = focusables[0], last = focusables[focusables.length-1];
  if(e.shiftKey && document.activeElement === first){
    e.preventDefault(); last.focus();
  } else if(!e.shiftKey && document.activeElement === last){
    e.preventDefault(); first.focus();
  }
});

function copyEmailToClipboard(){
  var email = DATA.settings.email;
  if(!email) return;

  function refocusCopyBtn(){
    requestAnimationFrame(function(){
      var btn = qs('[data-action="copy-email"]');
      if(btn && STATE.contactOpen) btn.focus();
    });
  }
  function afterCopy(){
    STATE.publicToast = 'Kopyaland\u0131';
    rerenderChrome();
    refocusCopyBtn();
    clearTimeout(copyEmailToClipboard._t);
    copyEmailToClipboard._t = setTimeout(function(){
      STATE.publicToast = null;
      rerenderChrome();
      refocusCopyBtn();
    }, 2600);
  }
  function fallbackCopy(){
    var ta = document.createElement('textarea');
    ta.value = email;
    ta.setAttribute('readonly','');
    ta.style.position = 'fixed';
    ta.style.opacity = '0';
    document.body.appendChild(ta);
    ta.select();
    try{ document.execCommand('copy'); } catch(e){}
    document.body.removeChild(ta);
    afterCopy();
  }
  if(navigator.clipboard && navigator.clipboard.writeText){
    navigator.clipboard.writeText(email).then(afterCopy).catch(fallbackCopy);
  } else {
    fallbackCopy();
  }
}

function lightboxHTML(){
  if(!STATE.lightbox) return '<div class="lightbox" aria-hidden="true"></div>';
  var lb = STATE.lightbox;
  var img = lb.images[lb.index];
  return '' +
  '<div class="lightbox open" role="dialog" aria-modal="true" aria-label="Image viewer">' +
    '<button type="button" class="lightbox-close" data-action="lightbox-close" aria-label="Close">\u2715</button>' +
    (lb.images.length > 1 ? '<button type="button" class="lightbox-arrow prev" data-action="lightbox-prev" aria-label="Previous image">\u2039</button>' : '') +
    '<img src="' + img.data + '" alt="' + escapeAttr(img.alt || lb.title || '') + '">' +
    (lb.images.length > 1 ? '<button type="button" class="lightbox-arrow next" data-action="lightbox-next" aria-label="Next image">\u203a</button>' : '') +
    '<div class="lightbox-caption">' + escapeHtml(lb.title || '') + ' \u00b7 ' + (lb.index+1) + ' / ' + lb.images.length + '</div>' +
  '</div>';
}

function footerHTML(){
  var s = DATA.settings;
  var launchMode = !!s.launchMode;
  var cats = sortedCategories();
  var wa = enquiryHref('whatsapp', null);
  var em = enquiryHref('email', null);
  var footerGrid = '' +
      '<div class="footer-grid">' +
        '<div class="footer-col">' +
          '<h4>Dadia Ateliers</h4>' +
          '<p class="muted" style="font-size:14px;max-width:36ch;">' + escapeHtml(s.tagline) + '</p>' +
          '<div class="btn-row" style="margin-top:16px;">' +
            (wa ? '<a class="btn btn-quiet" href="' + escapeAttr(wa) + '" target="_blank" rel="noopener">WhatsApp</a>' : '') +
            (em ? '<a class="btn btn-quiet" href="' + escapeAttr(em) + '">Email</a>' : '') +
          '</div>' +
        '</div>' +
        '<div class="footer-col">' +
          '<h4>Collection</h4>' +
          cats.map(function(c){ return '<a href="#/collection/' + encodeURIComponent(c.slug) + '">' + escapeHtml(c.name) + '</a>'; }).join('') +
          '<a href="#/story">Story</a>' +
        '</div>' +
        '<div class="footer-col">' +
          '<h4>Connect</h4>' +
          (s.instagram ? '<a href="' + escapeAttr(s.instagram) + '" target="_blank" rel="noopener">Instagram</a>' : '') +
          (em ? '<a href="' + escapeAttr(em) + '">' + escapeHtml(s.email) + '</a>' : '') +
          (wa ? '<a href="' + escapeAttr(wa) + '" target="_blank" rel="noopener">WhatsApp</a>' : '') +
        '</div>' +
      '</div>';
  return '' +
  '<footer class="site-footer' + (launchMode ? ' launch-mode' : '') + '">' +
    '<div class="container">' +
      (launchMode ? '' : footerGrid) +
      '<div class="footer-bottom">' +
        '<span>' + escapeHtml(s.footerNote) + '</span>' +
        '<span>&copy; ' + new Date().getFullYear() + '</span>' +
      '</div>' +
    '</div>' +
  '</footer>';
}

/* ===========================================================
   6. PIECE CARD / PLAQUE
   =========================================================== */
function pieceCardHTML(p){
  var img = firstImage(p);
  var cat = findCategory(p.categoryId);
  var meta = (p.origin ? p.origin.toUpperCase() : '') + (p.h ? ' \u00b7 H ' + p.h + ' CM' : '');
  return '' +
  '<a class="piece-card reveal" href="#/piece/' + encodeURIComponent(p.ref) + '">' +
    '<div class="frame' + (p.status === 'sold' ? ' is-sold' : '') + '">' +
      (img ? '<img src="' + img.data + '" alt="' + escapeAttr(img.alt || p.title) + '" loading="lazy">' : '') +
    '</div>' +
    '<div class="plaque">' +
      '<div class="plaque-title-row">' +
        '<span class="plaque-title">' + escapeHtml(p.title) + (p.era ? '<span class="era">, ' + escapeHtml(p.era) + '</span>' : '') + '</span>' +
      '</div>' +
      '<div class="plaque-meta">' +
        '<span>' + escapeHtml(meta) + '</span>' +
        '<span class="plaque-ref">' + escapeHtml(p.ref) + '</span>' +
      '</div>' +
    '</div>' +
  '</a>';
}

function pieceGridHTML(products){
  if(!products.length){
    return '<div class="empty-state"><p class="muted">No pieces in this collection yet.</p></div>';
  }
  return '<div class="piece-grid">' + products.map(pieceCardHTML).join('') + '</div>';
}

/* ===========================================================
   7. HOME
   =========================================================== */
function homeHTML(){
  var s = DATA.settings;
  var launchMode = !!s.launchMode;
  var allProducts = DATA.products.slice().sort(byOrder);
  var featured = allProducts.filter(function(p){ return p.featured; });
  if(!featured.length){
    featured = allProducts.slice().sort(function(a,b){ return (b.order||0) - (a.order||0); }).slice(0,6);
  }
  var heroProduct = featured[0] || allProducts[0];
  var heroImg = heroProduct ? coverSrc(heroProduct) : '';
  var cats = sortedCategories();

  var heroEyebrowText = (launchMode && s.launchEyebrow) ? s.launchEyebrow : s.tagline;
  var heroCtaHTML = launchMode
    ? '<a class="textlink" style="animation-delay:180ms;" href="#" data-action="open-contact">' + escapeHtml(s.launchCtaLabel || 'Enquiries') + ' \u2192</a>'
    : '<a class="textlink" style="animation-delay:180ms;" href="#/collection/' + (cats[0] ? encodeURIComponent(cats[0].slug) : '') + '">View the collection \u2192</a>';

  var heroSection = '' +
  '<section class="hero">' +
    '<div class="container">' +
      '<div class="hero-grid load-seq">' +
        '<div class="hero-copy">' +
          '<div class="eyebrow" style="animation-delay:0ms;">' + escapeHtml(heroEyebrowText) + '</div>' +
          '<h1 style="animation-delay:60ms;">' + escapeHtml(s.heroTitle) + '</h1>' +
          '<p class="hero-sub" style="animation-delay:120ms;">' + escapeHtml(s.heroSubtitle) + '</p>' +
          heroCtaHTML +
        '</div>' +
        '<div class="hero-image" style="animation-delay:120ms;">' +
          ((s.gallery && s.gallery.length) ? heroCarouselHTML(s.gallery) :
            ('<div class="frame">' + (heroImg ? '<img src="' + heroImg + '" alt="' + escapeAttr(heroProduct.title) + '">' : '') + '</div>')) +
        '</div>' +
      '</div>' +
    '</div>' +
  '</section>';

  var arrivalsSection = '' +
  '<section class="section">' +
    '<div class="container">' +
      '<div class="section-head">' +
        '<div><div class="eyebrow">New arrivals</div><h2>Recently catalogued</h2></div>' +
      '</div>' +
      pieceGridHTML(featured.slice(0,6)) +
    '</div>' +
  '</section>';

  var catSection = '' +
  '<section class="section">' +
    '<div class="container">' +
      '<div class="section-head"><div><div class="eyebrow">Browse</div><h2>The collection</h2></div></div>' +
      cats.map(function(c){
        var prods = productsInCategory(c.id);
        var img = prods[0] ? coverSrc(prods[0]) : '';
        return '' +
        '<div class="cat-row reveal">' +
          '<div class="frame">' + (img ? '<img src="' + img + '" alt="' + escapeAttr(c.name) + '">' : '') + '</div>' +
          '<div class="cat-row-body">' +
            '<div class="eyebrow">' + (c.type === 'concept' ? 'Edit' : 'Category') + '</div>' +
            '<h3>' + escapeHtml(c.name) + '</h3>' +
            (c.type === 'concept' && c.intro ? '<p>' + escapeHtml(c.intro) + '</p>' : '<p>' + prods.length + ' piece' + (prods.length===1?'':'s') + ' catalogued</p>') +
          '</div>' +
          '<div class="cat-row-cta"><a class="textlink" href="#/collection/' + encodeURIComponent(c.slug) + '">View \u2192</a></div>' +
        '</div>';
      }).join('') +
    '</div>' +
  '</section>';

  var storySection = '' +
  '<section class="section">' +
    '<div class="container">' +
      '<div class="story-col reveal" style="text-align:center;">' +
        '<div class="eyebrow">' + escapeHtml(s.tagline) + '</div>' +
        '<h2 style="margin:14px 0 18px;">' + escapeHtml(s.storyTitle) + '</h2>' +
        '<p class="lede muted">' + escapeHtml((s.storyParagraphs[0]||'').slice(0,220)) + '\u2026</p>' +
        '<a class="textlink" href="#/story" style="margin-top:18px;display:inline-block;">Read the full story \u2192</a>' +
      '</div>' +
    '</div>' +
  '</section>';

  if(launchMode){
    return heroSection + storySection;
  }
  return heroSection + arrivalsSection + catSection + storySection;
}

/* ===========================================================
   8. COLLECTION
   =========================================================== */
function collectionHTML(cat){
  var prods = productsInCategory(cat.id);
  return '' +
  '<section class="section" style="padding-top:var(--sp-5);">' +
    '<div class="container">' +
      '<div class="load-seq" style="margin-bottom:var(--sp-4);">' +
        '<div class="eyebrow" style="animation-delay:0ms;">' + (cat.type === 'concept' ? 'Edit' : 'Collection') + '</div>' +
        '<h1 style="animation-delay:60ms;margin-top:8px;">' + escapeHtml(cat.name) + '</h1>' +
        (cat.type === 'concept' && cat.intro ? '<p class="lede muted" style="animation-delay:120ms;max-width:60ch;margin-top:14px;">' + escapeHtml(cat.intro) + '</p>' : '') +
      '</div>' +
      pieceGridHTML(prods) +
    '</div>' +
  '</section>';
}

/* ===========================================================
   9. PRODUCT PAGE
   =========================================================== */
function productHTML(p){
  var cat = findCategory(p.categoryId);
  var images = (p.images && p.images.length) ? p.images : [];
  var main = images[0];
  var wa = enquiryHref('whatsapp', p);
  var em = enquiryHref('email', p);
  var isSold = p.status === 'sold';
  var isReserved = p.status === 'reserved';

  var statusHTML = '';
  if(p.status === 'available'){
    statusHTML = '<span class="status-flag status-available"><span class="status-dot"></span>Available</span>';
  } else if(p.status === 'reserved'){
    statusHTML = '<span class="status-flag status-reserved">Reserved</span>';
  } else if(p.status === 'sold'){
    statusHTML = '<span class="status-flag status-sold">Sold</span>';
  }

  var thumbs = images.length > 1 ? (
    '<div class="gallery-thumbs">' +
    images.map(function(img, i){
      return '<button type="button" data-action="gallery-thumb" data-index="' + i + '" class="' + (i===0?'active':'') + '"><img src="' + img.data + '" alt="' + escapeAttr(img.alt||'') + '"></button>';
    }).join('') +
    '</div>'
  ) : '';

  var ctaLabel = (isSold) ? 'Enquire about similar pieces' : 'Enquire';

  return '' +
  '<article class="section" style="padding-top:var(--sp-5);">' +
    '<div class="container">' +
      '<div class="product-layout load-seq">' +
        '<div class="gallery" style="animation-delay:0ms;">' +
          '<div class="gallery-main" data-action="gallery-open">' +
            '<div class="frame' + (isSold ? ' is-sold' : '') + '" id="gallery-main-frame">' +
              (main ? '<img src="' + main.data + '" alt="' + escapeAttr(main.alt || p.title) + '" id="gallery-main-img">' : '') +
            '</div>' +
          '</div>' +
          thumbs +
        '</div>' +
        '<div class="product-info" style="animation-delay:80ms;">' +
          '<div class="eyebrow">' + (cat ? escapeHtml(cat.name) : '') + '</div>' +
          '<div class="plaque-title">' + escapeHtml(p.title) + (p.era ? '<span class="era">, ' + escapeHtml(p.era) + '</span>' : '') + '</div>' +
          '<div class="plaque-meta"><span>' + escapeHtml((p.origin||'').toUpperCase()) + (p.h ? ' \u00b7 H ' + p.h + ' CM' : '') + '</span><span class="plaque-ref">' + escapeHtml(p.ref) + '</span></div>' +
          '<div class="product-status-row">' + statusHTML + '</div>' +
          (p.story ? '<p class="product-story">' + escapeHtml(p.story) + '</p>' : '') +
          '<div class="provenance">' +
            '<div class="provenance-row"><span class="k">Origin</span><span class="v">' + escapeHtml(p.origin||'\u2014') + '</span></div>' +
            '<div class="provenance-row"><span class="k">Circa</span><span class="v">' + escapeHtml(p.era||'\u2014') + '</span></div>' +
            '<div class="provenance-row"><span class="k">Dimensions</span><span class="v">' + formatDims(p) + ' (' + cmToIn(p.h) + '\u00d7' + cmToIn(p.w) + '\u00d7' + cmToIn(p.d) + ' in)</span></div>' +
          '</div>' +
          '<div class="enquiry-context">' + (isSold ? 'This piece has found its home.' : 'Enquiries are answered directly by the atelier.') + ' &middot; Ref ' + escapeHtml(p.ref) + '</div>' +
          '<div class="btn-row">' +
            (wa ? '<a class="btn" href="' + escapeAttr(wa) + '" target="_blank" rel="noopener">' + ctaLabel + ' \u2014 WhatsApp</a>' : '') +
            (em ? '<a class="btn btn-quiet" href="' + escapeAttr(em) + '">' + ctaLabel + ' \u2014 Email</a>' : '') +
          '</div>' +
        '</div>' +
      '</div>' +
    '</div>' +
  '</article>';
}

/* ===========================================================
   10. STORY PAGE
   =========================================================== */
function storyHTML(){
  var s = DATA.settings;
  return '' +
  '<article class="section" style="padding-top:var(--sp-5);">' +
    '<div class="container">' +
      '<div class="story-col load-seq">' +
        '<div class="eyebrow" style="animation-delay:0ms;text-align:center;">' + escapeHtml(s.tagline) + '</div>' +
        '<h1 style="animation-delay:60ms;text-align:center;margin:14px 0 var(--sp-4);">' + escapeHtml(s.storyTitle) + '</h1>' +
        s.storyParagraphs.map(function(par, i){
          return '<p style="animation-delay:' + (100 + i*40) + 'ms;">' + escapeHtml(par) + '</p>';
        }).join('') +
      '</div>' +
    '</div>' +
  '</article>';
}

function notFoundHTML(){
  return '' +
  '<div class="empty-state container">' +
    '<div class="eyebrow">Not found</div>' +
    '<h2 style="margin:14px 0;">This page has been de-accessioned</h2>' +
    '<a class="textlink" href="#/">Return home \u2192</a>' +
  '</div>';
}

/* ===========================================================
   11. POST-RENDER: reveals + gallery state
   =========================================================== */
var _observer = null;
function afterPublicRender(){
  setupRevealObserver();
  initHeroCarousel();
  document.body.style.overflow = (STATE.mobileMenuOpen || STATE.contactOpen || STATE.lightbox) ? 'hidden' : '';
}

function setupRevealObserver(){
  var items = qsa('.reveal');
  if(!items.length) return;
  var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if(reduce || typeof IntersectionObserver === 'undefined'){
    items.forEach(function(el){ el.classList.add('is-visible'); });
    return;
  }
  if(_observer) _observer.disconnect();
  _observer = new IntersectionObserver(function(entries){
    entries.forEach(function(entry){
      if(entry.isIntersecting){
        entry.target.classList.add('is-visible');
        _observer.unobserve(entry.target);
      }
    });
  }, { threshold: 0.12 });
  items.forEach(function(el){ _observer.observe(el); });
}

/* ===========================================================
   12. GALLERY / LIGHTBOX helpers
   =========================================================== */
function currentProductFromRoute(){
  var parts = parseHash();
  if(parts[0] === 'piece' && parts[1]) return productByRef(decodeURIComponent(parts[1]));
  return null;
}

function setGalleryIndex(i){
  var p = currentProductFromRoute();
  if(!p || !p.images.length) return;
  var idx = ((i % p.images.length) + p.images.length) % p.images.length;
  var img = p.images[idx];
  var mainImg = document.getElementById('gallery-main-img');
  if(mainImg){ mainImg.src = img.data; mainImg.alt = img.alt || p.title; }
  qsa('.gallery-thumbs button').forEach(function(btn, i2){
    btn.classList.toggle('active', i2 === idx);
  });
  setGalleryIndex._current = idx;
}

function openLightboxForProduct(startIndex){
  var p = currentProductFromRoute();
  if(!p || !p.images.length) return;
  STATE.lightbox = { images: p.images, index: startIndex || 0, title: p.title };
  renderLightboxOnly();
}
function renderLightboxOnly(){
  var existing = qs('.lightbox');
  var html = lightboxHTML();
  if(existing) existing.outerHTML = html;
  document.body.style.overflow = STATE.lightbox ? 'hidden' : (STATE.mobileMenuOpen || STATE.contactOpen ? 'hidden' : '');
}
function closeLightbox(){
  STATE.lightbox = null;
  renderLightboxOnly();
}
function lightboxStep(dir){
  if(!STATE.lightbox) return;
  var lb = STATE.lightbox;
  lb.index = ((lb.index + dir) % lb.images.length + lb.images.length) % lb.images.length;
  renderLightboxOnly();
}

/* ===========================================================
   12B. HERO CAROUSEL — A/B double-buffer, module-level state
   =========================================================== */
var HERO = {
  images: [], index: 0, activeLayer: 'a', timer: null, intervalMs: 5200,
  reduceMotion: false, paused: { hover:false, focus:false, hidden:false },
  swipeStartX: null, rootEl: null, _listeners: null
};

function pad2(n){ return String(n).padStart(2,'0'); }
function heroSrcset(entry){ return gallerySrcUrl(entry.srcSmall) + ' 800w, ' + gallerySrcUrl(entry.src) + ' 1600w'; }

function heroCarouselHTML(gallery){
  var first = gallery[0], w = first.w || 1600, h = first.h || 2000;
  var sizes = '(min-width:900px) 46vw, 92vw';
  var frame = '' +
  '<div class="frame hero-carousel" role="group" aria-roledescription="carousel" aria-label="Gallery" id="hero-carousel" tabindex="0">' +
    '<img class="hc-layer hc-active" id="hc-layer-a" src="' + escapeAttr(gallerySrcUrl(first.srcSmall)) + '" srcset="' + escapeAttr(heroSrcset(first)) + '" sizes="' + sizes + '" width="' + w + '" height="' + h + '" alt="' + escapeAttr(first.alt || '') + '" fetchpriority="high" decoding="async">' +
    '<img class="hc-layer" id="hc-layer-b" alt="" sizes="' + sizes + '" width="' + w + '" height="' + h + '">' +
  '</div>';
  if(gallery.length < 2) return frame;
  return frame +
  '<div class="hc-controls">' +
    '<button type="button" class="hc-btn" data-action="hero-prev" aria-label="Previous photo">‹</button>' +
    '<span class="hc-counter" id="hc-counter">' + pad2(1) + ' / ' + pad2(gallery.length) + '</span>' +
    '<button type="button" class="hc-btn" data-action="hero-next" aria-label="Next photo">›</button>' +
  '</div>';
}

function initHeroCarousel(){
  if(parseHash().length !== 0) return;
  var gallery = DATA.settings.gallery;
  if(!gallery || !gallery.length) return;
  var root = document.getElementById('hero-carousel');
  if(!root) return;
  HERO.images = gallery; HERO.index = 0; HERO.activeLayer = 'a'; HERO.rootEl = root;
  HERO.reduceMotion = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
  HERO.intervalMs = Math.max(1500, Number(DATA.settings.galleryIntervalMs) || 5200);
  HERO.paused = { hover:false, focus:false, hidden:false };
  if(gallery.length > 1){
    preloadNextHeroImage();
    bindHeroCarouselEvents(root);
    startHeroTimer();
  }
}

function teardownHeroCarousel(){
  if(HERO.timer){ clearInterval(HERO.timer); HERO.timer = null; }
  if(HERO._listeners) HERO._listeners.forEach(function(p){ p[0].removeEventListener(p[1], p[2]); });
  HERO._listeners = null; HERO.rootEl = null; HERO.images = []; HERO.swipeStartX = null;
}

function bindHeroCarouselEvents(root){
  var pairs = [
    [root,'pointerenter',function(){ HERO.paused.hover = true; }],
    [root,'pointerleave',function(){ HERO.paused.hover = false; }],
    [root,'focusin',function(){ HERO.paused.focus = true; }],
    [root,'focusout',function(){ HERO.paused.focus = false; }],
    [root,'keydown',function(e){
      if(e.key === 'ArrowLeft'){ e.preventDefault(); heroStep(-1, true); }
      else if(e.key === 'ArrowRight'){ e.preventDefault(); heroStep(1, true); }
    }],
    [root,'touchstart',function(e){ HERO.swipeStartX = e.touches[0].clientX; }, {passive:true}],
    [root,'touchend',function(e){
      if(HERO.swipeStartX === null) return;
      var dx = e.changedTouches[0].clientX - HERO.swipeStartX;
      if(Math.abs(dx) > 40) heroStep(dx > 0 ? -1 : 1, true);
      HERO.swipeStartX = null;
    }, {passive:true}],
    [document,'visibilitychange',function(){ HERO.paused.hidden = document.visibilityState === 'hidden'; }]
  ];
  pairs.forEach(function(p){ p[0].addEventListener(p[1], p[2], p[3]); });
  HERO._listeners = pairs;
}

function startHeroTimer(){
  if(HERO.timer) clearInterval(HERO.timer);
  HERO.timer = null;
  if(HERO.reduceMotion) return;
  HERO.timer = setInterval(function(){
    if(HERO.paused.hover || HERO.paused.focus || HERO.paused.hidden) return;
    heroStep(1, false);
  }, HERO.intervalMs);
}

function heroStep(dir, manual){
  if(!HERO.images.length) return;
  var n = HERO.images.length;
  HERO.index = ((HERO.index + dir) % n + n) % n;
  advanceHeroSlide();
  if(manual) startHeroTimer();
}

function advanceHeroSlide(){
  var entry = HERO.images[HERO.index];
  if(!entry) return;
  var incomingLayer = HERO.activeLayer === 'a' ? 'b' : 'a';
  var incomingEl = document.getElementById('hc-layer-' + incomingLayer);
  var outgoingEl = document.getElementById('hc-layer-' + HERO.activeLayer);
  if(!incomingEl) return;

  function commitSwap(){
    incomingEl.classList.add('hc-active');
    if(outgoingEl) outgoingEl.classList.remove('hc-active');
    HERO.activeLayer = incomingLayer;
    updateHeroCounter();
    preloadNextHeroImage();
  }

  incomingEl.alt = entry.alt || '';
  incomingEl.srcset = heroSrcset(entry);
  incomingEl.src = gallerySrcUrl(entry.srcSmall);

  if(HERO.reduceMotion){ commitSwap(); return; }
  if(typeof incomingEl.decode === 'function'){ incomingEl.decode().then(commitSwap, commitSwap); }
  else { incomingEl.addEventListener('load', commitSwap, { once:true }); }
}

function updateHeroCounter(){
  var el = document.getElementById('hc-counter');
  if(el) el.textContent = pad2(HERO.index+1) + ' / ' + pad2(HERO.images.length);
}

function preloadNextHeroImage(){
  if(!HERO.images.length) return;
  var nextEntry = HERO.images[(HERO.index + 1) % HERO.images.length];
  if(!nextEntry) return;
  var img = new Image();
  img.src = gallerySrcUrl(nextEntry.srcSmall);
}

/* ===========================================================
   13. EVENT DELEGATION — public actions
   =========================================================== */
document.addEventListener('click', function(e){
  // any in-app link click is a forward navigation, unless go-home-top below overrides it
  if(e.target.closest('a[href^="#/"]')) NAV_INTENT = 'push';

  var t = e.target.closest('[data-action]');
  if(!t) return;
  var action = t.getAttribute('data-action');

  switch(action){
    case 'go-home-top':
      e.preventDefault();
      if(parseHash().length){
        NAV_INTENT = 'top';
        navigate('#/');
      } else {
        window.scrollTo(0,0);
      }
      break;
    case 'copy-email':
      copyEmailToClipboard();
      break;
    case 'toggle-mobile-menu':
      STATE.mobileMenuOpen = !STATE.mobileMenuOpen;
      rerenderChrome();
      break;
    case 'close-mobile-menu':
      STATE.mobileMenuOpen = false;
      rerenderChrome();
      break;
    case 'toggle-more':
      STATE.moreOpen = !STATE.moreOpen;
      rerenderChrome();
      break;
    case 'open-contact':
    case 'open-contact-mobile':
      e.preventDefault(); // both triggers are href="#" anchors; without this a hashchange
                           // fires a full router() re-render moments after opening and wipes
                           // the focus we just set inside the modal
      STATE.contactOpen = true;
      STATE.mobileMenuOpen = false;
      rerenderChrome();
      break;
    case 'close-contact':
      STATE.contactOpen = false;
      rerenderChrome();
      break;
    case 'close-contact-backdrop':
      if(e.target === t){ STATE.contactOpen = false; rerenderChrome(); }
      break;
    case 'gallery-thumb':
      setGalleryIndex(parseInt(t.getAttribute('data-index'),10));
      break;
    case 'gallery-open':
      openLightboxForProduct(setGalleryIndex._current || 0);
      break;
    case 'lightbox-close':
      closeLightbox();
      break;
    case 'lightbox-prev':
      lightboxStep(-1);
      break;
    case 'lightbox-next':
      lightboxStep(1);
      break;
    case 'hero-prev':
      heroStep(-1, true);
      break;
    case 'hero-next':
      heroStep(1, true);
      break;
  }

  // click-away for "more" dropdown
  if(action !== 'toggle-more' && STATE.moreOpen){
    STATE.moreOpen = false;
  }
});

// click outside more-menu closes it
document.addEventListener('click', function(e){
  if(STATE.moreOpen && !e.target.closest('.more-wrap')){
    STATE.moreOpen = false;
    var wrap = qs('.more-wrap');
    if(wrap) wrap.classList.remove('open');
  }
});

document.addEventListener('keydown', function(e){
  if(STATE.lightbox){
    if(e.key === 'Escape') closeLightbox();
    if(e.key === 'ArrowLeft') lightboxStep(-1);
    if(e.key === 'ArrowRight') lightboxStep(1);
  } else if(STATE.mobileMenuOpen && e.key === 'Escape'){
    STATE.mobileMenuOpen = false; rerenderChrome();
  } else if(STATE.contactOpen && e.key === 'Escape'){
    STATE.contactOpen = false; rerenderChrome();
  }
});

// touch swipe for lightbox
var _touchStartX = null;
document.addEventListener('touchstart', function(e){
  if(!STATE.lightbox) return;
  _touchStartX = e.touches[0].clientX;
}, { passive: true });
document.addEventListener('touchend', function(e){
  if(!STATE.lightbox || _touchStartX === null) return;
  var dx = e.changedTouches[0].clientX - _touchStartX;
  if(Math.abs(dx) > 40){
    lightboxStep(dx > 0 ? -1 : 1);
  }
  _touchStartX = null;
}, { passive: true });

// re-render only the "chrome" pieces that toggle without a route change
function rerenderChrome(){
  var headerEl = qs('.site-header');
  if(headerEl) headerEl.outerHTML = headerHTML();
  var menuEl = qs('.mobile-menu');
  if(menuEl) menuEl.outerHTML = mobileMenuHTML();
  var contactEl = qs('.modal-backdrop');
  if(contactEl) contactEl.outerHTML = contactModalHTML();
  document.body.style.overflow = (STATE.mobileMenuOpen || STATE.contactOpen || STATE.lightbox) ? 'hidden' : '';
}

window.addEventListener('beforeunload', function(e){
  if(STATE.adminDirty){
    e.preventDefault();
    e.returnValue = '';
    return '';
  }
});

/* ===========================================================
   14. ADMIN — PATH HELPERS
   =========================================================== */
function dateStamp(){
  var d = new Date();
  return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0');
}
function bytesToMB(bytes){ return (bytes / (1024*1024)).toFixed(2); }
function currentDataSizeBytes(){
  try{ return new Blob([JSON.stringify(DATA)]).size; }
  catch(e){ return JSON.stringify(DATA).length; }
}

function getByPath(root, path){
  if(!path) return root;
  var parts = path.split('.');
  var obj = root;
  for(var i=0;i<parts.length;i++){
    if(obj === undefined || obj === null) return undefined;
    obj = obj[parts[i]];
  }
  return obj;
}
function setByPath(root, path, value){
  var parts = path.split('.');
  var obj = root;
  for(var i=0;i<parts.length-1;i++){ obj = obj[parts[i]]; }
  obj[parts[parts.length-1]] = value;
}
function resolveRootName(name){ return name === 'DATA' ? DATA : (name === 'ADMIN' ? ADMIN : null); }
function bindGet(fullPath){
  var idx = fullPath.indexOf('.');
  var rootName = fullPath.slice(0, idx);
  var rest = fullPath.slice(idx+1);
  return getByPath(resolveRootName(rootName), rest);
}
function bindSet(fullPath, value){
  var idx = fullPath.indexOf('.');
  var rootName = fullPath.slice(0, idx);
  var rest = fullPath.slice(idx+1);
  setByPath(resolveRootName(rootName), rest, value);
}

/* ===========================================================
   15. ADMIN — RENDER ROOT
   =========================================================== */
function renderAdmin(){
  var root = document.getElementById('admin-root');
  if(!root) return;
  root.innerHTML = adminOverlayHTML();
  applyMetaTags();
}

function adminOverlayHTML(){
  return '' +
  '<div class="admin-overlay">' +
    adminNoticeHTML() +
    adminTopbarHTML() +
    adminTabsHTML() +
    '<div class="admin-body">' + adminTabBodyHTML() + '</div>' +
    confirmModalHTML() +
    publishModalHTML() +
    toastHTML() +
  '</div>';
}

function adminNoticeHTML(){
  return '<div class="admin-notice">Bu paneldeki değişiklikler yalnızca \u2018Yayınla\u2019 ile indirdiğin dosyayı Netlify\u2019a yüklediğinde canlıya geçer.</div>';
}

function adminTopbarHTML(){
  return '' +
  '<div class="admin-topbar">' +
    '<h1>Dadia Atölye \u2014 Yönetim Paneli</h1>' +
    '<button type="button" class="admin-exit" data-aa="admin-exit">\u2190 Siteye dön</button>' +
  '</div>';
}

var ADMIN_TABS = [
  { id:'urunler', label:'Ürünler' },
  { id:'koleksiyonlar', label:'Koleksiyonlar' },
  { id:'galeri', label:'Galeri' },
  { id:'marka', label:'Marka & Hikaye' },
  { id:'iletisim', label:'İletişim & Ayarlar' },
  { id:'yayinla', label:'Yayınla & Yedek' }
];

function adminTabsHTML(){
  return '<div class="admin-tabs" role="tablist">' +
    ADMIN_TABS.map(function(t){
      return '<button type="button" role="tab" class="admin-tab' + (ADMIN.tab === t.id ? ' active' : '') + '" data-aa="admin-tab" data-tab="' + t.id + '">' + t.label + '</button>';
    }).join('') +
  '</div>';
}

function adminTabBodyHTML(){
  switch(ADMIN.tab){
    case 'urunler': return ADMIN.productView === 'form' ? adminProductFormHTML() : adminProductListHTML();
    case 'koleksiyonlar': return ADMIN.categoryView === 'form' ? adminCategoryFormHTML() : adminCategoryListHTML();
    case 'galeri': return adminGaleriTabHTML();
    case 'marka': return adminBrandTabHTML();
    case 'iletisim': return adminSettingsTabHTML();
    case 'yayinla': return adminPublishTabHTML();
    default: return '';
  }
}

/* ===========================================================
   16. ADMIN — ÜRÜNLER (products)
   =========================================================== */
function adminProductListHTML(){
  var prods = DATA.products.slice().sort(byOrder);
  return '' +
  '<div class="admin-section-title">Ürünler</div>' +
  '<div class="admin-section-sub">Katalogdaki tüm parçalar. Sıralama, ana sayfa ve koleksiyon sayfalarındaki görünüm sırasını belirler.</div>' +
  '<button type="button" class="admin-btn primary" data-aa="product-new">+ Yeni Ürün</button>' +
  '<div class="table-scroll">' +
  '<table class="admin-table">' +
    '<thead><tr><th></th><th>Başlık</th><th>Ref</th><th>Koleksiyon</th><th>Durum</th><th></th></tr></thead>' +
    '<tbody>' +
    (prods.length ? prods.map(function(p, i){
      var cat = findCategory(p.categoryId);
      var img = coverSrc(p);
      return '' +
      '<tr>' +
        '<td>' + (img ? '<img class="admin-thumb" src="' + img + '" alt="">' : '') + '</td>' +
        '<td>' + escapeHtml(p.title) + (p.sample ? '<span class="sample-tag">örnek ürün</span>' : '') + '</td>' +
        '<td>' + escapeHtml(p.ref) + '</td>' +
        '<td>' + (cat ? escapeHtml(cat.name) : '\u2014') + '</td>' +
        '<td><span class="status-pill">' + statusLabel(p.status) + '</span></td>' +
        '<td><div class="admin-row-actions">' +
          '<button type="button" class="admin-btn small" data-aa="product-move-up" data-id="' + p.id + '"' + (i===0?' disabled':'') + '>\u2191</button>' +
          '<button type="button" class="admin-btn small" data-aa="product-move-down" data-id="' + p.id + '"' + (i===prods.length-1?' disabled':'') + '>\u2193</button>' +
          '<button type="button" class="admin-btn small" data-aa="product-edit" data-id="' + p.id + '">Düzenle</button>' +
          '<button type="button" class="admin-btn small" data-aa="product-duplicate" data-id="' + p.id + '">Çoğalt</button>' +
          '<button type="button" class="admin-btn small danger" data-aa="product-delete" data-id="' + p.id + '">Sil</button>' +
        '</div></td>' +
      '</tr>';
    }).join('') : '<tr><td colspan="6" class="muted" style="padding:20px 10px;">Henüz ürün yok.</td></tr>') +
    '</tbody>' +
  '</table>' +
  '</div>';
}

function adminProductFormHTML(){
  var f = ADMIN.productForm;
  if(!f) return '';
  var cats = sortedCategories();
  var title = f._isNew ? 'Yeni Ürün' : ('Düzenle \u2014 ' + f.ref);

  return '' +
  '<div class="admin-section-title">' + escapeHtml(title) + '</div>' +
  (f.sample ? '<div class="admin-section-sub">Bu bir örnek üründür \u2014 gerçek fotoğraf ve bilgilerle güncelleyin.</div>' : '') +
  '<form class="admin-card" data-aa-form="product-form" onsubmit="return false;">' +

    '<div class="field-row">' +
      '<div class="field"><label>Başlık</label><input type="text" data-bind="ADMIN.productForm.title" value="' + escapeAttr(f.title) + '" placeholder="ör. Enez Jar"></div>' +
      '<div class="field"><label>Referans No</label><input type="text" data-bind="ADMIN.productForm.ref" value="' + escapeAttr(f.ref) + '"></div>' +
    '</div>' +

    '<div class="field-row">' +
      '<div class="field"><label>Koleksiyon</label><select data-bind="ADMIN.productForm.categoryId">' +
        cats.map(function(c){ return '<option value="' + c.id + '"' + (f.categoryId===c.id?' selected':'') + '>' + escapeHtml(c.name) + '</option>'; }).join('') +
      '</select></div>' +
      '<div class="field"><label>Durum</label><select data-bind="ADMIN.productForm.status">' +
        '<option value="available"' + (f.status==='available'?' selected':'') + '>Available</option>' +
        '<option value="reserved"' + (f.status==='reserved'?' selected':'') + '>Reserved</option>' +
        '<option value="sold"' + (f.status==='sold'?' selected':'') + '>Sold</option>' +
      '</select></div>' +
    '</div>' +

    '<div class="field-row">' +
      '<div class="field"><label>Menşe (Origin)</label><input type="text" data-bind="ADMIN.productForm.origin" value="' + escapeAttr(f.origin) + '" placeholder="ör. Aegean Anatolia"></div>' +
      '<div class="field"><label>Dönem (Era)</label><input type="text" data-bind="ADMIN.productForm.era" value="' + escapeAttr(f.era) + '" placeholder="ör. c. 1930"></div>' +
    '</div>' +

    '<div class="field-row">' +
      '<div class="field" style="max-width:120px;"><label>Y (cm)</label><input type="number" min="0" data-bind="ADMIN.productForm.h" value="' + escapeAttr(f.h) + '"></div>' +
      '<div class="field" style="max-width:120px;"><label>G (cm)</label><input type="number" min="0" data-bind="ADMIN.productForm.w" value="' + escapeAttr(f.w) + '"></div>' +
      '<div class="field" style="max-width:120px;"><label>D (cm)</label><input type="number" min="0" data-bind="ADMIN.productForm.d" value="' + escapeAttr(f.d) + '"></div>' +
    '</div>' +

    '<div class="field full"><label>Hikaye / Açıklama</label><textarea data-bind="ADMIN.productForm.story" rows="5">' + escapeHtml(f.story) + '</textarea></div>' +

    '<div class="field"><label style="display:flex;align-items:center;gap:8px;"><input type="checkbox" style="width:16px;height:16px;" data-bind="ADMIN.productForm.featured"' + (f.featured?' checked':'') + '> Öne çıkan (yeni gelenler şeridinde göster)</label></div>' +

    '<div class="field full">' +
      '<label>Fotoğraflar (en fazla 4)</label>' +
      '<input type="file" accept="image/*" multiple data-aa-change="product-images-upload"' + (f.images.length>=4 ? ' disabled' : '') + '>' +
      '<div class="hint">' + f.images.length + '/4 fotoğraf yüklendi. Yüklerken en uzun kenar 1600px\u2019e küçültülür ve JPEG (kalite 0.8) olarak sıkıştırılır (PNG saydamlık içeriyorsa PNG kalır).</div>' +
      '<div class="admin-imgs">' +
        f.images.map(function(img, i){
          var sizeTxt = img._size ? bytesToMB(img._size) + ' MB' : '';
          return '' +
          '<div class="admin-img-item">' +
            '<img src="' + img.data + '" alt="">' +
            (i===0 ? '<span class="cover-badge">Kapak</span>' : '') +
            '<div class="meta">' + sizeTxt + '</div>' +
            '<div class="row">' +
              (i>0 ? '<button type="button" data-aa="img-move-left" data-index="' + i + '">\u2190</button>' : '') +
              (i<f.images.length-1 ? '<button type="button" data-aa="img-move-right" data-index="' + i + '">\u2192</button>' : '') +
              (i!==0 ? '<button type="button" data-aa="img-set-cover" data-index="' + i + '">Kapak yap</button>' : '') +
              '<button type="button" data-aa="img-remove" data-index="' + i + '">Sil</button>' +
            '</div>' +
          '</div>';
        }).join('') +
      '</div>' +
    '</div>' +

    '<div class="btn-row" style="margin-top:var(--sp-3);">' +
      '<button type="button" class="admin-btn primary" data-aa="product-save">Kaydet</button>' +
      '<button type="button" class="admin-btn" data-aa="product-cancel">Vazgeç</button>' +
    '</div>' +
  '</form>';
}

/* ===========================================================
   17. ADMIN — KOLEKSİYONLAR (categories)
   =========================================================== */
function adminCategoryListHTML(){
  var cats = DATA.categories.slice().sort(byOrder);
  return '' +
  '<div class="admin-section-title">Koleksiyonlar</div>' +
  '<div class="admin-section-sub">Standart koleksiyonlar sade kategori sayfaları; konsept koleksiyonlar üstte bir tanıtım metniyle görünür.</div>' +
  '<button type="button" class="admin-btn primary" data-aa="category-new">+ Yeni Koleksiyon</button>' +
  '<div class="table-scroll">' +
  '<table class="admin-table">' +
    '<thead><tr><th>Ad</th><th>Slug</th><th>Tür</th><th>Ürün</th><th></th></tr></thead>' +
    '<tbody>' +
    (cats.length ? cats.map(function(c, i){
      var count = productsInCategory(c.id).length;
      return '' +
      '<tr>' +
        '<td>' + escapeHtml(c.name) + '</td>' +
        '<td class="muted">/collection/' + escapeHtml(c.slug) + '</td>' +
        '<td>' + (c.type === 'concept' ? 'Konsept' : 'Standart') + '</td>' +
        '<td>' + count + '</td>' +
        '<td><div class="admin-row-actions">' +
          '<button type="button" class="admin-btn small" data-aa="category-move-up" data-id="' + c.id + '"' + (i===0?' disabled':'') + '>\u2191</button>' +
          '<button type="button" class="admin-btn small" data-aa="category-move-down" data-id="' + c.id + '"' + (i===cats.length-1?' disabled':'') + '>\u2193</button>' +
          '<button type="button" class="admin-btn small" data-aa="category-edit" data-id="' + c.id + '">Düzenle</button>' +
          '<button type="button" class="admin-btn small danger" data-aa="category-delete" data-id="' + c.id + '">Sil</button>' +
        '</div></td>' +
      '</tr>';
    }).join('') : '<tr><td colspan="5" class="muted" style="padding:20px 10px;">Henüz koleksiyon yok.</td></tr>') +
    '</tbody>' +
  '</table>' +
  '</div>';
}

function adminCategoryFormHTML(){
  var f = ADMIN.categoryForm;
  if(!f) return '';
  var title = f._isNew ? 'Yeni Koleksiyon' : ('Düzenle \u2014 ' + f.name);
  return '' +
  '<div class="admin-section-title">' + escapeHtml(title) + '</div>' +
  '<form class="admin-card" onsubmit="return false;">' +
    '<div class="field"><label>Ad</label><input type="text" data-bind="ADMIN.categoryForm.name" value="' + escapeAttr(f.name) + '" placeholder="ör. Vessels & Jars"></div>' +
    '<div class="field"><label>Slug (URL)</label><input type="text" data-bind="ADMIN.categoryForm.slug" value="' + escapeAttr(f.slug) + '"><div class="hint">/collection/' + escapeHtml(f.slug||'') + '</div></div>' +
    '<div class="field"><label>Tür</label><select data-bind="ADMIN.categoryForm.type">' +
      '<option value="standard"' + (f.type==='standard'?' selected':'') + '>Standart</option>' +
      '<option value="concept"' + (f.type==='concept'?' selected':'') + '>Konsept</option>' +
    '</select></div>' +
    (f.type === 'concept' ? '<div class="field full"><label>Konsept Tanıtım Metni</label><textarea rows="3" data-bind="ADMIN.categoryForm.intro">' + escapeHtml(f.intro) + '</textarea></div>' : '') +
    '<div class="btn-row" style="margin-top:var(--sp-3);">' +
      '<button type="button" class="admin-btn primary" data-aa="category-save">Kaydet</button>' +
      '<button type="button" class="admin-btn" data-aa="category-cancel">Vazgeç</button>' +
    '</div>' +
  '</form>';
}

/* ===========================================================
   17B. ADMIN — GALERİ
   =========================================================== */
function adminGaleriTabHTML(){
  var s = DATA.settings, gallery = s.gallery || [], progress = ADMIN.galleryProgress, stats = galleryStats();

  var top = '' +
  '<div class="admin-section-title">Galeri</div>' +
  '<div class="admin-section-sub">Ana sayfadaki büyük görsel dönüşümlü olarak bu fotoğrafları gösterir. Sürükleyip bırakın veya dosya seçin — boyutlandırma otomatik yapılır.</div>' +
  '<div class="gallery-dropzone" data-aa-dropzone="gallery">' +
    '<div class="eyebrow">Fotoğraf ekle</div>' +
    '<p style="margin:10px 0;">Fotoğrafları buraya sürükleyip bırakın, ya da dosya seçin.</p>' +
    '<label class="admin-btn primary" style="display:inline-flex;">Dosya seç<input type="file" accept="image/*" multiple data-aa-change="gallery-files-upload" style="display:none;"></label>' +
    '<p class="hint">Fotoğraflar otomatik küçültülür; dikey (4:5) kadraj en iyi sonucu verir.</p>' +
  '</div>' +
  (progress ? '<div class="hint">' + progress.current + ' / ' + progress.total + ' işleniyor…</div>' : '') +
  (!GALLERY_IDB_AVAILABLE ? '<div class="hint warn">Bu tarayıcıda yerel depolama yok, bu yüzden fotoğraflar yalnızca bu sekme açıkken hafızada kalır. Kapatmadan önce mutlaka "Yayınla" ile indirin.</div>' : '') +
  '<div class="field" style="max-width:280px;">' +
    '<label>Otomatik geçiş süresi (milisaniye)</label>' +
    '<input type="number" min="1500" step="100" data-bind="DATA.settings.galleryIntervalMs" value="' + escapeAttr(s.galleryIntervalMs) + '">' +
    '<div class="hint">Fotoğrafların kaç milisaniyede bir otomatik değişeceğini belirler. Varsayılan: 5200.</div>' +
  '</div>' +
  '<div class="admin-section-sub" style="margin-top:var(--sp-3);">Alt metin, ekran okuyucuların okuduğu ve Google Görseller’in kullandığı açıklamadır — nesneyi sade bir dille tarif edin.</div>';

  if(!gallery.length){
    return top + '<div class="empty-state" style="padding:var(--sp-4) 0;"><p class="muted">Henüz galeri fotoğrafı yok. Yukarıdaki alandan ilk fotoğraflarınızı ekleyin.</p></div>';
  }

  var rows = gallery.map(function(g, i){
    return '' +
    '<tr>' +
      '<td><img class="admin-thumb" src="' + escapeAttr(gallerySrcUrl(g.srcSmall)) + '" alt=""></td>' +
      '<td class="muted">' + escapeHtml((g.src||'').split('/').pop()) + '</td>' +
      '<td><input type="text" data-bind="DATA.settings.gallery.' + i + '.alt" value="' + escapeAttr(g.alt) + '" placeholder="ör. Enez küpü, atölye avlusunda"></td>' +
      '<td><div class="admin-row-actions">' +
        '<button type="button" class="admin-btn small" data-aa="gallery-move-up" data-index="' + i + '"' + (i===0?' disabled':'') + '>↑</button>' +
        '<button type="button" class="admin-btn small" data-aa="gallery-move-down" data-index="' + i + '"' + (i===gallery.length-1?' disabled':'') + '>↓</button>' +
        '<button type="button" class="admin-btn small danger" data-aa="gallery-remove" data-index="' + i + '">Sil</button>' +
      '</div></td>' +
    '</tr>';
  }).join('');

  return top +
  '<div class="table-scroll"><table class="admin-table">' +
    '<thead><tr><th></th><th>Dosya</th><th>Alt metin</th><th></th></tr></thead>' +
    '<tbody>' + rows + '</tbody>' +
  '</table></div>' +
  '<div class="admin-section-sub" style="margin-top:10px;">' + gallery.length + ' fotoğraf · toplam ' + bytesToMB(stats.total) + ' MB</div>' +
  (stats.oversize.length ? '<div class="hint warn">Şu dosyalar 260 KB sınırını aşıyor, sayfa yavaş açılabilir: ' + escapeHtml(stats.oversize.join(', ')) + '</div>' : '');
}

/* ===========================================================
   18. ADMIN — MARKA & HİKAYE
   =========================================================== */
function adminBrandTabHTML(){
  var s = DATA.settings;
  return '' +
  '<div class="admin-section-title">Marka & Hikaye</div>' +
  '<div class="admin-section-sub">Marka adı, ana sayfa metinleri ve "Story" sayfası içeriği.</div>' +

  '<div class="field"><label>Marka Adı</label><input type="text" data-bind="DATA.settings.brandName" value="' + escapeAttr(s.brandName) + '"></div>' +
  '<div class="field"><label>Slogan</label><input type="text" data-bind="DATA.settings.tagline" value="' + escapeAttr(s.tagline) + '"></div>' +

  '<div class="field full">' +
    '<label>Logo (PNG, saydamlık korunur, azami 600px)</label>' +
    (s.logo ? '<div class="admin-imgs"><div class="admin-img-item"><img src="' + s.logo + '" alt="Logo" style="background:#eee;"><div class="row"><button type="button" data-aa="logo-remove">Kaldır</button></div></div></div>' : '') +
    '<input type="file" accept="image/png,image/jpeg,image/webp" data-aa-change="logo-upload" style="margin-top:8px;">' +
  '</div>' +

  '<div class="field-row">' +
    '<div class="field"><label>Logo yüksekliği (piksel)</label><input type="number" min="16" max="96" data-bind="DATA.settings.logoHeight" value="' + escapeAttr(s.logoHeight) + '"><div class="hint">16–96 piksel arası. Varsayılan: 34.</div></div>' +
    '<div class="field"><label>Logo gösterimi</label><select data-bind="DATA.settings.logoDisplay">' +
      '<option value="mark-and-name"' + (s.logoDisplay==='mark-and-name'?' selected':'') + '>Logo + marka adı</option>' +
      '<option value="mark-only"' + (s.logoDisplay==='mark-only'?' selected':'') + '>Sadece logo</option>' +
      '<option value="name-only"' + (s.logoDisplay==='name-only'?' selected':'') + '>Sadece marka adı</option>' +
    '</select></div>' +
  '</div>' +

  '<div class="field-row">' +
    '<div class="field"><label>Açılış sitesi öne çıkan metni</label><input type="text" data-bind="DATA.settings.launchEyebrow" value="' + escapeAttr(s.launchEyebrow) + '" placeholder="Opening soon"><div class="hint">Boş bırakılırsa slogan kullanılır.</div></div>' +
    '<div class="field"><label>Açılış sitesi buton metni</label><input type="text" data-bind="DATA.settings.launchCtaLabel" value="' + escapeAttr(s.launchCtaLabel) + '" placeholder="Enquiries"><div class="hint">Ana sayfadaki iletişim bağlantısının yazısı.</div></div>' +
  '</div>' +

  '<div class="field full"><label>Hero Başlık</label><input type="text" data-bind="DATA.settings.heroTitle" value="' + escapeAttr(s.heroTitle) + '"></div>' +
  '<div class="field full"><label>Hero Alt Başlık</label><input type="text" data-bind="DATA.settings.heroSubtitle" value="' + escapeAttr(s.heroSubtitle) + '"></div>' +

  '<div class="field full"><label>Story Başlığı</label><input type="text" data-bind="DATA.settings.storyTitle" value="' + escapeAttr(s.storyTitle) + '"></div>' +

  '<div class="field full">' +
    '<label>Story Paragrafları</label>' +
    s.storyParagraphs.map(function(par, i){
      return '' +
      '<div style="margin-bottom:10px;">' +
        '<textarea rows="3" data-bind="DATA.settings.storyParagraphs.' + i + '">' + escapeHtml(par) + '</textarea>' +
        '<div style="margin-top:4px;"><button type="button" class="admin-btn small danger" data-aa="story-para-remove" data-index="' + i + '"' + (s.storyParagraphs.length<=1?' disabled':'') + '>Bu paragrafı sil</button></div>' +
      '</div>';
    }).join('') +
    '<button type="button" class="admin-btn small" data-aa="story-para-add">+ Paragraf ekle</button>' +
  '</div>' +

  '<div class="field full"><label>Footer Notu</label><input type="text" data-bind="DATA.settings.footerNote" value="' + escapeAttr(s.footerNote) + '"></div>';
}

/* ===========================================================
   19. ADMIN — İLETİŞİM & AYARLAR
   =========================================================== */
function adminSettingsTabHTML(){
  var s = DATA.settings;
  var waValid = !s.whatsapp || /^[0-9]{8,15}$/.test(s.whatsapp);
  var adminDraft = (ADMIN.adminPathDraft === null || ADMIN.adminPathDraft === undefined) ? s.adminPath : ADMIN.adminPathDraft;

  return '' +
  '<div class="admin-section-title">İletişim & Ayarlar</div>' +
  '<div class="admin-section-sub">Enquiry butonlarında ve SEO meta etiketlerinde kullanılan bilgiler.</div>' +

  '<div class="admin-card">' +
    '<div style="font-size:15px;font-weight:600;margin-bottom:10px;">Site modu</div>' +
    '<div class="field" style="max-width:420px;margin-bottom:0;">' +
      '<label>Görünüm</label>' +
      '<select data-aa-change="launch-mode-change">' +
        '<option value="true"' + (s.launchMode ? ' selected' : '') + '>Açılış sitesi (tek sayfa)</option>' +
        '<option value="false"' + (!s.launchMode ? ' selected' : '') + '>Tam katalog</option>' +
      '</select>' +
      '<div class="hint">Açılış sitesi modu; koleksiyonları ve ürün sayfalarını silmeden gizler, yalnızca ana sayfa, hikaye ve iletişim görünür kalır. İstediğiniz zaman "Tam katalog"a geri dönebilirsiniz.</div>' +
    '</div>' +
  '</div>' +

  '<div class="field">' +
    '<label>WhatsApp (yalnızca rakam, ülke kodu ile)</label>' +
    '<input type="tel" id="whatsapp-input" data-bind="DATA.settings.whatsapp" value="' + escapeAttr(s.whatsapp) + '" placeholder="38267123456">' +
    '<div class="hint' + (waValid ? '' : ' err') + '" id="whatsapp-hint">' + (waValid ? 'Örnek: 38267123456 (ülke kodu + numara, boşluksuz)' : 'Sadece rakam girin, 8\u201315 haneli olmalı.') + '</div>' +
  '</div>' +

  '<div class="field"><label>E-posta</label><input type="email" data-bind="DATA.settings.email" value="' + escapeAttr(s.email) + '" placeholder="atolye@dadia.com"></div>' +
  '<div class="field"><label>Instagram (tam URL)</label><input type="url" data-bind="DATA.settings.instagram" value="' + escapeAttr(s.instagram) + '" placeholder="https://instagram.com/dadiaateliers"></div>' +
  '<div class="field"><label>Site URL (og:image için)</label><input type="url" data-bind="DATA.settings.siteUrl" value="' + escapeAttr(s.siteUrl) + '" placeholder="https://dadiaateliers.com"></div>' +
  '<div class="field full"><label>Meta Açıklama</label><textarea rows="2" data-bind="DATA.settings.metaDescription">' + escapeHtml(s.metaDescription) + '</textarea></div>' +

  '<div class="admin-card" style="margin-top:var(--sp-4);max-width:560px;">' +
    '<div class="admin-section-sub" style="margin-bottom:12px;">Yönetim paneli adresi \u2014 değiştirirseniz yeni bağlantıyı not edin.</div>' +
    '<div class="field"><label>Mevcut adres</label><div class="muted" style="font-size:14px;">#/' + escapeHtml(s.adminPath) + '</div></div>' +
    '<div class="field"><label>Yeni adres</label><input type="text" id="admin-path-input" value="' + escapeAttr(adminDraft) + '"></div>' +
    '<button type="button" class="admin-btn" data-aa="admin-path-change">Değiştir</button>' +
  '</div>';
}

/* ===========================================================
   20. ADMIN — YAYINLA & YEDEK
   =========================================================== */
function adminPublishTabHTML(){
  var est = zipSizeEstimate();
  var totalMB = bytesToMB(est.total);
  var photoMB = bytesToMB(est.photos);
  var overLimit = est.total > 40 * 1024 * 1024;
  var pct = Math.min(100, (est.total / (40*1024*1024)) * 100);
  var progress = ADMIN.zipProgress;
  var unpublishedPhotos = hasUnpublishedGalleryChanges();

  return '' +
  '<div class="admin-section-title">Yayınla & Yedek</div>' +
  '<div class="admin-section-sub">Değişiklikleriniz yalnızca bu tarayıcının belleğinde tutulur. Canlıya almak için aşağıdaki ZIP klasörünü indirip Netlify\u2019a yüklemeniz gerekir.</div>' +

  (unpublishedPhotos ? (
    '<div class="admin-card" style="border-color:#a3341f;">' +
      '<strong style="color:#a3341f;">Kaydedilmemiş fotoğraflarınız var.</strong>' +
      '<div class="hint" style="margin-top:6px;">Yayınlamadan bu sekmeyi ya da tarayıcıyı kapatmayın; kapatırsanız az önce eklediğiniz fotoğrafları kaybedebilirsiniz.</div>' +
    '</div>'
  ) : '') +

  '<div class="admin-card">' +
    '<div class="admin-section-sub" style="margin-bottom:0;">Tahmini ZIP boyutu</div>' +
    '<div style="font-family:var(--font-display);font-size:1.6rem;margin:6px 0;">' + totalMB + ' MB</div>' +
    '<div class="hint">Bunun yaklaşık ' + photoMB + ' MB\u2019ı fotoğraflardan oluşuyor.</div>' +
    '<div class="size-meter"><div class="size-meter-bar"><div class="size-meter-fill" style="width:' + pct + '%;' + (overLimit ? 'background:#a3341f;' : '') + '"></div></div></div>' +
    (overLimit
      ? '<div class="hint warn">40 MB üzerindeki paketler, yavaş internet bağlantısı olan ziyaretçilerde yavaş açılabilir. Site yine de çalışır \u2014 fotoğraf sayısını azaltmayı düşünebilirsiniz.</div>'
      : '<div class="hint">40 MB\u2019a kadar sorunsuzdur.</div>') +
  '</div>' +

  '<div class="admin-card">' +
    '<div class="admin-section-sub" style="margin-bottom:10px;">1. Siteyi yayınla</div>' +
    '<button type="button" class="admin-btn primary" data-aa="publish-zip-download"' + (ADMIN.zipBusy ? ' disabled' : '') + '>' + (ADMIN.zipBusy ? 'Paketleniyor\u2026' : 'Siteyi indir (ZIP klasör)') + '</button>' +
    (progress ? (
      '<div class="hint" style="margin-top:8px;">Paketleniyor\u2026 ' + progress.current + ' / ' + progress.total + ' dosya</div>' +
      '<div class="size-meter"><div class="size-meter-bar"><div class="size-meter-fill" style="width:' + Math.min(100, (progress.current/progress.total)*100) + '%;"></div></div></div>'
    ) : '') +
    (ADMIN.zipMissing && ADMIN.zipMissing.length ? (
      '<div class="hint err" style="margin-top:10px;">Şu fotoğraflar bulunamadığı için pakete eklenemedi: ' + ADMIN.zipMissing.map(escapeHtml).join(', ') + '. ZIP yine de indirildi; bu fotoğrafları eklemek için yeni klasörü Netlify\u2019daki mevcut klasörle <strong>değiştirmeyin</strong>, Finder\u2019da ikisini birleştirin.</div>'
    ) : '') +
    '<div class="hint" style="margin-top:10px;">Yalnızca yazı/metin değişikliği yaptıysanız (yeni fotoğraf eklemediyseniz), ' +
      '<button type="button" class="admin-btn small" data-aa="publish-download">yalnızca sayfa dosyasını indirin</button>.' +
    '</div>' +
    '<div class="hint">İndirme başlamazsa <button type="button" class="admin-btn small" data-aa="publish-fallback-manual">kaynağı görüntüle</button>.</div>' +
  '</div>' +

  '<div class="admin-card">' +
    '<div class="admin-section-sub" style="margin-bottom:10px;">2. İçerik yedeği</div>' +
    '<div class="btn-row">' +
      '<button type="button" class="admin-btn" data-aa="backup-download">İçerik yedeğini indir (JSON)</button>' +
      '<label class="admin-btn" style="display:inline-flex;align-items:center;">Yedekten geri yükle<input type="file" accept="application/json,.json" data-aa-change="backup-restore" style="display:none;"></label>' +
    '</div>' +
    (ADMIN.restoreError ? '<div class="hint err">' + escapeHtml(ADMIN.restoreError) + '</div>' : '') +
  '</div>' +

  '<div class="admin-explainer">' +
    '<strong>ZIP\u2019i Netlify\u2019a yükleme adımları</strong>' +
    '<ol>' +
      '<li>ZIP dosyasını indir</li>' +
      '<li>İndirilenler klasöründe dosyaya çift tıkla \u2014 yanında bir klasör oluşur</li>' +
      '<li>Netlify\u2019da sitene gir, Deploys sekmesini aç</li>' +
      '<li>Oluşan klasörü sayfanın altındaki alana sürükle</li>' +
      '<li>Yaklaşık yarım dakika içinde site güncellenir</li>' +
    '</ol>' +
  '</div>';
}

/* ===========================================================
   21. MODALS / TOAST
   =========================================================== */
function askConfirm(opts){
  ADMIN.confirm = opts;
  renderAdmin();
}

function confirmModalHTML(){
  var c = ADMIN.confirm;
  if(!c) return '';
  return '' +
  '<div class="modal-backdrop" data-aa="confirm-cancel-backdrop">' +
    '<div class="modal-box confirm-box">' +
      '<h3>' + escapeHtml(c.title) + '</h3>' +
      '<p>' + escapeHtml(c.message) + '</p>' +
      (c.extraHTML || '') +
      '<div class="modal-close-row">' +
        '<button type="button" class="admin-btn" data-aa="confirm-cancel">Vazgeç</button>' +
        '<button type="button" class="admin-btn ' + (c.danger ? 'danger' : 'primary') + '" data-aa="confirm-ok">' + escapeHtml(c.confirmLabel || 'Onayla') + '</button>' +
      '</div>' +
    '</div>' +
  '</div>';
}

function publishModalHTML(){
  if(ADMIN.publishModal === null || ADMIN.publishModal === undefined) return '';
  return '' +
  '<div class="modal-backdrop" data-aa="publish-modal-close-backdrop">' +
    '<div class="modal-box">' +
      '<h3>Kaynağı görüntüle</h3>' +
      '<p style="font-size:13.5px;">İndirme bu ortamda engellendi ya da manuel görüntüleme istediniz. Aşağıdaki metnin tamamını kopyalayıp <strong>index.html</strong> adlı bir dosyaya yapıştırın (veya JSON yedeği içinse dosya adının sonu .json olsun).</p>' +
      '<textarea readonly id="publish-modal-textarea">' + escapeHtml(ADMIN.publishModal) + '</textarea>' +
      '<div class="modal-close-row">' +
        '<button type="button" class="admin-btn primary" data-aa="publish-modal-copy">Tümünü kopyala</button>' +
        '<button type="button" class="admin-btn" data-aa="publish-modal-close">Kapat</button>' +
      '</div>' +
    '</div>' +
  '</div>';
}

function toastHTML(){
  if(!ADMIN.toast) return '<div class="toast"></div>';
  return '<div class="toast show">' + escapeHtml(ADMIN.toast) + '</div>';
}

/* ===========================================================
   22. IMAGE COMPRESSION
   =========================================================== */
function compressProductImage(file){
  return new Promise(function(resolve, reject){
    var reader = new FileReader();
    reader.onload = function(ev){
      var img = new Image();
      img.onload = function(){
        var maxEdge = 1600;
        var w = img.naturalWidth, h = img.naturalHeight;
        var scale = Math.min(1, maxEdge / Math.max(w, h));
        var cw = Math.max(1, Math.round(w * scale));
        var ch = Math.max(1, Math.round(h * scale));
        var canvas = document.createElement('canvas');
        canvas.width = cw; canvas.height = ch;
        var ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0, cw, ch);
        var isPng = file.type === 'image/png';
        var dataUrl = isPng ? canvas.toDataURL('image/png') : canvas.toDataURL('image/jpeg', 0.8);
        var sizeBytes = Math.round(dataUrl.length * 0.75);
        resolve({ data: dataUrl, sizeBytes: sizeBytes });
      };
      img.onerror = function(){ reject(new Error('image decode failed')); };
      img.src = ev.target.result;
    };
    reader.onerror = function(){ reject(new Error('file read failed')); };
    reader.readAsDataURL(file);
  });
}

function compressLogoImage(file){
  return new Promise(function(resolve, reject){
    var reader = new FileReader();
    reader.onload = function(ev){
      var img = new Image();
      img.onload = function(){
        var maxEdge = 600;
        var w = img.naturalWidth, h = img.naturalHeight;
        var scale = Math.min(1, maxEdge / Math.max(w, h));
        var cw = Math.max(1, Math.round(w * scale));
        var ch = Math.max(1, Math.round(h * scale));
        var canvas = document.createElement('canvas');
        canvas.width = cw; canvas.height = ch;
        var ctx = canvas.getContext('2d');
        ctx.clearRect(0,0,cw,ch);
        ctx.drawImage(img, 0, 0, cw, ch);
        resolve(canvas.toDataURL('image/png'));
      };
      img.onerror = function(){ reject(new Error('image decode failed')); };
      img.src = ev.target.result;
    };
    reader.onerror = function(){ reject(new Error('file read failed')); };
    reader.readAsDataURL(file);
  });
}

/* ===========================================================
   23. EXPORT / PUBLISH
   =========================================================== */
function cleanProductForExport(p){
  var out = {};
  for(var k in p){
    if(k.charAt(0) === '_') continue;
    out[k] = p[k];
  }
  out.images = (p.images||[]).map(function(img){
    var o = { data: img.data, alt: img.alt || '' };
    return o;
  });
  return out;
}

function dataForExport(){
  var clone = deepClone(DATA);
  clone.products = clone.products.map(cleanProductForExport);
  return clone;
}

function buildExportedHTML(){
  var json = JSON.stringify(dataForExport(), null, 2).replace(/<\//g, '<\\/');
  var re = /(<script\s+id=["']dadia-data["'][^>]*>)([\s\S]*?)(<\/script>)/i;
  if(!re.test(PRISTINE)){
    throw new Error('dadia-data script tag not found in pristine source');
  }
  return PRISTINE.replace(re, function(m, open, mid, close){
    return open + json + close;
  });
}

function triggerDownload(content, filename, mime){
  var blob = new Blob([content], { type: mime });
  var url = URL.createObjectURL(blob);
  var a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.rel = 'noopener';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  setTimeout(function(){ URL.revokeObjectURL(url); }, 4000);
}

function downloadPublish(){
  try{
    var html = buildExportedHTML();
    triggerDownload(html, 'index.html', 'text/html');
    showToast('index.html indirildi');
    STATE.adminDirty = hasUnpublishedGalleryChanges();
  } catch(err){
    console.error(err);
    ADMIN.publishModal = (function(){ try{ return buildExportedHTML(); } catch(e2){ return 'Dışa aktarma hatası: ' + e2.message; } })();
    renderAdmin();
  }
}

/* ===========================================================
   23B. ZIP WRITER — plain-JS STORE-method ZIP (pass 3)
   CRC-32 table, local file headers, central directory, EOCD.
   STORE (no compression): payload is already-compressed JPEGs/JSON,
   so DEFLATE would cost CPU time for almost no size gain.
   =========================================================== */
var ZIP_CRC_TABLE = (function(){
  var table = new Uint32Array(256);
  for(var n = 0; n < 256; n++){
    var c = n;
    for(var k = 0; k < 8; k++){ c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); }
    table[n] = c >>> 0;
  }
  return table;
})();
function zipCrc32(bytes){
  var crc = 0xFFFFFFFF;
  for(var i = 0; i < bytes.length; i++){ crc = ZIP_CRC_TABLE[(crc ^ bytes[i]) & 0xFF] ^ (crc >>> 8); }
  return (crc ^ 0xFFFFFFFF) >>> 0;
}
function zipDosDateTime(date){
  date = date || new Date();
  var time = ((date.getHours() & 0x1F) << 11) | ((date.getMinutes() & 0x3F) << 5) | (Math.floor(date.getSeconds() / 2) & 0x1F);
  var d = (((date.getFullYear() - 1980) & 0x7F) << 9) | (((date.getMonth() + 1) & 0xF) << 5) | (date.getDate() & 0x1F);
  return { time: time & 0xFFFF, date: d & 0xFFFF };
}
function zipUtf8(str){ return new TextEncoder().encode(str); }

// files: [{ name:'assets/gallery/x.jpg', bytes:Uint8Array }, ...]. Returns a Blob.
function buildZipBlob(files){
  var dt = zipDosDateTime();
  var localParts = [], centralParts = [], offset = 0;

  files.forEach(function(f){
    var nameBytes = zipUtf8(f.name);
    var data = f.bytes;
    var crc = zipCrc32(data);
    var size = data.length;
    var flag = 0x0800; // bit 11: filename is UTF-8

    var local = new Uint8Array(30);
    var lv = new DataView(local.buffer);
    lv.setUint32(0, 0x04034b50, true);
    lv.setUint16(4, 20, true);   // version needed
    lv.setUint16(6, flag, true);
    lv.setUint16(8, 0, true);    // method: STORE
    lv.setUint16(10, dt.time, true);
    lv.setUint16(12, dt.date, true);
    lv.setUint32(14, crc, true);
    lv.setUint32(18, size, true);
    lv.setUint32(22, size, true);
    lv.setUint16(26, nameBytes.length, true);
    lv.setUint16(28, 0, true);   // extra field length
    localParts.push(local, nameBytes, data);

    var central = new Uint8Array(46);
    var cv = new DataView(central.buffer);
    cv.setUint32(0, 0x02014b50, true);
    cv.setUint16(4, 20, true);   // version made by
    cv.setUint16(6, 20, true);   // version needed
    cv.setUint16(8, flag, true);
    cv.setUint16(10, 0, true);   // method: STORE
    cv.setUint16(12, dt.time, true);
    cv.setUint16(14, dt.date, true);
    cv.setUint32(16, crc, true);
    cv.setUint32(20, size, true);
    cv.setUint32(24, size, true);
    cv.setUint16(28, nameBytes.length, true);
    cv.setUint16(30, 0, true);   // extra field length
    cv.setUint16(32, 0, true);   // comment length
    cv.setUint16(34, 0, true);   // disk number start
    cv.setUint16(36, 0, true);   // internal attrs
    cv.setUint32(38, 0, true);   // external attrs
    cv.setUint32(42, offset, true); // local header offset
    centralParts.push(central, nameBytes);

    offset += local.length + nameBytes.length + size;
  });

  var centralStart = offset;
  var centralSize = 0;
  centralParts.forEach(function(p){ centralSize += p.length; });

  var eocd = new Uint8Array(22);
  var ev = new DataView(eocd.buffer);
  ev.setUint32(0, 0x06054b50, true);
  ev.setUint16(4, 0, true);
  ev.setUint16(6, 0, true);
  ev.setUint16(8, files.length, true);
  ev.setUint16(10, files.length, true);
  ev.setUint32(12, centralSize, true);
  ev.setUint32(16, centralStart, true);
  ev.setUint16(20, 0, true);

  return new Blob(localParts.concat(centralParts, [eocd]), { type: 'application/zip' });
}

/* ===========================================================
   23C. FOLDER PUBLISH — sourcing files & ZIP orchestration (pass 3)
   =========================================================== */
var ZIP_OPTIONAL_STATIC_FILES = ['robots.txt', 'sitemap.xml', '_headers'];

function zipSizeEstimate(){
  var photos = galleryStats().total;
  var html = currentDataSizeBytes();
  return { html: html, photos: photos, total: html + photos };
}

// tracks which version of settings.gallery was last actually shipped in a full ZIP,
// so the "unsaved photos" warning survives a page reload (not just STATE.adminDirty)
function gallerySnapshot(){
  try{ return JSON.stringify(DATA.settings.gallery || []); } catch(e){ return ''; }
}
function getPublishedGallerySnapshot(){
  if(ADMIN.publishedGallerySnapshot !== null) return ADMIN.publishedGallerySnapshot;
  try{ return localStorage.getItem('dadia-gallery-published') || ''; } catch(e){ return ''; }
}
function setPublishedGallerySnapshot(snap){
  ADMIN.publishedGallerySnapshot = snap;
  try{ localStorage.setItem('dadia-gallery-published', snap); } catch(e){}
}
function hasUnpublishedGalleryChanges(){
  var gallery = DATA.settings.gallery || [];
  if(!gallery.length) return false;
  return gallerySnapshot() !== getPublishedGallerySnapshot();
}

// sources one file's bytes: session blob first, then a same-origin fetch
// (recovers photos already live from an earlier publish), else null (missing)
function resolveAssetBytes(path){
  var blob = GALLERY_STORE[path];
  if(blob){
    return blob.arrayBuffer().then(function(buf){ return new Uint8Array(buf); });
  }
  if(location.protocol === 'http:' || location.protocol === 'https:'){
    return fetch(path).then(function(res){
      if(!res.ok) return null;
      return res.arrayBuffer().then(function(buf){ return new Uint8Array(buf); });
    }).catch(function(){ return null; });
  }
  return Promise.resolve(null);
}

function collectZipTasks(){
  var tasks = [];
  (DATA.settings.gallery || []).forEach(function(g){
    if(g.src) tasks.push({ path: g.src, label: g.src.split('/').pop(), optional: false });
    if(g.srcSmall) tasks.push({ path: g.srcSmall, label: g.srcSmall.split('/').pop(), optional: false });
  });
  var logo = DATA.settings.logo;
  if(logo && typeof logo === 'string' && logo.indexOf('data:') !== 0){
    tasks.push({ path: logo.replace(/^\/+/, ''), label: 'Logo (' + logo + ')', optional: false });
  }
  ZIP_OPTIONAL_STATIC_FILES.forEach(function(p){ tasks.push({ path: p, label: p, optional: true }); });
  return tasks;
}

function buildFolderZipEntries(onProgress){
  var entries = [{ name: 'index.html', bytes: zipUtf8(buildExportedHTML()) }];
  var missing = [];
  var tasks = collectZipTasks();
  var total = tasks.length + 1, done = 1;
  onProgress(done, total);

  var chain = Promise.resolve();
  tasks.forEach(function(task){
    chain = chain.then(function(){
      return resolveAssetBytes(task.path).then(function(bytes){
        if(bytes){
          entries.push({ name: task.path, bytes: bytes });
        } else if(!task.optional){
          missing.push(task.label);
        }
        done++;
        onProgress(done, total);
      });
    });
  });

  return chain.then(function(){ return { entries: entries, missing: missing }; });
}

function downloadFolderZip(){
  if(ADMIN.zipBusy) return;

  function proceed(){
    ADMIN.zipBusy = true;
    ADMIN.zipMissing = null;
    ADMIN.zipProgress = { current: 0, total: 1 };
    renderAdmin();

    buildFolderZipEntries(function(current, total){
      ADMIN.zipProgress = { current: current, total: total };
      renderAdmin();
    }).then(function(res){
      var blob = buildZipBlob(res.entries);
      var url = URL.createObjectURL(blob);
      var a = document.createElement('a');
      a.href = url;
      a.download = 'dadia-site-' + dateStamp() + '.zip';
      a.rel = 'noopener';
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      setTimeout(function(){ URL.revokeObjectURL(url); }, 4000);

      ADMIN.zipBusy = false;
      ADMIN.zipProgress = null;
      STATE.adminDirty = false;
      setPublishedGallerySnapshot(gallerySnapshot());

      if(res.missing.length){
        ADMIN.zipMissing = res.missing;
        showToast('ZIP indirildi, ancak bazı fotoğraflar eksik');
      } else {
        ADMIN.zipMissing = null;
        showToast('ZIP indirildi (' + bytesToMB(blob.size) + ' MB)');
      }
      renderAdmin();
    }).catch(function(err){
      console.error(err);
      ADMIN.zipBusy = false;
      ADMIN.zipProgress = null;
      showToast('ZIP oluşturulamadı: ' + err.message);
      renderAdmin();
    });
  }

  var est = zipSizeEstimate();
  if(est.total > 40 * 1024 * 1024){
    askConfirm({
      title: 'Dosya boyutu büyük',
      message: 'Paket yaklaşık ' + bytesToMB(est.total) + ' MB. Site yine de çalışır, ama yavaş internet bağlantısı olan ziyaretçiler için yüklenmesi uzun sürebilir. Fotoğraf sayısını azaltmayı düşünebilirsiniz. Yine de indirmek ister misiniz?',
      confirmLabel: 'Yine de indir',
      onConfirm: proceed
    });
  } else {
    proceed();
  }
}

function downloadBackup(){
  try{
    var json = JSON.stringify(dataForExport(), null, 2);
    triggerDownload(json, 'dadia-yedek-' + dateStamp() + '.json', 'application/json');
    showToast('Yedek indirildi');
  } catch(err){
    console.error(err);
    ADMIN.publishModal = JSON.stringify(dataForExport(), null, 2);
    renderAdmin();
  }
}

function handleRestoreFile(fileInput){
  var file = fileInput.files && fileInput.files[0];
  if(!file) return;
  var reader = new FileReader();
  reader.onload = function(ev){
    try{
      var parsed = JSON.parse(ev.target.result);
      if(!parsed || typeof parsed !== 'object' || !parsed.settings || !parsed.categories || !parsed.products){
        throw new Error('geçersiz yapı');
      }
      DATA = parsed;
      ensureSettingsDefaults(DATA);
      STATE.adminDirty = true;
      ADMIN.restoreError = null;
      ADMIN.productView = 'list';
      ADMIN.categoryView = 'list';
      showToast('Yedek geri yüklendi');
      renderAdmin();
    } catch(err){
      ADMIN.restoreError = 'Dosya okunamadı veya geçerli bir Dadia yedeği değil.';
      renderAdmin();
    }
  };
  reader.onerror = function(){
    ADMIN.restoreError = 'Dosya okunamadı.';
    renderAdmin();
  };
  reader.readAsText(file);
  fileInput.value = '';
}

function copyPublishModalText(){
  var ta = document.getElementById('publish-modal-textarea');
  if(!ta) return;
  var text = ta.value;
  function fallbackCopy(){
    ta.removeAttribute('readonly');
    ta.focus(); ta.select();
    try{ document.execCommand('copy'); showToast('Kopyalandı'); }
    catch(e){ showToast('Kopyalama başarısız \u2014 elle seçip kopyalayın'); }
    ta.setAttribute('readonly','');
  }
  if(navigator.clipboard && navigator.clipboard.writeText){
    navigator.clipboard.writeText(text).then(function(){ showToast('Kopyalandı'); }).catch(fallbackCopy);
  } else {
    fallbackCopy();
  }
}

/* ===========================================================
   24. ADMIN — CRUD: PRODUCTS
   =========================================================== */
function blankProductDraft(){
  var maxOrder = DATA.products.reduce(function(m,p){ return Math.max(m, p.order||0); }, 0);
  return {
    id: genId(), ref: nextRef(), categoryId: (DATA.categories.sort(byOrder)[0] || {}).id || '',
    title: '', origin: '', era: '', h: '', w: '', d: '', story: '',
    status: 'available', featured: false, order: maxOrder + 1, sample: false, images: [],
    _isNew: true
  };
}

function productNew(){
  ADMIN.productForm = blankProductDraft();
  ADMIN.productView = 'form';
  renderAdmin();
}
function productEdit(id){
  var p = DATA.products.find(function(x){ return x.id === id; });
  if(!p) return;
  var draft = deepClone(p);
  draft._isNew = false;
  draft._origId = p.id;
  ADMIN.productForm = draft;
  ADMIN.productView = 'form';
  renderAdmin();
}
function productDuplicate(id){
  var p = DATA.products.find(function(x){ return x.id === id; });
  if(!p) return;
  var draft = deepClone(p);
  draft.id = genId();
  draft.ref = nextRef();
  var maxOrder = DATA.products.reduce(function(m,pp){ return Math.max(m, pp.order||0); }, 0);
  draft.order = maxOrder + 1;
  draft.sample = false;
  draft._isNew = true;
  ADMIN.productForm = draft;
  ADMIN.productView = 'form';
  renderAdmin();
}
function productCancel(){
  ADMIN.productForm = null;
  ADMIN.productView = 'list';
  renderAdmin();
}
function productSave(){
  var f = ADMIN.productForm;
  if(!f.title || !f.title.trim()){ showToast('Başlık gerekli'); return; }
  if(!f.ref || !f.ref.trim()){ showToast('Referans no gerekli'); return; }
  var dupRef = DATA.products.some(function(p){ return p.ref === f.ref && p.id !== f.id; });
  if(dupRef){ showToast('Bu referans no zaten kullanılıyor'); return; }

  var clean = {
    id: f.id, ref: f.ref.trim(), categoryId: f.categoryId, title: f.title.trim(),
    origin: f.origin || '', era: f.era || '',
    h: Number(f.h) || 0, w: Number(f.w) || 0, d: Number(f.d) || 0,
    story: f.story || '', status: f.status || 'available',
    featured: !!f.featured, order: Number(f.order) || 0, sample: !!f.sample,
    images: (f.images||[]).map(function(img){ return { data: img.data, alt: img.alt || f.title || '' }; })
  };

  if(f._isNew){
    DATA.products.push(clean);
  } else {
    var idx = DATA.products.findIndex(function(p){ return p.id === f._origId; });
    if(idx >= 0) DATA.products[idx] = clean;
  }
  STATE.adminDirty = true;
  ADMIN.productForm = null;
  ADMIN.productView = 'list';
  showToast('Ürün kaydedildi');
  renderAdmin();
}
function productDelete(id){
  var p = DATA.products.find(function(x){ return x.id === id; });
  if(!p) return;
  askConfirm({
    title: 'Ürünü sil',
    message: '"' + p.title + '" (' + p.ref + ') kalıcı olarak silinecek. Emin misiniz?',
    confirmLabel: 'Sil', danger: true,
    onConfirm: function(){
      DATA.products = DATA.products.filter(function(x){ return x.id !== id; });
      STATE.adminDirty = true;
      showToast('Ürün silindi');
    }
  });
}
function productMove(id, dir){
  var prods = DATA.products.slice().sort(byOrder);
  var idx = prods.findIndex(function(p){ return p.id === id; });
  var swapIdx = idx + dir;
  if(idx < 0 || swapIdx < 0 || swapIdx >= prods.length) return;
  var a = prods[idx], b = prods[swapIdx];
  var tmp = a.order; a.order = b.order; b.order = tmp;
  STATE.adminDirty = true;
  renderAdmin();
}

/* ===========================================================
   25. ADMIN — CRUD: CATEGORIES
   =========================================================== */
function blankCategoryDraft(){
  var maxOrder = DATA.categories.reduce(function(m,c){ return Math.max(m, c.order||0); }, 0);
  return { id: genId(), slug: '', name: '', type: 'standard', intro: '', order: maxOrder + 1, _isNew: true, _slugTouched: false };
}
function categoryNew(){
  ADMIN.categoryForm = blankCategoryDraft();
  ADMIN.categoryView = 'form';
  renderAdmin();
}
function categoryEdit(id){
  var c = DATA.categories.find(function(x){ return x.id === id; });
  if(!c) return;
  var draft = deepClone(c);
  draft._isNew = false;
  draft._origId = c.id;
  draft._slugTouched = true;
  ADMIN.categoryForm = draft;
  ADMIN.categoryView = 'form';
  renderAdmin();
}
function categoryCancel(){
  ADMIN.categoryForm = null;
  ADMIN.categoryView = 'list';
  renderAdmin();
}
function categorySave(){
  var f = ADMIN.categoryForm;
  if(!f.name || !f.name.trim()){ showToast('Ad gerekli'); return; }
  var slug = f.slug && f.slug.trim() ? slugify(f.slug) : slugify(f.name);
  slug = uniqueSlug(slug, f._isNew ? null : f._origId);

  var clean = {
    id: f.id, slug: slug, name: f.name.trim(), type: f.type === 'concept' ? 'concept' : 'standard',
    intro: f.intro || '', order: Number(f.order) || 0
  };

  if(f._isNew){
    DATA.categories.push(clean);
  } else {
    var idx = DATA.categories.findIndex(function(c){ return c.id === f._origId; });
    if(idx >= 0) DATA.categories[idx] = clean;
  }
  STATE.adminDirty = true;
  ADMIN.categoryForm = null;
  ADMIN.categoryView = 'list';
  showToast('Koleksiyon kaydedildi');
  renderAdmin();
}
function categoryDelete(id){
  var c = DATA.categories.find(function(x){ return x.id === id; });
  if(!c) return;
  var count = productsInCategory(c.id).length;
  var others = DATA.categories.filter(function(x){ return x.id !== c.id; });

  if(count === 0){
    askConfirm({
      title: 'Koleksiyonu sil',
      message: '"' + c.name + '" silinecek. Bu koleksiyonda ürün yok. Emin misiniz?',
      confirmLabel: 'Sil', danger: true,
      onConfirm: function(){
        DATA.categories = DATA.categories.filter(function(x){ return x.id !== id; });
        STATE.adminDirty = true;
        showToast('Koleksiyon silindi');
      }
    });
    return;
  }

  if(!others.length){
    showToast('Bu koleksiyonu silmeden önce başka bir koleksiyon oluşturun.');
    return;
  }

  ADMIN.categoryDeleteMoveTarget = others[0].id;
  var selectHTML = '<div class="field" style="margin-top:12px;"><label>Ürünleri şuraya taşı</label><select id="category-move-select">' +
    others.map(function(o){ return '<option value="' + o.id + '">' + escapeHtml(o.name) + '</option>'; }).join('') +
    '</select></div>';

  askConfirm({
    title: 'Koleksiyonu sil',
    message: '"' + c.name + '" içinde ' + count + ' ürün var. Silmeden önce bu ürünleri başka bir koleksiyona taşıyın.',
    confirmLabel: 'Taşı ve Sil', danger: true,
    extraHTML: selectHTML,
    onConfirm: function(){
      var sel = document.getElementById('category-move-select');
      var target = sel ? sel.value : others[0].id;
      DATA.products.forEach(function(p){ if(p.categoryId === id) p.categoryId = target; });
      DATA.categories = DATA.categories.filter(function(x){ return x.id !== id; });
      STATE.adminDirty = true;
      showToast('Ürünler taşındı, koleksiyon silindi');
    }
  });
}
function categoryMove(id, dir){
  var cats = DATA.categories.slice().sort(byOrder);
  var idx = cats.findIndex(function(c){ return c.id === id; });
  var swapIdx = idx + dir;
  if(idx < 0 || swapIdx < 0 || swapIdx >= cats.length) return;
  var a = cats[idx], b = cats[swapIdx];
  var tmp = a.order; a.order = b.order; b.order = tmp;
  STATE.adminDirty = true;
  renderAdmin();
}

/* ===========================================================
   26. ADMIN — IMAGES / STORY PARAGRAPHS / LOGO
   =========================================================== */
function productImagesUpload(fileInput){
  var f = ADMIN.productForm;
  if(!f) return;
  var room = 4 - f.images.length;
  var files = Array.prototype.slice.call(fileInput.files || []).slice(0, room);
  if(!files.length){ fileInput.value=''; return; }
  var chain = Promise.resolve();
  files.forEach(function(file){
    chain = chain.then(function(){
      return compressProductImage(file).then(function(res){
        f.images.push({ data: res.data, alt: f.title || '', _size: res.sizeBytes });
      }).catch(function(err){ console.error(err); showToast('Fotoğraf yüklenemedi'); });
    });
  });
  chain.then(function(){ fileInput.value=''; renderAdmin(); });
}
function imgMove(dir, index){
  var f = ADMIN.productForm;
  if(!f) return;
  var swap = index + dir;
  if(swap < 0 || swap >= f.images.length) return;
  var tmp = f.images[index]; f.images[index] = f.images[swap]; f.images[swap] = tmp;
  renderAdmin();
}
function imgSetCover(index){
  var f = ADMIN.productForm;
  if(!f || index <= 0) return;
  var img = f.images.splice(index,1)[0];
  f.images.unshift(img);
  renderAdmin();
}
function imgRemove(index){
  var f = ADMIN.productForm;
  if(!f) return;
  f.images.splice(index,1);
  renderAdmin();
}

function storyParaAdd(){
  DATA.settings.storyParagraphs.push('');
  STATE.adminDirty = true;
  renderAdmin();
}
function storyParaRemove(index){
  if(DATA.settings.storyParagraphs.length <= 1) return;
  DATA.settings.storyParagraphs.splice(index,1);
  STATE.adminDirty = true;
  renderAdmin();
}

function logoUpload(fileInput){
  var file = fileInput.files && fileInput.files[0];
  if(!file) return;
  compressLogoImage(file).then(function(dataUrl){
    DATA.settings.logo = dataUrl;
    STATE.adminDirty = true;
    fileInput.value = '';
    renderAdmin();
  }).catch(function(err){ console.error(err); showToast('Logo yüklenemedi'); });
}
function logoRemove(){
  DATA.settings.logo = null;
  STATE.adminDirty = true;
  renderAdmin();
}

/* ===========================================================
   26B. GALLERY — image pipeline, blob store, IndexedDB
   =========================================================== */
var GALLERY_STORE = {};
var GALLERY_URL_CACHE = {};
var GALLERY_SEQ = 0;
(DATA.settings.gallery || []).forEach(function(g){
  var m = /-(\d+)-(?:1600|800)\.jpg$/.exec(g.src || '');
  if(m) GALLERY_SEQ = Math.max(GALLERY_SEQ, +m[1]);
});

function gallerySrcUrl(path){
  if(!path) return '';
  if(GALLERY_URL_CACHE[path]) return GALLERY_URL_CACHE[path];
  var blob = GALLERY_STORE[path];
  if(!blob) return path;
  var url = URL.createObjectURL(blob);
  GALLERY_URL_CACHE[path] = url;
  return url;
}

var IDB_NAME = 'dadia-gallery', IDB_STORE = 'blobs', GALLERY_IDB_AVAILABLE = true, _idbPromise = null;
function openGalleryDB(){
  if(_idbPromise) return _idbPromise;
  _idbPromise = new Promise(function(resolve){
    if(!window.indexedDB){ GALLERY_IDB_AVAILABLE = false; resolve(null); return; }
    try{
      var req = indexedDB.open(IDB_NAME, 1);
      req.onupgradeneeded = function(){ req.result.createObjectStore(IDB_STORE); };
      req.onsuccess = function(){ resolve(req.result); };
      req.onerror = function(){ GALLERY_IDB_AVAILABLE = false; resolve(null); };
    } catch(e){ GALLERY_IDB_AVAILABLE = false; resolve(null); }
  });
  return _idbPromise;
}
function idbTx(mode, fn){
  openGalleryDB().then(function(db){ if(!db) return; try{ fn(db.transaction(IDB_STORE,mode).objectStore(IDB_STORE)); } catch(e){} });
}
function idbPutBlob(key, blob){ idbTx('readwrite', function(os){ os.put(blob, key); }); }
function idbDeleteBlob(key){ idbTx('readwrite', function(os){ os['delete'](key); }); }
function idbRehydrateAll(){
  return openGalleryDB().then(function(db){
    if(!db) return;
    return new Promise(function(resolve){
      try{
        var req = db.transaction(IDB_STORE,'readonly').objectStore(IDB_STORE).openCursor();
        req.onsuccess = function(e){
          var c = e.target.result;
          if(c){ GALLERY_STORE[c.key] = c.value; c['continue'](); } else resolve();
        };
        req.onerror = function(){ resolve(); };
      } catch(e){ resolve(); }
    });
  });
}

function galleryCanvasBlob(img, maxEdge, quality){
  var w = img.naturalWidth, h = img.naturalHeight;
  var scale = Math.min(1, maxEdge / Math.max(w,h));
  var cw = Math.max(1, Math.round(w*scale)), ch = Math.max(1, Math.round(h*scale));
  var c = document.createElement('canvas');
  c.width = cw; c.height = ch;
  c.getContext('2d').drawImage(img, 0, 0, cw, ch);
  return new Promise(function(resolve){ c.toBlob(function(b){ resolve({ blob:b, w:cw, h:ch }); }, 'image/jpeg', quality); });
}
function compressGalleryImage(file){
  return new Promise(function(resolve, reject){
    var reader = new FileReader();
    reader.onload = function(ev){
      var img = new Image();
      // browsers decode JPEGs already rotated per embedded EXIF orientation,
      // so the canvas below never needs to read or apply that tag itself
      img.onload = function(){
        Promise.all([ galleryCanvasBlob(img, 1600, 0.72), galleryCanvasBlob(img, 800, 0.70) ])
          .then(function(r){ resolve({ large:r[0], small:r[1] }); }, reject);
      };
      img.onerror = function(){ reject(new Error('image decode failed')); };
      img.src = ev.target.result;
    };
    reader.onerror = function(){ reject(new Error('file read failed')); };
    reader.readAsDataURL(file);
  });
}

function yieldFrame(){ return new Promise(function(r){ setTimeout(r,0); }); }

function processGalleryFiles(fileList){
  var files = Array.prototype.slice.call(fileList || []).filter(function(f){ return f && f.type && f.type.indexOf('image/') === 0; });
  if(!files.length) return;
  ADMIN.galleryProgress = { current:0, total:files.length };
  renderAdmin();
  var chain = Promise.resolve();
  files.forEach(function(file, i){
    chain = chain.then(function(){
      return compressGalleryImage(file).then(function(res){
        GALLERY_SEQ++;
        var pad = String(GALLERY_SEQ).padStart(2,'0');
        var pLarge = 'assets/gallery/da-g-' + pad + '-1600.jpg', pSmall = 'assets/gallery/da-g-' + pad + '-800.jpg';
        GALLERY_STORE[pLarge] = res.large.blob;
        GALLERY_STORE[pSmall] = res.small.blob;
        idbPutBlob(pLarge, res.large.blob);
        idbPutBlob(pSmall, res.small.blob);
        DATA.settings.gallery.push({ src:pLarge, srcSmall:pSmall, w:res.large.w, h:res.large.h, alt:'' });
        STATE.adminDirty = true;
      }).catch(function(err){
        console.error(err);
        showToast('Bir fotoğraf işlenemedi: ' + file.name);
      }).then(function(){
        ADMIN.galleryProgress = { current:i+1, total:files.length };
        renderAdmin();
        return yieldFrame();
      });
    });
  });
  chain.then(function(){ ADMIN.galleryProgress = null; renderAdmin(); });
}

function galleryFilesUpload(input){ processGalleryFiles(input.files); input.value = ''; }

function galleryMove(index, dir){
  var arr = DATA.settings.gallery, swap = index + dir;
  if(swap < 0 || swap >= arr.length) return;
  var tmp = arr[index]; arr[index] = arr[swap]; arr[swap] = tmp;
  STATE.adminDirty = true;
  renderAdmin();
}

function galleryRemove(index){
  var entry = DATA.settings.gallery[index];
  if(!entry) return;
  askConfirm({
    title:'Fotoğrafı sil',
    message:'Bu fotoğraf galeriden kaldırılacak. Bu işlem geri alınamaz.',
    confirmLabel:'Sil', danger:true,
    onConfirm:function(){
      [entry.src, entry.srcSmall].forEach(function(p){
        delete GALLERY_STORE[p];
        idbDeleteBlob(p);
        if(GALLERY_URL_CACHE[p]){ URL.revokeObjectURL(GALLERY_URL_CACHE[p]); delete GALLERY_URL_CACHE[p]; }
      });
      DATA.settings.gallery.splice(index,1);
      STATE.adminDirty = true;
      showToast('Fotoğraf silindi');
    }
  });
}

function galleryStats(){
  var total = 0, oversize = [];
  (DATA.settings.gallery || []).forEach(function(g){
    var l = GALLERY_STORE[g.src], sm = GALLERY_STORE[g.srcSmall];
    if(l){ total += l.size; if(l.size > 260*1024) oversize.push(g.src.split('/').pop()); }
    if(sm) total += sm.size;
  });
  return { total:total, oversize:oversize };
}

idbRehydrateAll().then(function(){
  if(ADMIN.active) renderAdmin();
  else if(HERO.rootEl){
    var e = HERO.images[HERO.index], el = document.getElementById('hc-layer-' + HERO.activeLayer);
    if(e && el){ el.src = gallerySrcUrl(e.srcSmall); el.srcset = heroSrcset(e); }
  }
});

function adminPathChange(){
  var input = document.getElementById('admin-path-input');
  var val = slugify(input ? input.value : ADMIN.adminPathDraft || '');
  if(!val){ showToast('Geçerli bir adres girin'); return; }
  var newUrl = window.location.origin + window.location.pathname + '#/' + val;
  askConfirm({
    title: 'Yönetim adresini değiştir',
    message: 'Yeni panel adresi: ' + newUrl + ' \u2014 Bu adresi not almadan devam etmeyin.',
    confirmLabel: 'Değiştir',
    onConfirm: function(){
      DATA.settings.adminPath = val;
      STATE.adminDirty = true;
      ADMIN.adminPathDraft = null;
      showToast('Adres değiştirildi');
      window.location.hash = '#/' + val;
      router();
    }
  });
}

function adminExit(){
  function goHome(){
    ADMIN.productForm = null; ADMIN.productView = 'list';
    ADMIN.categoryForm = null; ADMIN.categoryView = 'list';
    window.location.hash = '#/';
  }
  if(STATE.adminDirty){
    askConfirm({
      title: 'Siteye dön',
      message: 'Kaydedilmemiş değişiklikleriniz olabilir (yalnızca bellekte tutulur). Yine de siteye dönmek istiyor musunuz?',
      confirmLabel: 'Devam et',
      onConfirm: goHome
    });
  } else {
    goHome();
  }
}

/* ===========================================================
   27. ADMIN — EVENT DELEGATION
   =========================================================== */
document.addEventListener('click', function(e){
  var t = e.target.closest('[data-aa]');
  if(!t) return;
  var aa = t.getAttribute('data-aa');
  var id = t.getAttribute('data-id');
  var index = t.hasAttribute('data-index') ? parseInt(t.getAttribute('data-index'),10) : null;

  switch(aa){
    case 'admin-exit': adminExit(); break;
    case 'admin-tab': ADMIN.tab = t.getAttribute('data-tab'); ADMIN.productView='list'; ADMIN.categoryView='list'; renderAdmin(); break;

    case 'product-new': productNew(); break;
    case 'product-edit': productEdit(id); break;
    case 'product-duplicate': productDuplicate(id); break;
    case 'product-delete': productDelete(id); break;
    case 'product-move-up': productMove(id, -1); break;
    case 'product-move-down': productMove(id, 1); break;
    case 'product-save': productSave(); break;
    case 'product-cancel': productCancel(); break;

    case 'img-move-left': imgMove(-1, index); break;
    case 'img-move-right': imgMove(1, index); break;
    case 'img-set-cover': imgSetCover(index); break;
    case 'img-remove': imgRemove(index); break;

    case 'category-new': categoryNew(); break;
    case 'category-edit': categoryEdit(id); break;
    case 'category-delete': categoryDelete(id); break;
    case 'category-move-up': categoryMove(id, -1); break;
    case 'category-move-down': categoryMove(id, 1); break;
    case 'category-save': categorySave(); break;
    case 'category-cancel': categoryCancel(); break;

    case 'story-para-add': storyParaAdd(); break;
    case 'story-para-remove': storyParaRemove(index); break;
    case 'logo-remove': logoRemove(); break;

    case 'gallery-move-up': galleryMove(index, -1); break;
    case 'gallery-move-down': galleryMove(index, 1); break;
    case 'gallery-remove': galleryRemove(index); break;

    case 'admin-path-change': adminPathChange(); break;

    case 'publish-zip-download': downloadFolderZip(); break;
    case 'publish-download': downloadPublish(); break;
    case 'publish-fallback-manual':
      try{ ADMIN.publishModal = buildExportedHTML(); } catch(err){ ADMIN.publishModal = 'Hata: ' + err.message; }
      renderAdmin();
      break;
    case 'publish-modal-close': ADMIN.publishModal = null; renderAdmin(); break;
    case 'publish-modal-close-backdrop': if(e.target === t){ ADMIN.publishModal = null; renderAdmin(); } break;
    case 'publish-modal-copy': copyPublishModalText(); break;
    case 'backup-download': downloadBackup(); break;

    case 'confirm-ok':
      var fn = ADMIN.confirm && ADMIN.confirm.onConfirm;
      ADMIN.confirm = null;
      if(fn) fn();
      renderAdmin();
      break;
    case 'confirm-cancel': ADMIN.confirm = null; renderAdmin(); break;
    case 'confirm-cancel-backdrop': if(e.target === t){ ADMIN.confirm = null; renderAdmin(); } break;
  }
});

// gallery drag-and-drop zone
document.addEventListener('dragover', function(e){
  var t = e.target.closest('[data-aa-dropzone]');
  if(!t) return;
  e.preventDefault();
  t.classList.add('drag-active');
});
document.addEventListener('dragleave', function(e){
  var t = e.target.closest('[data-aa-dropzone]');
  if(!t) return;
  t.classList.remove('drag-active');
});
document.addEventListener('drop', function(e){
  var t = e.target.closest('[data-aa-dropzone]');
  if(!t) return;
  e.preventDefault();
  t.classList.remove('drag-active');
  var files = e.dataTransfer && e.dataTransfer.files;
  if(files && files.length) processGalleryFiles(files);
});

// file inputs / explicit upload triggers (always fire 'change', never 'input')
document.addEventListener('change', function(e){
  var t = e.target.closest('[data-aa-change]');
  if(!t) return;
  var aa = t.getAttribute('data-aa-change');
  switch(aa){
    case 'product-images-upload': productImagesUpload(t); break;
    case 'logo-upload': logoUpload(t); break;
    case 'backup-restore': handleRestoreFile(t); break;
    case 'gallery-files-upload': galleryFilesUpload(t); break;
    case 'launch-mode-change':
      DATA.settings.launchMode = (t.value === 'true');
      STATE.adminDirty = true;
      renderAdmin();
      break;
  }
});

// generic two-way binding for text/number/textarea data-bind fields, fired live while typing
// (never triggers a full re-render, so focus/cursor position is preserved)
document.addEventListener('input', function(e){
  var t = e.target;
  if(t.id === 'admin-path-input'){ ADMIN.adminPathDraft = t.value; return; }
  if(!t.hasAttribute || !t.hasAttribute('data-bind')) return;
  var path = t.getAttribute('data-bind');
  var val;
  if(t.type === 'checkbox') val = t.checked;
  else if(t.type === 'number') val = t.value === '' ? '' : Number(t.value);
  else val = t.value;
  bindSet(path, val);
  if(path.indexOf('DATA.') === 0) STATE.adminDirty = true;

  // light-touch side effects (direct DOM update, no full re-render)
  if(path === 'DATA.settings.whatsapp'){
    var hint = document.getElementById('whatsapp-hint');
    if(hint){
      var ok = !val || /^[0-9]{8,15}$/.test(val);
      hint.textContent = ok ? 'Örnek: 38267123456 (ülke kodu + numara, boşluksuz)' : 'Sadece rakam girin, 8\u201315 haneli olmalı.';
      hint.classList.toggle('err', !ok);
    }
  }
  if(path === 'ADMIN.categoryForm.name' && ADMIN.categoryForm && !ADMIN.categoryForm._slugTouched){
    ADMIN.categoryForm.slug = slugify(val);
    var slugInput = document.querySelector('input[data-bind="ADMIN.categoryForm.slug"]');
    if(slugInput){
      slugInput.value = ADMIN.categoryForm.slug;
      var hintEl = slugInput.parentElement.querySelector('.hint');
      if(hintEl) hintEl.textContent = '/collection/' + ADMIN.categoryForm.slug;
    }
  }
  if(path === 'ADMIN.categoryForm.slug' && ADMIN.categoryForm){
    ADMIN.categoryForm._slugTouched = true;
  }
});

// generic two-way binding for select/checkbox data-bind fields (these only fire 'change')
document.addEventListener('change', function(e){
  var t = e.target;
  if(!t.hasAttribute || !t.hasAttribute('data-bind')) return;
  if(t.tagName !== 'SELECT' && t.type !== 'checkbox') return;
  var path = t.getAttribute('data-bind');
  var val = t.type === 'checkbox' ? t.checked : t.value;
  bindSet(path, val);
  if(path.indexOf('DATA.') === 0) STATE.adminDirty = true;
  // category type toggles the "intro" field's visibility, so this one needs a re-render
  if(path === 'ADMIN.categoryForm.type'){ renderAdmin(); }
});

/* ===========================================================
   28. META TAGS
   =========================================================== */
function applyMetaTags(){
  var s = DATA.settings;
  document.title = s.brandName + ' \u2014 ' + s.tagline;

  var metaDesc = document.querySelector('meta[name="description"]');
  if(metaDesc) metaDesc.setAttribute('content', s.metaDescription);

  var ogTitle = document.querySelector('meta[property="og:title"]');
  if(ogTitle) ogTitle.setAttribute('content', s.brandName + ' \u2014 ' + s.tagline);

  var ogDesc = document.querySelector('meta[property="og:description"]');
  if(ogDesc) ogDesc.setAttribute('content', s.metaDescription);

  var ogImage = document.querySelector('meta[property="og:image"]');
  if(s.siteUrl){
    var url = s.siteUrl.replace(/\/+$/,'') + '/og.jpg';
    if(!ogImage){
      ogImage = document.createElement('meta');
      ogImage.setAttribute('property','og:image');
      document.head.appendChild(ogImage);
    }
    ogImage.setAttribute('content', url);
  } else if(ogImage){
    ogImage.parentNode.removeChild(ogImage);
  }
}

/* ===========================================================
   29. INIT
   =========================================================== */
function init(){
  applyMetaTags();
  router();
}

if(document.readyState === 'loading'){
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

})();
</script></body></html>