const { useState, useEffect, useRef } = React;

/* ============ i18n content ============ */
const T = {
  en: {
    docTitle: "DropIn — low-fi wireframes",
    docSub: "6 pages · matches source layout · use Tweaks to switch",
    banner: "DropIn is early access now.",
    studio: "DropIn Studio.",
    nav: { about: "About me", features: "Features", roadmap: "Roadmap", change: "change logs", download: "Downloads", contact: "contracts" },
    about: {
      h1Compare: "Why DropIn?",
      heroSub: "Where design meets commerce.",
      heroDesc: "DropIn brings real products into your SketchUp workflow — browse, insert, and quote in one panel.",
      thumbs: [
      { src: "thumbs/Table_Dining_001.png", label: "dining 01" },
      { src: "thumbs/Chair_Lounge_003.png", label: "lounge 02" },
      { src: "thumbs/Bed_King_003.png", label: "bed 03" },
      { src: "thumbs/Table_Coffee_001.png", label: "coffee 04" },
      { src: "thumbs/Decor_Painting_002.png", label: "painting 05" },
      { src: "thumbs/Table_Desk_001.png", label: "desk 06" },
      { src: "thumbs/Decor_Sculpture_003.png", label: "sculpture 07" },
      { src: "thumbs/Decor_Tray_003.png", label: "tray 08" },
      { src: "thumbs/Decor_Artwork_001.png", label: "artwork 09" },
      { src: "thumbs/Table_Coffee_002.png", label: "coffee 10" },
      { src: "thumbs/Decor_Brick_003.png", label: "brick 11" },
      { src: "thumbs/Street-Furniture_Bench-Public_031.png", label: "bench 12" },
      { src: "thumbs/Street-Furniture_Bench-Public_041.png", label: "bench 13" },
      { src: "thumbs/Street-Furniture_Bench-Public_044.png", label: "bench 14" },
      { src: "thumbs/Street-Furniture_Bench-Public_051.png", label: "bench 15" }],
      colA: "Without DropIn", colB: "With DropIn",
      rows: [
      ["Download .skp manually from vendor websites", "Browse + insert in one click"],
      ["Track prices in a spreadsheet separately", "BOQ auto-generates from your scene"],
      ["Lose time hunting for the right material", "Filter by brand, category, price"],
      ["Re-download every time you open a new file", "Smart disk cache — instant on re-open"]]

    },
    features: {
      h1: "Features",
      cards: [
      { glyph: "✦", tag: "Showroom", title: "Branded 3D catalog", body: "Browse a curated catalog of real branded furniture and products from a cloud library. Each entry includes brand, SKU, price, and availability.", bullets: ["Brand & price filters", "Availability badges", "Favorites + recent history"] },
      { glyph: "☁", tag: "Creative", title: "Design asset library", body: "A design-focused 3D model library. Same experience as Showroom but without brands.", bullets: ["Category sidebar nav", "Virtual scrolling at 500+", "Hourly background refresh"] },
      { glyph: "▣", tag: "Local", title: "Your own .skp folder", body: "Point DropIn at any folder on your machine and browse your own .skp files as a visual catalog. No internet required.", bullets: ["Non-blocking thumbnails", "Auto-organize 40+ categories", "Undo organize"] },
      { glyph: "◑", tag: "Material", title: "PBR texture library", body: "Apply albedo, normal, roughness, metallic, and AO maps to any face with one click. Pre-cached → ~0.2s apply.", bullets: ["Full PBR on SketchUp 2025+", "Albedo-only on older versions", "Imported from real Materials"] },
      { glyph: "Σ", tag: "BOQ Exporter", title: "Bill of Quantities", body: "Scans your live SketchUp scene for DropIn-inserted models & materials. Furniture (qty × price) and Material (m² × price/m²) tabs.", bullets: ["PDF · landscape A4", "Excel .xlsx via SheetJS", "Export by brand → folder"] },
      { glyph: "↺", tag: "Smart Cache", title: "Always instant", body: "Hash-diff index merge skips re-render when catalog unchanged. Version-based invalidation purges stale thumbnails on bump.", bullets: ["Thumbnail disk cache", "Hourly auto-refresh", "Retroactive BOQ enrichment"] }]

    },
    roadmap: {
      h1: "Roadmaps",
      nodes: [
      { ver: "v1.0", ttl: "Showroom", stage: "Shipped", stageCls: "", body: "Core catalog, BOQ, disk cache. Showroom + Creative + Local tabs ship with virtual scrolling and hourly auto-refresh." },
      { ver: "v1.1", ttl: "Material", stage: "Now", stageCls: "now", body: "PBR texture library from R2 cloud. Background pre-cache brings apply time from 5–10s down to ~0.2s. Material BOQ tab." },
      { ver: "v1.2", ttl: "Collection", stage: "Planned", stageCls: "soon", body: "BOQ cloud sync per user. Thai font in PDF export (Noto Sans Thai). User-curated bundles across libraries." }],

      labelTab: (name) => name + " tab",
      kanban: [
      { h: "Shipped", ver: "v1.0", items: ["Showroom catalog", "Creative library", "Local browser", "BOQ exporter", "Disk cache + virtual scroll"] },
      { h: "Now", ver: "v1.1", items: ["Material tab — PBR", "Material BOQ (m² × price/m²)", "Parallel cache workers", "Export by brand"] },
      { h: "Planned", ver: "v1.2", items: ["Thai font in PDF export", "BOQ cloud sync", "Collection tab", "Team-level libraries", "Render-engine hooks"] }],

      milestones: [
      { stage: "Now", ttl: "Thai font in PDF export", note: "Embed Noto Sans Thai so BOQ no longer renders Thai as boxes.", cls: "now" },
      { stage: "Next", ttl: "BOQ cloud sync", note: "Save BOQ snapshots to cloud per user email; team-level libraries." },
      { stage: "Later", ttl: "Collection tab", note: "User-curated bundles across Showroom, Creative, Local and Material." },
      { stage: "Idea", ttl: "Render-engine material hooks", note: "Swap albedo for V-Ray / Enscape native materials when present." }]

    },
    change: {
      h1: "changelogs",
      tagClass: { "Added": "added", "Fixed": "fixed", "Changed": "changed", "Refactored": "refactored", "Removed": "removed" },
      log: [
      { v: "v1.1.0", d: "2026-05-17",
        sections: [
        ["Added", ["Parallel background cache workers — 2× faster pre-download for Showroom, Creative, Material."]],
        ["Fixed", ["BOQ overlay: close button repositioned to absolute top-right."]]]
      },
      { v: "v1.1.0-rc", d: "2026-05-15",
        sections: [
        ["Added", [
        "Material BOQ tab: area-based pricing (m² × price/m²), PDF / Excel / Export-by-Brand.",
        "Material card inline insert (↓) and favorite (♡) buttons.",
        "Refresh (↺) button on all 4 tab toolbars — forces full re-download."]],

        ["Changed", [
        "Terms of Use: EN tab first, 6-bullet prohibited actions list.",
        "License manager: removed VERIFIED row from info card.",
        'Material tab: "Apply to face" renamed to "↓ Insert".']],

        ["Refactored", ["dialog.rb (1662 lines) split into 7 focused domain files — zero behavior change."]]]
      },
      { v: "v1.0.0", d: "2026-05-13",
        sections: [
        ["Added", [
        "Material Tab (◑): PBR texture library from R2 cloud.",
        "Background material pre-download — apply time 5–10s → ~0.2s.",
        "Hourly background auto-refresh for Showroom, Creative, Material catalogs.",
        "Version-based cache invalidation — stale thumbnails auto-purged on bump."]]]

      },
      { v: "v0.9.0", d: "2026-05-11",
        sections: [
        ["Added", [
        "Virtual scrolling for Showroom + Creative grids — no lag at 500+ cards.",
        "Local tab: non-blocking background thumbnail generation."]],

        ["Refactored", [
        "app.js (3751 lines) split into 6 files.",
        "index.html split into HTML + style.css + app.js."]]]

      }]

    },
    download: {
      h1: "Downloads",
      version: "v1.2.0", relDate: "May 19, 2026",
      filenameWin: "DropIn_Setup_v1.2.0.exe", formatWin: ".exe Windows Installer",
      relLabel: "released",
      ctaWinHero: "⤓ Download for Windows",
      ctaWin: "⤓ Download .exe",
      installTitle: "Install",
      installSteps: [
      "Download DropIn_Setup_v1.2.0.exe",
      "Run the installer (administrator not required)",
      "Follow on-screen steps — the plugin installs automatically",
      "Open or restart SketchUp",
      "Access via Extensions > DropIn or the toolbar button"],

      macTitle: (v) => "DropIn " + v + " — macOS",
      macFormat: ".pkg installer · macOS 12+ · in development",
      macFilename: "DropIn_macOS_v1.2.0.pkg",
      macCta: "⤓ Notify me when ready",
      macBadge: "Coming soon",
      macListTitle: "What's coming",
      macList: [
      "Native .pkg installer — auto-installs without Extension Manager",
      "Supports Intel and Apple Silicon (Universal Binary)",
      "Access via Extensions > DropIn just like the Windows build",
      "Auto-update when SketchUp launches",
      "Estimated release in Q4 2026"],

      compatHead: "SketchUp compatibility",
      compatNote: "PBR maps (normal, roughness, metallic, AO) require SketchUp 2025+. On older versions, albedo texture is applied — no error shown.",
      compat: [
      ["2026", "Full support + PBR materials", "pbr"],
      ["2025", "Full support + PBR materials", "pbr"],
      ["2024", "Full support", "ok"],
      ["2023", "Full support", "ok"],
      ["2022", "Full support", "ok"],
      ["2021", "Full support", "ok"],
      ["2020", "Full support", "ok"],
      ["2019 & below", "Not supported", "no"]]

    },
    contact: {
      h1: "Contracts",
      list: [
      { name: "Instagram", handle: "Drop.__.In", note: "Daily renders & behind-the-scenes.", url: "https://www.instagram.com/drop.__.in?igsh=Nng5dmwwYmE4MTlp&utm_source=qr" },
      { name: "Facebook", handle: "DropIn", note: "Release notes & community Q&A.", url: "https://www.facebook.com/profile.php?id=61568038913118" },
      { name: "TikTok", handle: "dropin57", note: "30-sec workflow tutorials.\n\n", url: "https://tiktok.com/@dropin57" },
      { name: "LINE", handle: "dropin0000", note: "Direct support · TH/EN.\n\n", url: "https://line.me/ti/p/C4awKCccKw" },
      { name: "Email", handle: "dropin0000@gmail.com", note: "Licensing, partnerships, press.\n\n", url: "mailto:dropin0000@gmail.com" },
      { name: "Phone", handle: "063 658 2022", note: "Mon–Fri · 09:00–18:00\n\n", url: "tel:+66636582022" }]

    },
    tweaks: {
      title: "Tweaks",
      navigate: "Navigate", page: "Page",
      aboutLayout: "About layout", featuresLayout: "Features layout",
      roadmapLayout: "Roadmap layout", changelogLayout: "Changelog layout",
      downloadsLayout: "Downloads layout", contactsLayout: "Contacts layout",
      display: "Display", gridPaper: "Grid paper",
      opt: { hero: "Hero", compare: "Compare", three: "3-up", two: "2-up", list: "List",
        timeline: "Timeline", kanban: "Kanban", milestones: "List",
        latest: "Latest", all: "All", twoCol: "Two-col", single: "Single",
        grid: "Grid", listV: "List" }
    }
  },

  th: {
    docTitle: "DropIn — ไวร์เฟรมโลไฟ",
    docSub: "6 หน้า · ใช้เลย์เอาต์เดิม · สลับมุมมองได้จาก Tweaks",
    banner: "DropIn อยู่ในช่วง Early Access แล้ว",
    studio: "DropIn Studio.",
    nav: { about: "เกี่ยวกับเรา", features: "ฟีเจอร์", roadmap: "แผนพัฒนา", change: "บันทึกอัปเดต", download: "ดาวน์โหลด", contact: "ติดต่อ" },
    about: {
      h1Compare: "ทำไมต้อง DropIn?",
      heroSub: "เมื่อดีไซน์ มาบรรจบกับการค้า",
      heroDesc: "DropIn นำสินค้าจริงเข้าสู่ขั้นตอนการทำงานบน SketchUp ของคุณ — เลือก แทรก และออกใบเสนอราคา ได้ในแผงเดียว",
      thumbs: [
      { src: "thumbs/Table_Dining_001.png", label: "โต๊ะอาหาร 01" },
      { src: "thumbs/Chair_Lounge_003.png", label: "เก้าอี้ 02" },
      { src: "thumbs/Bed_King_003.png", label: "เตียง 03" },
      { src: "thumbs/Table_Coffee_001.png", label: "โต๊ะกลาง 04" },
      { src: "thumbs/Decor_Painting_002.png", label: "ภาพ 05" },
      { src: "thumbs/Table_Desk_001.png", label: "โต๊ะทำงาน 06" },
      { src: "thumbs/Decor_Sculpture_003.png", label: "ประติมากรรม 07" },
      { src: "thumbs/Decor_Tray_003.png", label: "ถาด 08" },
      { src: "thumbs/Decor_Artwork_001.png", label: "งานศิลป์ 09" },
      { src: "thumbs/Table_Coffee_002.png", label: "โต๊ะกลาง 10" },
      { src: "thumbs/Decor_Brick_003.png", label: "ดีคอร์ 11" },
      { src: "thumbs/Street-Furniture_Bench-Public_031.png", label: "ม้านั่ง 12" },
      { src: "thumbs/Street-Furniture_Bench-Public_041.png", label: "ม้านั่ง 13" },
      { src: "thumbs/Street-Furniture_Bench-Public_044.png", label: "ม้านั่ง 14" },
      { src: "thumbs/Street-Furniture_Bench-Public_051.png", label: "ม้านั่ง 15" }],
      colA: "ก่อนใช้ DropIn", colB: "หลังใช้ DropIn",
      rows: [
      ["ดาวน์โหลดไฟล์ .skp ทีละชิ้นจากเว็บแบรนด์", "เลือกและแทรกได้ในคลิกเดียว"],
      ["จดราคาในสเปรดชีตแยกต่างหาก", "BOQ สร้างอัตโนมัติจากซีนใน SketchUp"],
      ["เสียเวลาค้นหาวัสดุที่ใช่", "กรองตามแบรนด์ หมวดหมู่ และราคาได้ทันที"],
      ["ต้องโหลดซ้ำทุกครั้งที่เปิดไฟล์ใหม่", "Smart Cache ทำให้เปิดซ้ำได้ทันที"]]

    },
    features: {
      h1: "ฟีเจอร์",
      cards: [
      { glyph: "✦", tag: "Showroom", title: "แคตตาล็อก 3D จากแบรนด์จริง", body: "เลือกชมโมเดลเฟอร์นิเจอร์และสินค้าแบรนด์จริงจากคลังคลาวด์ พร้อมข้อมูลแบรนด์ รหัสสินค้า ราคา และสถานะคงเหลือ", bullets: ["ตัวกรองแบรนด์และราคา", "แสดงสถานะสินค้าคงเหลือ", "รายการโปรด + ประวัติล่าสุด"] },
      { glyph: "☁", tag: "Creative", title: "คลังโมเดลสำหรับงานออกแบบ", body: "คลังโมเดล 3D สำหรับงานครีเอทีฟ ใช้งานเหมือน Showroom แต่เป็นคลังโมเดลที่ไม่มีแบรนด์", bullets: ["แถบหมวดหมู่ด้านข้าง", "Virtual scroll รองรับ 500+ รายการ", "รีเฟรชเบื้องหลังทุกชั่วโมง"] },
      { glyph: "▣", tag: "Local", title: "โฟลเดอร์ .skp ของคุณ", body: "ชี้ DropIn ไปที่โฟลเดอร์ใดก็ได้ในเครื่อง แล้วเปิดดูไฟล์ .skp ของตัวเองในรูปแบบแคตตาล็อกได้ทันที ไม่ต้องใช้อินเทอร์เน็ต", bullets: ["สร้าง thumbnail แบบไม่ค้างจอ", "จัดกลุ่มอัตโนมัติ 40+ หมวด", "ย้อนกลับการจัดกลุ่มได้"] },
      { glyph: "◑", tag: "Material", title: "คลังเท็กซ์เจอร์ PBR", body: "ทาวัสดุ albedo, normal, roughness, metallic และ AO ลงบนผิวได้ในคลิกเดียว โหลดล่วงหน้าไว้แล้ว ใช้งานในเวลา ~0.2 วินาที", bullets: ["รองรับ PBR เต็มรูปแบบบน SketchUp 2025+", "ใช้เฉพาะ albedo บนเวอร์ชันเก่ากว่า", "นำเข้าจาก Material จริง"] },
      { glyph: "Σ", tag: "BOQ Exporter", title: "ใบแสดงปริมาณงาน (BOQ)", body: "สแกนซีน SketchUp สด หาทุกชิ้นที่แทรกผ่าน DropIn แยกเป็นแท็บเฟอร์นิเจอร์ (จำนวน × ราคา) และวัสดุ (ตร.ม. × ราคา/ตร.ม.)", bullets: ["PDF A4 แนวนอน", "Excel .xlsx ผ่าน SheetJS", "ส่งออกแยกตามแบรนด์ลงโฟลเดอร์"] },
      { glyph: "↺", tag: "Smart Cache", title: "เปิดได้เร็วอยู่เสมอ", body: "ใช้ hash-diff เทียบดัชนี ไม่ render ซ้ำเมื่อแคตตาล็อกไม่เปลี่ยน และล้าง thumbnail เก่าทุกครั้งที่อัปเวอร์ชัน", bullets: ["แคช thumbnail บนดิสก์", "รีเฟรชเบื้องหลังทุกชั่วโมง", "เติมข้อมูล BOQ ย้อนหลังให้อัตโนมัติ"] }]

    },
    roadmap: {
      h1: "แผนพัฒนา",
      nodes: [
      { ver: "v1.0", ttl: "Showroom", stage: "เปิดตัวแล้ว", stageCls: "", body: "คอร์แคตตาล็อก BOQ และระบบแคช พร้อมแท็บ Showroom + Creative + Local รองรับ virtual scroll และรีเฟรชอัตโนมัติทุกชั่วโมง" },
      { ver: "v1.1", ttl: "Material", stage: "กำลังพัฒนา", stageCls: "now", body: "คลังเท็กซ์เจอร์ PBR จาก R2 cloud พร้อม pre-cache เบื้องหลัง ลดเวลาจาก 5–10 วิ เหลือ ~0.2 วิ และแท็บ BOQ วัสดุ" },
      { ver: "v1.2", ttl: "Collection", stage: "แผนถัดไป", stageCls: "soon", body: "ซิงค์ BOQ ขึ้นคลาวด์ต่อผู้ใช้ ฝังฟอนต์ไทยใน PDF (Noto Sans Thai) และรวมไอเทมข้ามคลังเป็น Bundle ของผู้ใช้" }],

      labelTab: (name) => "แท็บ " + name,
      kanban: [
      { h: "เปิดตัวแล้ว", ver: "v1.0", items: ["แคตตาล็อก Showroom", "คลัง Creative", "Local browser", "BOQ Exporter", "Disk cache + virtual scroll"] },
      { h: "กำลังพัฒนา", ver: "v1.1", items: ["แท็บ Material — PBR", "BOQ วัสดุ (ตร.ม. × ราคา/ตร.ม.)", "Worker แคชแบบขนาน", "ส่งออกแยกตามแบรนด์"] },
      { h: "แผนถัดไป", ver: "v1.2", items: ["ฟอนต์ไทยใน PDF", "ซิงค์ BOQ ขึ้นคลาวด์", "แท็บ Collection", "คลังระดับทีม", "Hook สำหรับ render engine"] }],

      milestones: [
      { stage: "กำลังทำ", ttl: "ฝังฟอนต์ไทยในไฟล์ PDF", note: "ฝัง Noto Sans Thai เพื่อให้ BOQ แสดงผลภาษาไทยได้ ไม่เป็นช่องสี่เหลี่ยมอีกต่อไป", cls: "now" },
      { stage: "ถัดไป", ttl: "ซิงค์ BOQ ขึ้นคลาวด์", note: "บันทึก snapshot ของ BOQ ขึ้นคลาวด์ตามอีเมลผู้ใช้ พร้อมคลังระดับทีม" },
      { stage: "ภายหลัง", ttl: "แท็บ Collection", note: "ให้ผู้ใช้รวมไอเทมข้าม Showroom, Creative, Local และ Material เป็นชุดของตัวเอง" },
      { stage: "ไอเดีย", ttl: "Hook วัสดุ render engine", note: "สลับ albedo เป็นวัสดุ V-Ray / Enscape เมื่อมีการติดตั้งปลั๊กอินไว้แล้ว" }]

    },
    change: {
      h1: "บันทึกอัปเดต",
      tagClass: { "เพิ่ม": "added", "แก้ไข": "fixed", "เปลี่ยน": "changed", "ปรับโครงสร้าง": "refactored", "ลบ": "removed" },
      log: [
      { v: "v1.1.0", d: "17 พ.ค. 2569",
        sections: [
        ["เพิ่ม", ["Worker แคชเบื้องหลังแบบขนาน — โหลดล่วงหน้าได้เร็วขึ้น 2 เท่า สำหรับ Showroom, Creative และ Material"]],
        ["แก้ไข", ["BOQ overlay: ย้ายปุ่มปิดไปมุมขวาบนแบบ absolute"]]]
      },
      { v: "v1.1.0-rc", d: "15 พ.ค. 2569",
        sections: [
        ["เพิ่ม", [
        "แท็บ Material BOQ: คิดราคาตามพื้นที่ (ตร.ม. × ราคา/ตร.ม.) ส่งออกได้ทั้ง PDF / Excel / แยกตามแบรนด์",
        "ปุ่มแทรก (↓) และปุ่มถูกใจ (♡) บนการ์ดวัสดุแบบ inline",
        "ปุ่มรีเฟรช (↺) บนแถบเครื่องมือทั้ง 4 แท็บ — บังคับโหลดใหม่ทั้งหมด"]],

        ["เปลี่ยน", [
        "ข้อตกลงการใช้งาน: ขึ้นแท็บ EN ก่อน พร้อมรายการข้อห้าม 6 ข้อ",
        "ตัวจัดการไลเซนส์: ตัดบรรทัด VERIFIED ออกจากการ์ดข้อมูล",
        'แท็บ Material: เปลี่ยนชื่อปุ่ม "Apply to face" เป็น "↓ Insert"']],

        ["ปรับโครงสร้าง", ["แยกไฟล์ dialog.rb (1,662 บรรทัด) ออกเป็น 7 ไฟล์ตามโดเมน โดยไม่มีการเปลี่ยน behavior"]]]
      },
      { v: "v1.0.0", d: "13 พ.ค. 2569",
        sections: [
        ["เพิ่ม", [
        "แท็บ Material (◑): คลังเท็กซ์เจอร์ PBR จาก R2 cloud",
        "โหลดวัสดุล่วงหน้าเบื้องหลัง — เวลาทาวัสดุ 5–10 วิ → ~0.2 วิ",
        "รีเฟรชอัตโนมัติทุกชั่วโมง สำหรับ Showroom, Creative และ Material",
        "ล้างแคชตามเวอร์ชัน — thumbnail เก่าถูกล้างอัตโนมัติเมื่ออัปเวอร์ชัน"]]]

      },
      { v: "v0.9.0", d: "11 พ.ค. 2569",
        sections: [
        ["เพิ่ม", [
        "Virtual scrolling สำหรับ Showroom + Creative — ไม่หน่วงแม้มี 500+ การ์ด",
        "แท็บ Local: สร้าง thumbnail เบื้องหลังโดยไม่ค้างจอ"]],

        ["ปรับโครงสร้าง", [
        "แยกไฟล์ app.js (3,751 บรรทัด) เป็น 6 ไฟล์",
        "แยก index.html ออกเป็น HTML + style.css + app.js"]]]

      }]

    },
    download: {
      h1: "ดาวน์โหลด",
      version: "v1.2.0", relDate: "19 พ.ค. 2569",
      filenameWin: "DropIn_Setup_v1.2.0.exe", formatWin: "ตัวติดตั้ง .exe สำหรับ Windows",
      relLabel: "เผยแพร่",
      ctaWinHero: "⤓ ดาวน์โหลดสำหรับ Windows",
      ctaWin: "⤓ ดาวน์โหลดไฟล์ .exe",
      installTitle: "วิธีติดตั้ง",
      installSteps: [
      "ดาวน์โหลดไฟล์ DropIn_Setup_v1.2.0.exe",
      "เปิดตัวติดตั้ง (ไม่ต้องสิทธิ์ผู้ดูแลระบบ)",
      "ทำตามขั้นตอนบนหน้าจอ — ปลั๊กอินจะติดตั้งให้อัตโนมัติ",
      "เปิดหรือรีสตาร์ท SketchUp",
      "เข้าใช้งานผ่าน Extensions > DropIn หรือปุ่มบนทูลบาร์"],

      macTitle: (v) => "DropIn " + v + " — macOS",
      macFormat: "ตัวติดตั้ง .pkg · macOS 12+ · อยู่ระหว่างพัฒนา",
      macFilename: "DropIn_macOS_v1.2.0.pkg",
      macCta: "⤓ แจ้งเตือนเมื่อพร้อม",
      macBadge: "เร็ว ๆ นี้",
      macListTitle: "สิ่งที่กำลังเตรียม",
      macList: [
      "ตัวติดตั้งเนทีฟ .pkg ติดตั้งให้อัตโนมัติ ไม่ต้องผ่าน Extension Manager",
      "รองรับ Intel และ Apple Silicon (Universal Binary)",
      "เข้าใช้ผ่าน Extensions > DropIn เหมือนเวอร์ชัน Windows",
      "อัปเดตอัตโนมัติเมื่อเปิด SketchUp",
      "คาดว่าเปิดให้ดาวน์โหลดไตรมาสที่ 4 ปี 2569"],

      compatHead: "เวอร์ชัน SketchUp ที่รองรับ",
      compatNote: "แผนที่ PBR (normal, roughness, metallic, AO) ต้องใช้ SketchUp 2025 ขึ้นไป สำหรับเวอร์ชันเก่า ระบบจะใส่ albedo ให้แทน โดยไม่แจ้ง error",
      compat: [
      ["2026", "รองรับเต็มรูปแบบ + วัสดุ PBR", "pbr"],
      ["2025", "รองรับเต็มรูปแบบ + วัสดุ PBR", "pbr"],
      ["2024", "รองรับเต็มรูปแบบ", "ok"],
      ["2023", "รองรับเต็มรูปแบบ", "ok"],
      ["2022", "รองรับเต็มรูปแบบ", "ok"],
      ["2021", "รองรับเต็มรูปแบบ", "ok"],
      ["2020", "รองรับเต็มรูปแบบ", "ok"],
      ["2019 ลงไป", "ไม่รองรับ", "no"]]

    },
    contact: {
      h1: "ติดต่อเรา",
      list: [
      { name: "Instagram", handle: "Drop.__.In", note: "อัปเดตเรนเดอร์และเบื้องหลังทุกวัน", url: "https://www.instagram.com/drop.__.in?igsh=Nng5dmwwYmE4MTlp&utm_source=qr" },
      { name: "Facebook", handle: "DropIn", note: "ประกาศเวอร์ชันใหม่ และตอบคำถามชุมชน", url: "https://www.facebook.com/profile.php?id=61568038913118" },
      { name: "TikTok", handle: "dropin57", note: "คลิปสอนใช้งานสั้น 30 วินาที", url: "https://tiktok.com/@dropin57" },
      { name: "LINE", handle: "dropin0000", note: "ติดต่อทีมซัพพอร์ตโดยตรง · TH/EN", url: "https://line.me/ti/p/C4awKCccKw" },
      { name: "อีเมล", handle: "dropin0000@gmail.com", note: "ไลเซนส์ พาร์ตเนอร์ และสื่อมวลชน", url: "mailto:dropin0000@gmail.com" },
      { name: "โทรศัพท์", handle: "063 658 2022", note: "จันทร์–ศุกร์ · 09:00–18:00 น.", url: "tel:+66636582022" }]

    },
    tweaks: {
      title: "Tweaks",
      navigate: "ไปยังหน้า", page: "หน้า",
      aboutLayout: "เลย์เอาต์หน้าเกี่ยวกับ", featuresLayout: "เลย์เอาต์ฟีเจอร์",
      roadmapLayout: "เลย์เอาต์แผนพัฒนา", changelogLayout: "เลย์เอาต์บันทึกอัปเดต",
      downloadsLayout: "เลย์เอาต์ดาวน์โหลด", contactsLayout: "เลย์เอาต์ติดต่อ",
      display: "การแสดงผล", gridPaper: "กระดาษกริด",
      opt: { hero: "Hero", compare: "เปรียบเทียบ", three: "3 คอลัมน์", two: "2 คอลัมน์", list: "รายการ",
        timeline: "Timeline", kanban: "Kanban", milestones: "รายการ",
        latest: "ล่าสุด", all: "ทั้งหมด", twoCol: "2 คอลัมน์", single: "เดี่ยว",
        grid: "กริด", listV: "รายการ" }
    }
  }
};

/* ============ Dolphin logo ============ */
function Dolphin({ size = 60 }) {
  return <img src="logo.svg" alt="DropIn" width={size} height={size} style={{ display: "block" }} draggable={false} />;
}

/* ============ Sidebar ============ */
const NAV_IDS = ["about", "features", "roadmap", "change", "download", "contact"];

function Sidebar({ active, onPick, lang, onLang, t }) {
  return (
    <aside className="sidebar">
      <div className="logo-circle"><Dolphin size={68} /></div>
      <div className="logo-title" style={{ width: "94px", fontSize: "12px" }}>{t.studio}</div>
      <nav className="nav">
        {NAV_IDS.map((id) =>
        <button key={id}
        className={active === id ? "active" : ""}
        onClick={() => onPick(id)}>{t.nav[id]}</button>
        )}
      </nav>
      <div className="lang">
        <span className={lang === "en" ? "on" : ""} onClick={() => onLang("en")}>ENG</span>
        <span className={lang === "th" ? "on" : ""} onClick={() => onLang("th")}>TH</span>
      </div>
    </aside>);

}

/* ============ Page: About ============ */
function PageAbout({ variant, t }) {
  if (variant === "compare") {
    return (
      <>
        <h1>{t.about.h1Compare}</h1>
        <div className="compare">
          <div className="compare-head"><div>{t.about.colA}</div><div className="hl">{t.about.colB}</div></div>
          {t.about.rows.map(([a, b], i) =>
          <div className="compare-row" key={i}>
              <div className="cell">{a}</div>
              <div className="cell hl">{b}</div>
            </div>
          )}
        </div>
      </>);

  }
  const thumbs = t.about.thumbs;
  return (
    <div className="about-wrap">
      <div className="about-logo"><Dolphin size={180} /></div>
      <div className="about-title">{t.studio}</div>
      <div className="about-sub">{t.about.heroSub}</div>
      <p className="about-desc">{t.about.heroDesc}</p>

      <div className="about-thumbs">
        <div className="about-thumbs-track">
          {[...thumbs, ...thumbs].map((it, i) =>
          <div className="about-thumb" key={i}>
            <img src={it.src} alt={it.label} loading="lazy" draggable="false" />
          </div>
          )}
        </div>
      </div>
    </div>);

}

/* ============ Page: Features ============ */
function PageFeatures({ variant, t }) {
  const gridCls =
  variant === "two" ? "features-grid cols-2" :
  variant === "list" ? "features-grid list" :
  "features-grid";
  return (
    <>
      <h1>{t.features.h1}</h1>
      <div className={gridCls}>
        {t.features.cards.map((f, i) =>
        <div className="feat-card" key={i}>
            <div className="feat-head">
              <span className="feat-glyph">{f.glyph}</span>
              <h3>{f.tag}</h3>
            </div>
            <div className="feat-title">{f.title}</div>
            <p className="feat-body">{f.body}</p>
            <ul className="feat-bullets">
              {f.bullets.map((b, j) => <li key={j}>{b}</li>)}
            </ul>
          </div>
        )}
      </div>
    </>);

}

/* ============ Page: Roadmap ============ */
function PageRoadmap({ variant, t }) {
  const r = t.roadmap;
  if (variant === "kanban") {
    return (
      <>
        <h1>{r.h1}</h1>
        <div className="road-kanban">
          {r.kanban.map((col, i) =>
          <div className="kanban-col" key={i}>
              <h3>{col.h} <span className="kbn-ver">{col.ver}</span></h3>
              {col.items.map((item, k) =>
            <div className={"kanban-card" + (i === 2 ? " dashed" : "")} key={k}>
                  <div className="ttl">{item}</div>
                </div>
            )}
            </div>
          )}
        </div>
      </>);

  }
  if (variant === "milestones") {
    return (
      <>
        <h1>{r.h1}</h1>
        <div className="road-vlist">
          {r.milestones.map((row, i) =>
          <div className="vlist-row" key={i}>
              <div className={"vlist-stage" + (row.cls ? " " + row.cls : "")}>{row.stage}</div>
              <div className="vlist-body">
                <div className="ttl">{row.ttl}</div>
                <div className="note">{row.note}</div>
              </div>
            </div>
          )}
        </div>
      </>);

  }

  const positions = [18, 50, 82];
  const nodes = r.nodes;
  return (
    <>
      <h1>{r.h1}</h1>
      <div className="road-rail-wrap">
        <div className="road-rail">
          <span className="dot minor" style={{ left: "4%" }}></span>
          <span className="dot minor" style={{ left: "9%" }}></span>
          <span className="dot minor" style={{ left: "14%" }}></span>

          <div className={"dot major" + (nodes[0].stageCls === "now" ? " now" : "")} style={{ left: "18%" }}>
            <div>
              <span className="ver">{nodes[0].ver}</span>
              <span className="ttl">{nodes[0].ttl}</span>
            </div>
          </div>

          <span className="dot minor" style={{ left: "26%" }}></span>
          <span className="dot minor" style={{ left: "34%" }}></span>
          <span className="dot minor" style={{ left: "42%" }}></span>

          <div className={"dot major" + (nodes[1].stageCls === "now" ? " now" : "")} style={{ left: "50%" }}>
            <div>
              <span className="ver">{nodes[1].ver}</span>
              <span className="ttl">{nodes[1].ttl}</span>
            </div>
          </div>

          <span className="dot minor" style={{ left: "58%" }}></span>
          <span className="dot minor" style={{ left: "66%" }}></span>
          <span className="dot minor" style={{ left: "74%" }}></span>

          <div className="dot major dashed" style={{ left: "82%" }}>
            <div>
              <span className="ver">{nodes[2].ver}</span>
              <span className="ttl">{nodes[2].ttl}</span>
            </div>
          </div>

          <span className="dot minor" style={{ left: "86%" }}></span>
          <span className="dot minor" style={{ left: "91%" }}></span>
          <span className="dot minor" style={{ left: "96%" }}></span>
        </div>

        <div className="road-labels">
          {nodes.map((n, i) =>
          <div className="rl-col" style={{ left: positions[i] + "%" }} key={i}>
              <div className={"stage" + (n.stageCls === "now" ? " now" : "")}>{n.stage}</div>
              <div className="name">{r.labelTab(n.ttl)}</div>
              <p className="rl-body">{n.body}</p>
            </div>
          )}
        </div>
      </div>
    </>);

}

/* ============ Page: Changelog ============ */
function PageChangelog({ variant, t }) {
  const c = t.change;
  const entries = variant === "single" ? c.log.slice(0, 1) : c.log;
  return (
    <>
      <h1>{c.h1}</h1>
      <div className="change-wrap">
        {entries.map((e, i) =>
        <div className="change-card" key={i}>
            <div className="change-head">
              <div className="ver">{e.v}</div>
              <div className="date">{e.d}</div>
            </div>
            {e.sections.map(([tag, items], j) =>
          <div className="change-section" key={j}>
                <div className={"change-tag " + (c.tagClass[tag] || "")}>{tag}</div>
                <ul className="change-list">
                  {items.map((it, k) => <li key={k}>{it}</li>)}
                </ul>
              </div>
          )}
          </div>
        )}
      </div>
    </>);

}

/* ============ Page: Downloads ============ */
function PageDownloads({ variant, t }) {
  const d = t.download;
  if (variant === "single") {
    return (
      <>
        <h1>{d.h1}</h1>
        <div className="dl-hero">
          <div className="dl-hero-meta">
            <div className="dl-version">DropIn {d.version}</div>
            <div className="dl-format">{d.formatWin} · Windows · {d.relLabel} {d.relDate}</div>
            <div className="dl-filename">{d.filenameWin}</div>
          </div>
          <a className="dl-cta" href={`downloads/${d.filenameWin}`} download={d.filenameWin}>{d.ctaWinHero}</a>
        </div>
        <DLCompat t={t} />
      </>);

  }
  return (
    <>
      <h1>{d.h1}</h1>
      <div className="dl-wrap">
        <div className="dl-col">
          <div className="dl-tab">Windows</div>
          <div className="dl-frame">
            <div className="dl-version">DropIn {d.version} — Windows</div>
            <div className="dl-format">{d.formatWin} · Win 10 / 11 · {d.relLabel} {d.relDate}</div>
            <div className="dl-filename">{d.filenameWin}</div>
            <a className="dl-cta" href={`downloads/${d.filenameWin}`} download={d.filenameWin}>{d.ctaWin}</a>
            <div className="dl-install-title">{d.installTitle}</div>
            <ol className="dl-install">
              {d.installSteps.map((s, i) => <li key={i}>{s}</li>)}
            </ol>
          </div>
        </div>
        <div className="dl-col">
          <div className="dl-tab muted">macOS</div>
          <div className="dl-frame coming-soon">
            <span className="dl-soon-badge dl-soon-badge-corner">{d.macBadge}</span>
            <div className="dl-version">{d.macTitle(d.version)}</div>
            <div className="dl-format">{d.macFormat}</div>
            <div className="dl-filename dl-filename-muted">{d.macFilename}</div>
            <button className="dl-cta secondary" disabled>{d.macCta}</button>
            <div className="dl-install-title">{d.macListTitle}</div>
            <ol className="dl-install">
              {d.macList.map((s, i) => <li key={i}>{s}</li>)}
            </ol>
          </div>
        </div>
        <div className="dl-compat-wrap"><DLCompat t={t} /></div>
      </div>
    </>);

}

function DLCompat({ t }) {
  const d = t.download;
  return (
    <div className="dl-compat">
      <div className="dl-compat-head">{d.compatHead}</div>
      <div className="dl-compat-grid">
        {d.compat.map(([yr, label, st], i) =>
        <div className={"dl-compat-cell " + st} key={i}>
            <div className="yr">{yr}</div>
            <div className="lb">{label}</div>
          </div>
        )}
      </div>
      <div className="dl-note">{d.compatNote}</div>
    </div>);

}

/* ============ Page: Contacts ============ */
function IconIG() {return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="5" /><circle cx="12" cy="12" r="4" /><circle cx="17.5" cy="6.5" r="0.8" fill="currentColor" /></svg>;}
function IconFB() {return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M15 4h-2a3 3 0 0 0-3 3v3H7v3h3v8h3v-8h2.5l.5-3H13V7.5a.5.5 0 0 1 .5-.5H15V4z" /></svg>;}
function IconTT() {return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M14 4v9.5a3.5 3.5 0 1 1-3.5-3.5" /><path d="M14 4c.5 2.5 2.5 4 5 4" /></svg>;}
function IconLine() {return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M21 10.5c0-4.1-4-7.5-9-7.5s-9 3.4-9 7.5c0 3.7 3.3 6.8 7.7 7.4l-.7 3 4-2.5c4-.2 7-3 7-7.9z" /><path d="M8 8.5v4M8 12.5h2.4M13 8.5v4M13 8.5l2.2 4V8.5" /></svg>;}
function IconMail() {return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="5" width="18" height="14" rx="2" /><path d="M3 7l9 6 9-6" /></svg>;}
function IconPhone() {return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M5 4h3l2 5-2.5 1.5a11 11 0 0 0 6 6L15 14l5 2v3a2 2 0 0 1-2 2A15 15 0 0 1 3 6a2 2 0 0 1 2-2z" /></svg>;}
const CONTACT_ICONS = [IconIG, IconFB, IconTT, IconLine, IconMail, IconPhone];

function PageContacts({ variant, t }) {
  const c = t.contact;
  const linkProps = (url) => ({
    href: url || "#",
    target: url && !/^(mailto:|tel:)/.test(url) ? "_blank" : undefined,
    rel: url && !/^(mailto:|tel:)/.test(url) ? "noopener noreferrer" : undefined
  });
  if (variant === "list") {
    return (
      <>
        <h1>{c.h1}</h1>
        <div className="contact-list">
          {c.list.map((row, i) => {
            const Icon = CONTACT_ICONS[i];
            return (
              <a className="contact-row contact-link" key={i} {...linkProps(row.url)}>
                <div className="contact-row-head">
                  <span className="contact-icon"><Icon /></span>
                  <div className="label">{row.name}</div>
                </div>
                <div className="ph">
                  <div className="handle">{row.handle}</div>
                  <div className="note">{row.note}</div>
                </div>
              </a>);

          })}
        </div>
      </>);

  }
  return (
    <>
      <h1>{c.h1}</h1>
      <div className="contact-grid">
        {c.list.map((row, i) => {
          const Icon = CONTACT_ICONS[i];
          return (
            <a className="contact-cell contact-link" key={i} {...linkProps(row.url)} style={{ height: "160px", width: "200px" }}>
              <span className="contact-icon"><Icon /></span>
              <div className="label">{row.name}</div>
              <div className="handle">{row.handle}</div>
              <div className="note">{row.note}</div>
            </a>);

        })}
      </div>
    </>);

}

/* ============ App shell ============ */
const DEFAULT_LANG = typeof window !== "undefined" && window.__DROPIN_LANG || (
(document.documentElement.lang || "en").toLowerCase().startsWith("th") ? "th" : "en");

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "page": "about",
  "aboutLayout": "hero",
  "featuresLayout": "three",
  "roadmapLayout": "timeline",
  "changelogLayout": "stack",
  "downloadsLayout": "two",
  "contactsLayout": "grid",
  "showGrid": true,
  "lang": "en"
} /*EDITMODE-END*/;

function App() {
  const [tw, setTweak] = (window.useTweaks || (() => [TWEAK_DEFAULTS, () => {}]))(TWEAK_DEFAULTS);
  const lang = tw.lang === "en" || tw.lang === "th" ? tw.lang : DEFAULT_LANG;
  const t = T[lang];

  // Reflect lang on <html> + document head so fonts/locale follow the toggle
  useEffect(() => {
    document.documentElement.lang = lang;
    document.title = t.docTitle;
    const dh = document.querySelector(".doc-head .t");
    if (dh) dh.textContent = t.docTitle;
    const ds = document.querySelector(".doc-head .s");
    if (ds) ds.textContent = t.docSub;
  }, [lang]);

  const pageBody = (() => {
    switch (tw.page) {
      case "features":return <PageFeatures variant={tw.featuresLayout} t={t} />;
      case "roadmap":return <PageRoadmap variant={tw.roadmapLayout} t={t} />;
      case "change":return <PageChangelog variant={tw.changelogLayout} t={t} />;
      case "download":return <PageDownloads variant={tw.downloadsLayout} t={t} />;
      case "contact":return <PageContacts variant={tw.contactsLayout} t={t} />;
      default:return <PageAbout variant={tw.aboutLayout} t={t} />;
    }
  })();

  const pageIdx = NAV_IDS.indexOf(tw.page) + 1 || 1;
  const pageLabel = t.nav[NAV_IDS[pageIdx - 1]] || t.nav.about;
  const frameCls = ["frame", tw.showGrid ? "" : "no-grid"].join(" ").trim();

  const Tw = t.tweaks;
  return (
    <>
      <div className={frameCls} data-screen-label={`0${pageIdx} ${pageLabel}`}>
        <div className="grid-bg"></div>
        <div className="banner"><span className="inner">{t.banner}</span></div>
        <Sidebar
          active={tw.page}
          onPick={(id) => setTweak("page", id)}
          lang={lang}
          onLang={(L) => setTweak("lang", L)}
          t={t} />
        
        <div className="canvas">{pageBody}</div>
      </div>

      {window.TweaksPanel &&
      <window.TweaksPanel title={Tw.title}>
          <window.TweakSection title={Tw.navigate}>
            <window.TweakSelect label={Tw.page} value={tw.page}
          onChange={(v) => setTweak("page", v)}
          options={NAV_IDS.map((id) => ({ value: id, label: t.nav[id] }))} />
            <window.TweakRadio value={lang}
          onChange={(v) => setTweak("lang", v)}
          options={[
          { value: "en", label: "English" },
          { value: "th", label: "ไทย" }]
          } />
          </window.TweakSection>

          {tw.page === "about" &&
        <window.TweakSection title={Tw.aboutLayout}>
              <window.TweakRadio value={tw.aboutLayout}
          onChange={(v) => setTweak("aboutLayout", v)}
          options={[
          { value: "hero", label: Tw.opt.hero },
          { value: "compare", label: Tw.opt.compare }]
          } />
            </window.TweakSection>
        }
          {tw.page === "features" &&
        <window.TweakSection title={Tw.featuresLayout}>
              <window.TweakRadio value={tw.featuresLayout}
          onChange={(v) => setTweak("featuresLayout", v)}
          options={[
          { value: "three", label: Tw.opt.three },
          { value: "two", label: Tw.opt.two },
          { value: "list", label: Tw.opt.list }]
          } />
            </window.TweakSection>
        }
          {tw.page === "roadmap" &&
        <window.TweakSection title={Tw.roadmapLayout}>
              <window.TweakRadio value={tw.roadmapLayout}
          onChange={(v) => setTweak("roadmapLayout", v)}
          options={[
          { value: "timeline", label: Tw.opt.timeline },
          { value: "kanban", label: Tw.opt.kanban },
          { value: "milestones", label: Tw.opt.milestones }]
          } />
            </window.TweakSection>
        }
          {tw.page === "change" &&
        <window.TweakSection title={Tw.changelogLayout}>
              <window.TweakRadio value={tw.changelogLayout}
          onChange={(v) => setTweak("changelogLayout", v)}
          options={[
          { value: "single", label: Tw.opt.latest },
          { value: "stack", label: Tw.opt.all }]
          } />
            </window.TweakSection>
        }
          {tw.page === "download" &&
        <window.TweakSection title={Tw.downloadsLayout}>
              <window.TweakRadio value={tw.downloadsLayout}
          onChange={(v) => setTweak("downloadsLayout", v)}
          options={[
          { value: "two", label: Tw.opt.twoCol },
          { value: "single", label: Tw.opt.single }]
          } />
            </window.TweakSection>
        }
          {tw.page === "contact" &&
        <window.TweakSection title={Tw.contactsLayout}>
              <window.TweakRadio value={tw.contactsLayout}
          onChange={(v) => setTweak("contactsLayout", v)}
          options={[
          { value: "grid", label: Tw.opt.grid },
          { value: "list", label: Tw.opt.listV }]
          } />
            </window.TweakSection>
        }

          <window.TweakSection title={Tw.display}>
            <window.TweakToggle label={Tw.gridPaper}
          value={tw.showGrid}
          onChange={(v) => setTweak("showGrid", v)} />
          </window.TweakSection>
        </window.TweaksPanel>
      }
    </>);

}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);