MediaWiki:Common.js: Difference between revisions

From Erakh Codex
Jump to navigation Jump to search
Juoh (talk | contribs)
No edit summary
No edit summary
 
(24 intermediate revisions by 2 users not shown)
Line 1: Line 1:
(function () {
(function () {
  /* --- Google Fonts 강제 로드 --- */
   var fontLink = document.createElement("link");
   var fontLink = document.createElement("link");
   fontLink.rel = "stylesheet";
   fontLink.rel = "stylesheet";
Line 6: Line 5:
   document.head.insertBefore(fontLink, document.head.firstChild);
   document.head.insertBefore(fontLink, document.head.firstChild);


  /* --- 쿠키 읽기 --- */
   function getCookie(name) {
   function getCookie(name) {
     var match = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)"));
     var match = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)"));
Line 12: Line 10:
   }
   }


  /* --- 유저 정보 파싱 --- */
   var user = null;
   var user = null;
   try {
   try {
Line 19: Line 16:
   } catch (e) {}
   } catch (e) {}


   /* --- 인라인 스타일 헬퍼 --- */
   var pageName = mw.config.get("wgPageName");
  function s(el, styles) {
  var isSysop  = (mw.config.get("wgUserGroups") || []).indexOf("sysop") !== -1;
     Object.keys(styles).forEach(function(k) { el.style[k] = styles[k]; });
 
  // proactive 토큰 갱신
  if (user && user.exp) {
    var now = Math.floor(Date.now() / 1000);
    var remaining = user.exp - now;
    if (remaining < 5 * 60) {
      fetch("https://erakh.com/api/auth/refresh", { method: "POST", credentials: "include" })
        .catch(function() {});
     } else {
      var delay = (remaining - 5 * 60) * 1000;
      setTimeout(function() {
        fetch("https://erakh.com/api/auth/refresh", { method: "POST", credentials: "include" })
          .catch(function() {});
      }, delay);
    }
  }
 
  function sp(el, prop, val) {
    el.style.setProperty(prop, val, "important");
   }
   }


   /* --- 헤더 --- */
   // ─── 헤더 ───
   var header = document.createElement("header");
   var header = document.createElement("header");
   s(header, {
   header.id = "erakh-header";
    position: "fixed",
  sp(header, "position", "fixed");
    top: "0",
  sp(header, "top", "0");
    left: "0",
  sp(header, "left", "0");
    right: "0",
  sp(header, "right", "0");
    zIndex: "9999",
  sp(header, "z-index", "9999");
    width: "100%",
  sp(header, "width", "100%");
    background: "#f9f6f0",
  sp(header, "background", "#f9f6f0");
    borderBottom: "1px solid #e2d9c8",
  sp(header, "border-bottom", "1px solid #e2d9c8");
    display: "flex",
  sp(header, "display", "flex");
    alignItems: "center",
  sp(header, "align-items", "center");
    justifyContent: "space-between",
  sp(header, "justify-content", "space-between");
    padding: "0 2rem",
  sp(header, "padding", "0 2rem");
    height: "56px",
  sp(header, "height", "56px");
    boxSizing: "border-box"
  sp(header, "box-sizing", "border-box");
  });


  /* --- 좌측 --- */
   var left = document.createElement("div");
   var left = document.createElement("div");
   s(left, {
   sp(left, "display", "flex");
    display: "flex",
  sp(left, "align-items", "center");
    alignItems: "center",
  sp(left, "gap", "1.75rem");
    gap: "1.75rem"
  });


   var logo = document.createElement("a");
   var logo = document.createElement("a");
   logo.href = "https://erakh.com";
   logo.href = "https://erakh.com";
   logo.textContent = "Erakh Archive";
   logo.textContent = "Erakh Archive";
   logo.style.setProperty("font-family", "'Inter', sans-serif", "important");
   sp(logo, "font-family", "'Inter', sans-serif");
   logo.style.setProperty("font-size", "1.125rem", "important");
   sp(logo, "font-size", "1.125rem");
   logo.style.setProperty("font-weight", "500", "important");
   sp(logo, "font-weight", "500");
   logo.style.setProperty("color", "#2a2218", "important");
   sp(logo, "color", "#2a2218");
   logo.style.setProperty("text-decoration", "none", "important");
   sp(logo, "text-decoration", "none");
   logo.style.setProperty("letter-spacing", "0.05em", "important");
   sp(logo, "letter-spacing", "-0.01em");
   logo.style.setProperty("text-transform", "none", "important");
   sp(logo, "text-transform", "none");
   logo.style.setProperty("font-variant", "normal", "important");
   sp(logo, "font-variant", "normal");
   logo.style.setProperty("font-variant-caps", "normal", "important");
   sp(logo, "font-variant-caps", "normal");
   logo.style.setProperty("font-feature-settings", "normal", "important");
   sp(logo, "line-height", "1");
  logo.style.setProperty("line-height", "1", "important");


   var nav = document.createElement("nav");
   var nav = document.createElement("nav");
   s(nav, { display: "flex", alignItems: "center", gap: "1.5rem" });
   sp(nav, "display", "flex");
  sp(nav, "align-items", "center");
  sp(nav, "gap", "1.5rem");


   var codexLink = document.createElement("a");
   var codexLink = document.createElement("a");
   codexLink.href = "https://codex.erakh.com";
   codexLink.href = "https://codex.erakh.com";
   codexLink.textContent = "Codex";
   codexLink.textContent = "Codex";
   s(codexLink, {
   sp(codexLink, "font-family", "'Inter', sans-serif");
    fontFamily: "'EB Garamond', serif",
  sp(codexLink, "font-size", "1rem");
    fontSize: "1rem",
  sp(codexLink, "color", "#6b5e4e");
    color: "#6b5e4e",
  sp(codexLink, "text-decoration", "none");
    textDecoration: "none",
  sp(codexLink, "text-transform", "none");
    textTransform: "none",
  sp(codexLink, "font-variant", "normal");
    fontVariant: "normal",
  sp(codexLink, "line-height", "1");
    lineHeight: "1"
   codexLink.addEventListener("mouseover", function() { this.style.setProperty("color", "#2a2218", "important"); });
  });
   codexLink.addEventListener("mouseout",  function() { this.style.setProperty("color", "#6b5e4e", "important"); });
   codexLink.addEventListener("mouseover", function() { this.style.color = "#2a2218"; });
   codexLink.addEventListener("mouseout",  function() { this.style.color = "#6b5e4e"; });


   nav.appendChild(codexLink);
   nav.appendChild(codexLink);
Line 88: Line 98:
   left.appendChild(nav);
   left.appendChild(nav);


  /* --- 우측 --- */
   var right = document.createElement("div");
   var right = document.createElement("div");
   s(right, { display: "flex", alignItems: "center" });
   sp(right, "display", "flex");
  sp(right, "align-items", "center");


   if (user && user.email) {
   if (user && user.nickname) {
    /* 로그인 — 아바타 + 드롭다운 */
     var profile = document.createElement("div");
     var profile = document.createElement("div");
     s(profile, { position: "relative" });
     sp(profile, "position", "relative");


     var avatar = document.createElement("div");
     var avatar = document.createElement("div");
     avatar.textContent = user.email.slice(0, 1).toUpperCase();
     avatar.textContent = user.nickname.slice(0, 1).toUpperCase();
     s(avatar, {
     sp(avatar, "width", "32px");
      width: "32px",
    sp(avatar, "height", "32px");
      height: "32px",
    sp(avatar, "border-radius", "50%");
      borderRadius: "50%",
    sp(avatar, "background", "#e2d9c8");
      background: "#e2d9c8",
    sp(avatar, "border", "1px solid #c8b99a");
      border: "1px solid #c8b99a",
    sp(avatar, "display", "flex");
      display: "flex",
    sp(avatar, "align-items", "center");
      alignItems: "center",
    sp(avatar, "justify-content", "center");
      justifyContent: "center",
    sp(avatar, "cursor", "pointer");
      cursor: "pointer",
    sp(avatar, "font-family", "'Inter', sans-serif");
      fontFamily: "'Cinzel', serif",
    sp(avatar, "font-size", "0.75rem");
      fontSize: "0.75rem",
    sp(avatar, "font-weight", "500");
      color: "#2a2218",
    sp(avatar, "color", "#2a2218");
      boxSizing: "border-box",
    sp(avatar, "box-sizing", "border-box");
      fontVariant: "normal",
      textTransform: "none"
    });


     var dropdown = document.createElement("div");
     var dropdown = document.createElement("div");
     s(dropdown, {
     sp(dropdown, "display", "none");
      display: "none",
    sp(dropdown, "position", "absolute");
      position: "absolute",
    sp(dropdown, "top", "calc(100% + 8px)");
      top: "calc(100% + 8px)",
    sp(dropdown, "right", "0");
      right: "0",
    sp(dropdown, "background", "#f9f6f0");
      background: "#f9f6f0",
    sp(dropdown, "border", "1px solid #e2d9c8");
      border: "1px solid #e2d9c8",
    sp(dropdown, "min-width", "220px");
       minWidth: "180px",
    sp(dropdown, "padding", "0");
       padding: "0.5rem 0",
    sp(dropdown, "box-shadow", "0 4px 16px rgba(0,0,0,0.06)");
       boxShadow: "0 4px 16px rgba(0,0,0,0.06)",
    sp(dropdown, "z-index", "10000");
       zIndex: "10000",
    sp(dropdown, "box-sizing", "border-box");
       boxSizing: "border-box"
 
     });
    var dpProfile = document.createElement("div");
    sp(dpProfile, "padding", "1rem");
    sp(dpProfile, "display", "flex");
    sp(dpProfile, "flex-direction", "column");
    sp(dpProfile, "align-items", "center");
    sp(dpProfile, "gap", "0.25rem");
    sp(dpProfile, "border-bottom", "1px solid #e2d9c8");
 
    var dpAvatar = document.createElement("div");
    dpAvatar.textContent = user.nickname.slice(0, 1).toUpperCase();
    sp(dpAvatar, "width", "44px");
    sp(dpAvatar, "height", "44px");
    sp(dpAvatar, "border-radius", "50%");
    sp(dpAvatar, "background", "#f0ebe0");
    sp(dpAvatar, "border", "1px solid #c8b99a");
    sp(dpAvatar, "display", "flex");
    sp(dpAvatar, "align-items", "center");
    sp(dpAvatar, "justify-content", "center");
    sp(dpAvatar, "font-family", "'Inter', sans-serif");
    sp(dpAvatar, "font-size", "1rem");
    sp(dpAvatar, "font-weight", "500");
    sp(dpAvatar, "color", "#c8a060");
    sp(dpAvatar, "margin-bottom", "0.25rem");
 
    var dpName = document.createElement("div");
    dpName.textContent = user.nickname;
    sp(dpName, "font-family", "'Inter', sans-serif");
    sp(dpName, "font-size", "0.875rem");
    sp(dpName, "font-weight", "500");
    sp(dpName, "color", "#2a2218");
 
    var dpTitle = document.createElement("div");
    dpTitle.textContent = user.title || "Chronicler";
    sp(dpTitle, "font-family", "'Inter', sans-serif");
    sp(dpTitle, "font-size", "0.6875rem");
    sp(dpTitle, "color", "#9c8e80");
    sp(dpTitle, "font-style", "italic");
 
    dpProfile.appendChild(dpAvatar);
    dpProfile.appendChild(dpName);
    dpProfile.appendChild(dpTitle);
 
    var dpStats = document.createElement("div");
    sp(dpStats, "display", "grid");
    sp(dpStats, "grid-template-columns", "1fr 1fr");
    sp(dpStats, "border-bottom", "1px solid #e2d9c8");
 
    function makeStatCell(val, lbl, borderRight, borderBottom) {
       var cell = document.createElement("div");
       sp(cell, "padding", "0.5rem 0");
      sp(cell, "text-align", "center");
       if (borderRight) sp(cell, "border-right", "1px solid #e2d9c8");
      if (borderBottom) sp(cell, "border-bottom", "1px solid #e2d9c8");
      var valEl = document.createElement("div");
      valEl.textContent = val;
      valEl.className = "dp-stat-val";
      sp(valEl, "font-family", "'Inter', sans-serif");
      sp(valEl, "font-size", "0.8125rem");
      sp(valEl, "font-weight", "500");
      sp(valEl, "color", "#2a2218");
      sp(valEl, "margin-bottom", "2px");
      var lblEl = document.createElement("div");
      lblEl.textContent = lbl;
      sp(lblEl, "font-family", "'Inter', sans-serif");
      sp(lblEl, "font-size", "0.5625rem");
      sp(lblEl, "letter-spacing", "0.1em");
       sp(lblEl, "color", "#9c8e80");
       sp(lblEl, "text-transform", "uppercase");
      cell.appendChild(valEl);
      cell.appendChild(lblEl);
      return cell;
     }
 
    var cellLevel = makeStatCell("—", "Level",      true,  true);
    var cellExp  = makeStatCell("—", "EXP",        false, true);
    var cellDays  = makeStatCell("—", "Days Active", true,  false);
    var cellEdits = makeStatCell("—", "Edits",      false, false);
 
    dpStats.appendChild(cellLevel);
    dpStats.appendChild(cellExp);
    dpStats.appendChild(cellDays);
    dpStats.appendChild(cellEdits);


     var emailDiv = document.createElement("div");
     var dpMenu = document.createElement("div");
     emailDiv.textContent = user.email;
     sp(dpMenu, "padding", "0.25rem 0");
    s(emailDiv, {
      padding: "0.5rem 1rem 0.75rem",
      fontFamily: "'EB Garamond', serif",
      fontSize: "0.875rem",
      color: "#6b5e4e",
      borderBottom: "1px solid #e2d9c8",
      marginBottom: "0.25rem",
      fontStyle: "italic",
      wordBreak: "break-all"
    });


     var profileLink = document.createElement("a");
     var profileLink = document.createElement("a");
     profileLink.href = "https://erakh.com/profile";
     profileLink.href = "https://erakh.com/profile";
     profileLink.textContent = "Profile";
     profileLink.textContent = "Profile";
     s(profileLink, {
     sp(profileLink, "display", "block");
      display: "block",
    sp(profileLink, "width", "100%");
      width: "100%",
    sp(profileLink, "padding", "0.5rem 1rem");
      padding: "0.5rem 1rem",
    sp(profileLink, "font-family", "'Inter', sans-serif");
      fontFamily: "'Inter', sans-serif",
    sp(profileLink, "font-size", "0.8125rem");
      fontSize: "0.8125rem",
    sp(profileLink, "color", "#2a2218");
      color: "#2a2218",
    sp(profileLink, "text-decoration", "none");
      textDecoration: "none",
    sp(profileLink, "box-sizing", "border-box");
      boxSizing: "border-box",
    profileLink.addEventListener("mouseover", function() { this.style.setProperty("background", "#f0ebe0", "important"); });
      textTransform: "none"
     profileLink.addEventListener("mouseout",  function() { this.style.setProperty("background", "transparent", "important"); });
     });


     var signOutBtn = document.createElement("button");
     var signOutBtn = document.createElement("button");
     signOutBtn.textContent = "Sign out";
     signOutBtn.textContent = "Sign out";
     s(signOutBtn, {
     sp(signOutBtn, "display", "block");
      display: "block",
    sp(signOutBtn, "width", "100%");
      width: "100%",
    sp(signOutBtn, "padding", "0.5rem 1rem");
      padding: "0.5rem 1rem",
    sp(signOutBtn, "font-family", "'Inter', sans-serif");
      fontFamily: "'Inter', sans-serif",
    sp(signOutBtn, "font-size", "0.8125rem");
      fontSize: "0.8125rem",
    sp(signOutBtn, "color", "#a04040");
      color: "#a04040",
    sp(signOutBtn, "text-align", "left");
      textAlign: "left",
    sp(signOutBtn, "background", "transparent");
      background: "transparent",
    sp(signOutBtn, "border", "none");
      border: "none",
    sp(signOutBtn, "cursor", "pointer");
      cursor: "pointer",
    sp(signOutBtn, "box-sizing", "border-box");
      boxSizing: "border-box",
    signOutBtn.addEventListener("mouseover", function() { this.style.setProperty("background", "#f0ebe0", "important"); });
      textTransform: "none"
     signOutBtn.addEventListener("mouseout",  function() { this.style.setProperty("background", "transparent", "important"); });
     });
     signOutBtn.addEventListener("click", function () {
     signOutBtn.addEventListener("click", function () {
       fetch("https://erakh.com/api/auth/logout", { method: "POST", credentials: "include" })
       fetch("https://erakh.com/api/auth/logout", { method: "POST", credentials: "include" })
Line 181: Line 256:
     });
     });


     dropdown.appendChild(emailDiv);
    dpMenu.appendChild(profileLink);
     dropdown.appendChild(profileLink);
    dpMenu.appendChild(signOutBtn);
     dropdown.appendChild(signOutBtn);
 
     dropdown.appendChild(dpProfile);
     dropdown.appendChild(dpStats);
     dropdown.appendChild(dpMenu);
 
    var profileLoaded = false;


     avatar.addEventListener("click", function () {
     avatar.addEventListener("click", function () {
       dropdown.style.display = dropdown.style.display === "none" ? "block" : "none";
       var isHidden = dropdown.style.display === "none";
      dropdown.style.display = isHidden ? "block" : "none";
      if (isHidden && !profileLoaded) {
        fetch("https://erakh.com/api/auth/me", { method: "GET", credentials: "include" })
          .then(function(r) { return r.json(); })
          .then(function(data) {
            if (data.isNew) return;
            profileLoaded = true;
            cellLevel.querySelector(".dp-stat-val").textContent = "Lv. " + data.level;
            cellExp.querySelector(".dp-stat-val").textContent  = data.exp_in_level + " / " + data.exp_to_next;
            cellDays.querySelector(".dp-stat-val").textContent  = data.login_days;
            cellEdits.querySelector(".dp-stat-val").textContent = data.edit_count;
          })
          .catch(function() {});
      }
     });
     });
     document.addEventListener("click", function (e) {
     document.addEventListener("click", function (e) {
       if (!profile.contains(e.target)) dropdown.style.display = "none";
       if (!profile.contains(e.target)) {
        dropdown.style.display = "none";
      }
     });
     });


Line 197: Line 294:


   } else {
   } else {
    /* 비로그인 — Sign in */
     var loginBtn = document.createElement("a");
     var loginBtn = document.createElement("a");
     loginBtn.href = "https://erakh.com/login?redirect=" + encodeURIComponent(window.location.href);
     loginBtn.href = "https://erakh.com/login?redirect=" + encodeURIComponent(window.location.href);
     loginBtn.textContent = "Sign in";
     loginBtn.textContent = "Sign in";
     s(loginBtn, {
     sp(loginBtn, "font-family", "'Inter', sans-serif");
      fontFamily: "'Inter', sans-serif",
    sp(loginBtn, "font-size", "0.8125rem");
      fontSize: "0.8125rem",
    sp(loginBtn, "font-weight", "500");
      fontWeight: "500",
    sp(loginBtn, "color", "#2a2218");
      color: "#2a2218",
    sp(loginBtn, "text-decoration", "none");
      textDecoration: "none",
    sp(loginBtn, "padding", "0.4rem 1rem");
      padding: "0.4rem 1rem",
    sp(loginBtn, "border", "1px solid #c8b99a");
      border: "1px solid #c8b99a",
    sp(loginBtn, "background", "transparent");
      background: "transparent",
    sp(loginBtn, "cursor", "pointer");
      cursor: "pointer",
    sp(loginBtn, "box-sizing", "border-box");
      textTransform: "none",
    sp(loginBtn, "line-height", "1");
      boxSizing: "border-box",
      lineHeight: "1"
    });
     loginBtn.addEventListener("mouseover", function() {
     loginBtn.addEventListener("mouseover", function() {
       this.style.borderColor = "#c8a060";
       this.style.setProperty("border-color", "#c8a060", "important");
       this.style.background = "#f0ebe0";
       this.style.setProperty("background", "#f0ebe0", "important");
     });
     });
     loginBtn.addEventListener("mouseout", function() {
     loginBtn.addEventListener("mouseout", function() {
       this.style.borderColor = "#c8b99a";
       this.style.setProperty("border-color", "#c8b99a", "important");
       this.style.background = "transparent";
       this.style.setProperty("background", "transparent", "important");
     });
     });
     right.appendChild(loginBtn);
     right.appendChild(loginBtn);
Line 229: Line 322:
   header.appendChild(right);
   header.appendChild(right);
   document.body.insertBefore(header, document.body.firstChild);
   document.body.insertBefore(header, document.body.firstChild);
  // ─── 서브바 ───
  var subbar = document.createElement("div");
  subbar.id = "erakh-subbar";
  sp(subbar, "position", "fixed");
  sp(subbar, "top", "56px");
  sp(subbar, "left", "0");
  sp(subbar, "right", "0");
  sp(subbar, "z-index", "9998");
  sp(subbar, "height", "44px");
  sp(subbar, "background", "#f0ebe0");
  sp(subbar, "border-bottom", "1px solid #e2d9c8");
  sp(subbar, "display", "flex");
  sp(subbar, "align-items", "center");
  sp(subbar, "gap", "1rem");
  sp(subbar, "padding", "0 1.5rem");
  sp(subbar, "box-sizing", "border-box");
  sp(subbar, "width", "100%");
  var hbBtn = document.createElement("button");
  sp(hbBtn, "display", "flex");
  sp(hbBtn, "flex-direction", "column");
  sp(hbBtn, "justify-content", "center");
  sp(hbBtn, "align-items", "center");
  sp(hbBtn, "gap", "5px");
  sp(hbBtn, "width", "32px");
  sp(hbBtn, "height", "32px");
  sp(hbBtn, "border", "none");
  sp(hbBtn, "background", "transparent");
  sp(hbBtn, "cursor", "pointer");
  sp(hbBtn, "padding", "4px");
  sp(hbBtn, "border-radius", "4px");
  sp(hbBtn, "flex-shrink", "0");
  sp(hbBtn, "box-sizing", "border-box");
  function makeBar() {
    var b = document.createElement("span");
    b.style.setProperty("display", "block", "important");
    b.style.setProperty("width", "18px", "important");
    b.style.setProperty("height", "1.5px", "important");
    b.style.setProperty("background", "#6b5e4e", "important");
    b.style.setProperty("border-radius", "1px", "important");
    return b;
  }
  hbBtn.appendChild(makeBar());
  hbBtn.appendChild(makeBar());
  hbBtn.appendChild(makeBar());
  hbBtn.addEventListener("mouseover", function() { this.style.setProperty("background", "#e2d9c8", "important"); });
  hbBtn.addEventListener("mouseout",  function() { this.style.setProperty("background", "transparent", "important"); });
  var searchWrap = document.createElement("div");
  sp(searchWrap, "position", "relative");
  sp(searchWrap, "flex", "1");
  sp(searchWrap, "max-width", "520px");
  var searchIconEl = document.createElement("span");
  searchIconEl.innerHTML = "&#9906;";
  sp(searchIconEl, "position", "absolute");
  sp(searchIconEl, "left", "0.6rem");
  sp(searchIconEl, "top", "50%");
  sp(searchIconEl, "transform", "translateY(-50%)");
  sp(searchIconEl, "font-size", "13px");
  sp(searchIconEl, "color", "#9c8e80");
  sp(searchIconEl, "pointer-events", "none");
  sp(searchIconEl, "line-height", "1");
  var searchInput = document.createElement("input");
  searchInput.type = "text";
  searchInput.placeholder = "Search Erakh Codex...";
  sp(searchInput, "width", "100%");
  sp(searchInput, "height", "30px");
  sp(searchInput, "padding", "0 0.75rem 0 2rem");
  sp(searchInput, "border", "1px solid #c8b99a");
  sp(searchInput, "border-radius", "3px");
  sp(searchInput, "background", "#f9f6f0");
  sp(searchInput, "font-family", "'Inter', sans-serif");
  sp(searchInput, "font-size", "0.8125rem");
  sp(searchInput, "color", "#2a2218");
  sp(searchInput, "outline", "none");
  sp(searchInput, "box-sizing", "border-box");
  var suggestBox = document.createElement("div");
  sp(suggestBox, "position", "absolute");
  sp(suggestBox, "top", "calc(100% + 2px)");
  sp(suggestBox, "left", "0");
  sp(suggestBox, "right", "0");
  sp(suggestBox, "background", "#f9f6f0");
  sp(suggestBox, "border", "1px solid #c8b99a");
  sp(suggestBox, "border-radius", "3px");
  sp(suggestBox, "z-index", "10001");
  sp(suggestBox, "display", "none");
  sp(suggestBox, "box-shadow", "0 4px 12px rgba(0,0,0,0.08)");
  searchWrap.appendChild(suggestBox);
  var suggestItems = [];
  var suggestIndex = -1;
  function renderSuggestions(titles) {
    suggestBox.innerHTML = "";
    suggestItems = titles;
    suggestIndex = -1;
    if (titles.length === 0) { suggestBox.style.setProperty("display", "none", "important"); return; }
    titles.forEach(function(title, i) {
      var item = document.createElement("div");
      item.textContent = title;
      sp(item, "padding", "0.4rem 0.75rem");
      sp(item, "font-family", "'Inter', sans-serif");
      sp(item, "font-size", "0.8125rem");
      sp(item, "color", "#2a2218");
      sp(item, "cursor", "pointer");
      sp(item, "border-bottom", i < titles.length - 1 ? "1px solid #f0ebe0" : "none");
      item.addEventListener("mouseover", function() { this.style.setProperty("background", "#f0ebe0", "important"); });
      item.addEventListener("mouseout",  function() { this.style.setProperty("background", "transparent", "important"); });
      item.addEventListener("mousedown", function(e) {
        e.preventDefault();
        window.location.href = "/index.php?title=" + encodeURIComponent(title.replace(/ /g, "_"));
      });
      suggestBox.appendChild(item);
    });
    suggestBox.style.setProperty("display", "block", "important");
  }
  var suggestTimer = null;
  searchInput.addEventListener("input", function() {
    var q = this.value.trim();
    clearTimeout(suggestTimer);
    if (q.length < 2) { suggestBox.style.setProperty("display", "none", "important"); return; }
    suggestTimer = setTimeout(function() {
      fetch("/api.php?action=opensearch&search=" + encodeURIComponent(q) + "&limit=8&namespace=0&format=json")
        .then(function(r) { return r.json(); })
        .then(function(data) { renderSuggestions(data[1] || []); })
        .catch(function() {});
    }, 200);
  });
  searchInput.addEventListener("focus", function() { this.style.setProperty("border-color", "#c8a060", "important"); });
  searchInput.addEventListener("blur",  function() {
    this.style.setProperty("border-color", "#c8b99a", "important");
    setTimeout(function() { suggestBox.style.setProperty("display", "none", "important"); }, 150);
  });
  searchInput.addEventListener("keydown", function(e) {
    var items = suggestBox.querySelectorAll("div");
    if (e.key === "ArrowDown") {
      e.preventDefault();
      suggestIndex = Math.min(suggestIndex + 1, items.length - 1);
      items.forEach(function(el, i) { el.style.setProperty("background", i === suggestIndex ? "#f0ebe0" : "transparent", "important"); });
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      suggestIndex = Math.max(suggestIndex - 1, -1);
      items.forEach(function(el, i) { el.style.setProperty("background", i === suggestIndex ? "#f0ebe0" : "transparent", "important"); });
    } else if (e.key === "Enter") {
      e.preventDefault();
      if (suggestIndex >= 0 && suggestItems[suggestIndex]) {
        window.location.href = "/index.php?title=" + encodeURIComponent(suggestItems[suggestIndex].replace(/ /g, "_"));
      } else if (this.value.trim()) {
        window.location.href = "/index.php?title=Special%3ASearch&search=" + encodeURIComponent(this.value.trim());
      }
    } else if (e.key === "Escape") {
      suggestBox.style.setProperty("display", "none", "important");
    }
  });
  searchWrap.appendChild(searchIconEl);
  searchWrap.appendChild(searchInput);
  subbar.appendChild(hbBtn);
  subbar.appendChild(searchWrap);
  if (isSysop) {
    var createDocBtn = document.createElement("a");
    createDocBtn.href = "/index.php?title=Erakh_Codex:Create";
    createDocBtn.textContent = "+ Create";
    sp(createDocBtn, "display", "flex");
    sp(createDocBtn, "align-items", "center");
    sp(createDocBtn, "height", "44px");
    sp(createDocBtn, "padding", "0 1.25rem");
    sp(createDocBtn, "margin-left", "auto");
    sp(createDocBtn, "font-family", "'Inter', sans-serif");
    sp(createDocBtn, "font-size", "0.8125rem");
    sp(createDocBtn, "font-weight", "500");
    sp(createDocBtn, "color", "#c8a060");
    sp(createDocBtn, "text-decoration", "none");
    sp(createDocBtn, "border-left", "1px solid #e2d9c8");
    sp(createDocBtn, "flex-shrink", "0");
    sp(createDocBtn, "box-sizing", "border-box");
    sp(createDocBtn, "letter-spacing", "-0.01em");
    createDocBtn.addEventListener("mouseover", function() {
      this.style.setProperty("background", "#e2d9c8", "important");
      this.style.setProperty("color", "#a07840", "important");
    });
    createDocBtn.addEventListener("mouseout", function() {
      this.style.setProperty("background", "transparent", "important");
      this.style.setProperty("color", "#c8a060", "important");
    });
    subbar.appendChild(createDocBtn);
  }
  // ─── 드로어 ───
  var drawerOverlay = document.createElement("div");
  sp(drawerOverlay, "position", "fixed");
  sp(drawerOverlay, "top", "100px");
  sp(drawerOverlay, "left", "0");
  sp(drawerOverlay, "right", "0");
  sp(drawerOverlay, "bottom", "0");
  sp(drawerOverlay, "background", "rgba(0,0,0,0.18)");
  sp(drawerOverlay, "z-index", "9996");
  sp(drawerOverlay, "display", "none");
  var drawer = document.createElement("nav");
  sp(drawer, "position", "fixed");
  sp(drawer, "top", "100px");
  sp(drawer, "left", "0");
  sp(drawer, "bottom", "0");
  sp(drawer, "width", "220px");
  sp(drawer, "background", "#f9f6f0");
  sp(drawer, "border-right", "1px solid #e2d9c8");
  sp(drawer, "z-index", "9997");
  sp(drawer, "display", "none");
  sp(drawer, "overflow-y", "auto");
  var drawerHead = document.createElement("div");
  sp(drawerHead, "padding", "0.75rem 1rem 0.5rem");
  sp(drawerHead, "border-bottom", "1px solid #e2d9c8");
  var drawerLabel = document.createElement("span");
  drawerLabel.textContent = "Menu";
  sp(drawerLabel, "font-family", "'Inter', sans-serif");
  sp(drawerLabel, "font-size", "0.78rem");
  sp(drawerLabel, "font-weight", "500");
  sp(drawerLabel, "color", "#2a2218");
  sp(drawerLabel, "display", "inline-block");
  sp(drawerLabel, "padding-bottom", "0.25rem");
  sp(drawerLabel, "border-bottom", "2px solid #c8a060");
  drawerHead.appendChild(drawerLabel);
  drawer.appendChild(drawerHead);
  var drawerMenus = [
    {
      items: [
        { label: "Main Page",      href: "/" },
        { label: "Categories",      href: "/index.php?title=Special:Categories" },
        { label: "Timeline",        href: "/index.php?title=Category:Chronicle" },
        { label: "Recent Changes",  href: "/index.php?title=Special:RecentChanges" },
        { label: "Random Article",  href: "/index.php?title=Special:Random" },
      ],
      gold: true
    },
    {
      items: [
        { label: "Policies & Guidelines", href: "/index.php?title=Erakh_Codex:Policies" },
      ],
      gold: false
    }
  ];
  drawerMenus.forEach(function(section, si) {
    var sec = document.createElement("div");
    sp(sec, "padding", "0.5rem 0");
    if (si < drawerMenus.length - 1) sp(sec, "border-bottom", "1px solid #e2d9c8");
    section.items.forEach(function(item) {
      var a = document.createElement("a");
      a.href = item.href;
      a.textContent = item.label;
      sp(a, "display", "block");
      sp(a, "padding", "0.45rem 1.25rem");
      sp(a, "font-family", "'Inter', sans-serif");
      sp(a, "font-size", "0.8125rem");
      sp(a, "color", section.gold ? "#c8a060" : "#6b5e4e");
      sp(a, "text-decoration", "none");
      a.addEventListener("mouseover", function() { this.style.setProperty("background", "#f0ebe0", "important"); });
      a.addEventListener("mouseout",  function() { this.style.setProperty("background", "transparent", "important"); });
      sec.appendChild(a);
    });
    drawer.appendChild(sec);
  });
  var drawerOpen = false;
  function toggleDrawer() {
    drawerOpen = !drawerOpen;
    drawer.style.setProperty("display",        drawerOpen ? "block" : "none", "important");
    drawerOverlay.style.setProperty("display", drawerOpen ? "block" : "none", "important");
  }
  hbBtn.addEventListener("click", toggleDrawer);
  drawerOverlay.addEventListener("click", toggleDrawer);
  document.body.insertBefore(subbar, document.body.children[1]);
  document.body.appendChild(drawerOverlay);
  document.body.appendChild(drawer);
  // ─── 3컬럼 레이아웃 (JS 직접 목차 빌드) ───
  function buildToc(headings) {
    var toc = document.createElement("nav");
    toc.id = "erakh-toc";
    var ul = document.createElement("ul");
    sp(ul, "list-style", "none");
    sp(ul, "margin", "0");
    sp(ul, "padding", "0");
    headings.forEach(function(h, i) {
      var level = parseInt(h.tagName.replace("H", ""));
      var id = h.id || ("erakh-section-" + i);
      h.id = id;
      var li = document.createElement("li");
      li.style.setProperty("padding", "0.25rem 0 0.25rem " + (level === 2 ? "0.5rem" : "1.25rem"), "important");
      li.style.setProperty("border-left", "2px solid transparent", "important");
      li.style.setProperty("line-height", "1.4", "important");
      li.dataset.id = id;
      var a = document.createElement("a");
      a.href = "#" + id;
      a.textContent = h.textContent.replace(/\[edit\]/g, "").trim();
      sp(a, "font-family", "'Inter', sans-serif");
      sp(a, "font-size", level === 2 ? "0.78rem" : "0.74rem");
      sp(a, "color", level === 2 ? "#6b5e4e" : "#9c8e80");
      sp(a, "text-decoration", "none");
      sp(a, "display", "block");
      a.addEventListener("mouseover", function() { this.style.setProperty("color", "#2a2218", "important"); });
      a.addEventListener("mouseout",  function() { this.style.setProperty("color", level === 2 ? "#6b5e4e" : "#9c8e80", "important"); });
      a.addEventListener("click", function(e) {
        e.preventDefault();
        var target = document.getElementById(id);
        if (target) {
          var _h = document.getElementById("erakh-header");
          var _s = document.getElementById("erakh-subbar");
          var _offset = (_h ? _h.offsetHeight : 56) + (_s ? _s.offsetHeight : 44) + 8;
          var top = target.getBoundingClientRect().top + window.scrollY - _offset;
          window.scrollTo({ top: top, behavior: "smooth" });
        }
      });
      li.appendChild(a);
      ul.appendChild(li);
    });
    toc.appendChild(ul);
    return toc;
  }
  function buildLayout() {
    // 메인페이지 / Create 페이지에서는 레이아웃 빌드 스킵
    if (pageName === "Erakh_Codex:Create") return;
    if (pageName === "Main_Page") return;
    // view 액션이 아닌 경우 (edit, delete 등) 레이아웃 빌드 스킵
    if (mw.config.get("wgAction") !== "view") return;
    var content = document.querySelector(".mw-body") || document.getElementById("content");
    if (!content) return;
    var headings = Array.from(content.querySelectorAll("h2, h3")).filter(function(h) {
      return !h.closest("#toc");
    });
    var layout = document.createElement("div");
    layout.className = "erakh-layout";
    var colLeft = document.createElement("aside");
    colLeft.className = "erakh-col-left";
    var colCenter = document.createElement("div");
    colCenter.className = "erakh-col-center";
    var colRight = document.createElement("aside");
    colRight.className = "erakh-col-right";
    var tocLabel = document.createElement("div");
    tocLabel.className = "erakh-toc-label";
    tocLabel.textContent = "Contents";
    colLeft.appendChild(tocLabel);
    if (headings.length > 0) {
      var toc = buildToc(headings);
      colLeft.appendChild(toc);
      var items = colLeft.querySelectorAll("li[data-id]");
      window.addEventListener("scroll", function() {
        var _h = document.getElementById("erakh-header");
        var _s = document.getElementById("erakh-subbar");
        var _offset = (_h ? _h.offsetHeight : 56) + (_s ? _s.offsetHeight : 44) + 8;
        var scrollY = window.scrollY + _offset;
        var current = null;
        headings.forEach(function(h) {
          if (h.offsetTop <= scrollY) current = h.id;
        });
        items.forEach(function(li) {
          if (li.dataset.id === current) {
            li.style.setProperty("border-left-color", "#c8a060", "important");
            li.querySelector("a").style.setProperty("color", "#2a2218", "important");
            li.querySelector("a").style.setProperty("font-weight", "500", "important");
          } else {
            li.style.setProperty("border-left-color", "transparent", "important");
            var level = li.style.paddingLeft === "0.5rem" ? 2 : 3;
            li.querySelector("a").style.setProperty("color", level === 2 ? "#6b5e4e" : "#9c8e80", "important");
            li.querySelector("a").style.setProperty("font-weight", "400", "important");
          }
        });
      });
    } else {
      var emptyMsg = document.createElement("div");
      sp(emptyMsg, "font-family", "'Inter', sans-serif");
      sp(emptyMsg, "font-size", "0.75rem");
      sp(emptyMsg, "color", "#9c8e80");
      sp(emptyMsg, "padding", "0.25rem 0.5rem");
      emptyMsg.textContent = "—";
      colLeft.appendChild(emptyMsg);
    }
    var sidebarLabel = document.createElement("div");
    sidebarLabel.className = "erakh-sidebar-label";
    sidebarLabel.textContent = "위젯 영역";
    colRight.appendChild(sidebarLabel);
    var ph1 = document.createElement("div");
    ph1.className = "erakh-sidebar-placeholder";
    ph1.textContent = "인기 문서 위젯 예정";
    colRight.appendChild(ph1);
    var ph2 = document.createElement("div");
    ph2.className = "erakh-sidebar-placeholder";
    ph2.textContent = "최근 변경 위젯 예정";
    colRight.appendChild(ph2);
    // ─── sysop 전용 [edit] · [delete] · [json] 버튼 ───
    if (isSysop) {
      var firstHeadingEl = document.getElementById("firstHeading");
      if (firstHeadingEl) {
        var actionWrap = document.createElement("span");
        sp(actionWrap, "margin-left", "0.75rem");
        sp(actionWrap, "font-size", "0.8125rem");
        sp(actionWrap, "font-weight", "400");
        // [edit]
        var editActionLink = document.createElement("a");
        editActionLink.href = "/index.php?title=" + encodeURIComponent(pageName) + "&action=edit";
        editActionLink.textContent = "[edit]";
        sp(editActionLink, "font-family", "'Inter', sans-serif");
        sp(editActionLink, "font-size", "0.8125rem");
        sp(editActionLink, "color", "#9c8e80");
        sp(editActionLink, "text-decoration", "none");
        editActionLink.addEventListener("mouseover", function() { this.style.setProperty("color", "#c8a060", "important"); });
        editActionLink.addEventListener("mouseout",  function() { this.style.setProperty("color", "#9c8e80", "important"); });
        // ·
        var sep1 = document.createElement("span");
        sep1.textContent = " · ";
        sp(sep1, "color", "#c8b99a");
        sp(sep1, "font-family", "'Inter', sans-serif");
        // [delete]
        var deleteActionLink = document.createElement("a");
        deleteActionLink.href = "/index.php?title=" + encodeURIComponent(pageName) + "&action=delete";
        deleteActionLink.textContent = "[delete]";
        sp(deleteActionLink, "font-family", "'Inter', sans-serif");
        sp(deleteActionLink, "font-size", "0.8125rem");
        sp(deleteActionLink, "color", "#9c8e80");
        sp(deleteActionLink, "text-decoration", "none");
        deleteActionLink.addEventListener("mouseover", function() { this.style.setProperty("color", "#a04040", "important"); });
        deleteActionLink.addEventListener("mouseout",  function() { this.style.setProperty("color", "#9c8e80", "important"); });
        // ·
        var sep2 = document.createElement("span");
        sep2.textContent = " · ";
        sp(sep2, "color", "#c8b99a");
        sp(sep2, "font-family", "'Inter', sans-serif");
        // [json] — API URL 클립보드 복사
        var apiUrl = "https://codex.erakh.com/api.php?action=query&titles=" + encodeURIComponent(pageName) + "&prop=revisions&rvprop=content&format=json";
        var jsonBtn = document.createElement("span");
        jsonBtn.textContent = "[json]";
        sp(jsonBtn, "font-family", "'Inter', sans-serif");
        sp(jsonBtn, "font-size", "0.8125rem");
        sp(jsonBtn, "color", "#9c8e80");
        sp(jsonBtn, "cursor", "pointer");
        sp(jsonBtn, "user-select", "none");
        jsonBtn.addEventListener("mouseover", function() { this.style.setProperty("color", "#c8a060", "important"); });
        jsonBtn.addEventListener("mouseout",  function() { this.style.setProperty("color", "#9c8e80", "important"); });
        jsonBtn.addEventListener("click", function() {
          navigator.clipboard.writeText(apiUrl).then(function() {
            jsonBtn.textContent = "[copied!]";
            sp(jsonBtn, "color", "#c8a060");
            setTimeout(function() {
              jsonBtn.textContent = "[json]";
              sp(jsonBtn, "color", "#9c8e80");
            }, 1500);
          }).catch(function() {
            // clipboard API 실패 시 fallback
            var ta = document.createElement("textarea");
            ta.value = apiUrl;
            ta.style.position = "fixed";
            ta.style.opacity = "0";
            document.body.appendChild(ta);
            ta.select();
            document.execCommand("copy");
            document.body.removeChild(ta);
            jsonBtn.textContent = "[copied!]";
            sp(jsonBtn, "color", "#c8a060");
            setTimeout(function() {
              jsonBtn.textContent = "[json]";
              sp(jsonBtn, "color", "#9c8e80");
            }, 1500);
          });
        });
        actionWrap.appendChild(editActionLink);
        actionWrap.appendChild(sep1);
        actionWrap.appendChild(deleteActionLink);
        actionWrap.appendChild(sep2);
        actionWrap.appendChild(jsonBtn);
        firstHeadingEl.appendChild(actionWrap);
      }
    }
    content.parentNode.insertBefore(layout, content);
    colCenter.appendChild(content);
    layout.appendChild(colLeft);
    layout.appendChild(colCenter);
    layout.appendChild(colRight);
  }
  // ─── 문서 생성 페이지 UI ───
  function buildCreatePage() {
    if (pageName !== "Erakh_Codex:Create") return;
    var CATEGORIES = [
      { label: "Characters",    preload: "Template:Preload/Character"    },
      { label: "Nations",      preload: "Template:Preload/Nation"      },
      { label: "Regions",      preload: "Template:Preload/Region"      },
      { label: "Races",        preload: "Template:Preload/Race"        },
      { label: "Organizations", preload: "Template:Preload/Organization" },
      { label: "Events",        preload: "Template:Preload/Event"        },
      { label: "Lore",          preload: "Template:Preload/Lore"        },
      { label: "Artifacts",    preload: "Template:Preload/Artifact"    },
      { label: "Chronicle",    preload: null                            },
    ];
    var firstHeading = document.getElementById("firstHeading");
    if (firstHeading) sp(firstHeading, "display", "none");
    var parseOutput = document.querySelector(".mw-parser-output");
    if (parseOutput) sp(parseOutput, "display", "none");
    var noArticle = document.querySelector(".noarticletext");
    if (noArticle) sp(noArticle, "display", "none");
    var pViews = document.getElementById("p-views");
    if (pViews) sp(pViews, "display", "none");
    var pCactions = document.getElementById("p-cactions");
    if (pCactions) sp(pCactions, "display", "none");
    var target = document.getElementById("mw-content-text");
    if (!target) return;
    if (!isSysop) {
      var denied = document.createElement("div");
      sp(denied, "padding", "60px 40px");
      sp(denied, "text-align", "center");
      sp(denied, "font-family", "'Inter', sans-serif");
      sp(denied, "font-size", "0.875rem");
      sp(denied, "color", "#9c8e80");
      denied.textContent = "Document creation is restricted to administrators.";
      target.appendChild(denied);
      return;
    }
    var selectedCat = null;
    var wrap = document.createElement("div");
    sp(wrap, "max-width", "680px");
    sp(wrap, "margin", "40px auto");
    sp(wrap, "padding", "0 24px 48px");
    sp(wrap, "font-family", "'Inter', sans-serif");
    sp(wrap, "color", "#2a2218");
    sp(wrap, "box-sizing", "border-box");
    var heading = document.createElement("h2");
    heading.textContent = "Create New Document";
    sp(heading, "font-size", "1.375rem");
    sp(heading, "font-weight", "600");
    sp(heading, "margin", "0 0 2rem 0");
    sp(heading, "color", "#2a2218");
    sp(heading, "letter-spacing", "-0.01em");
    sp(heading, "font-family", "'Inter', sans-serif");
    wrap.appendChild(heading);
    var catLabel = document.createElement("p");
    catLabel.textContent = "Select Category";
    sp(catLabel, "font-size", "0.6875rem");
    sp(catLabel, "font-weight", "500");
    sp(catLabel, "color", "#9c8e80");
    sp(catLabel, "margin", "0 0 0.625rem 0");
    sp(catLabel, "letter-spacing", "0.1em");
    sp(catLabel, "text-transform", "uppercase");
    sp(catLabel, "font-family", "'Inter', sans-serif");
    wrap.appendChild(catLabel);
    var grid = document.createElement("div");
    sp(grid, "display", "grid");
    sp(grid, "grid-template-columns", "repeat(5, 1fr)");
    sp(grid, "gap", "8px");
    sp(grid, "margin-bottom", "0.625rem");
    var catCards = [];
    CATEGORIES.forEach(function(cat) {
      var card = document.createElement("button");
      card.textContent = cat.label;
      sp(card, "border", "1.5px solid #e2d9c8");
      sp(card, "border-radius", "6px");
      sp(card, "background", "#f9f6f0");
      sp(card, "padding", "0.75rem 0.375rem");
      sp(card, "font-size", "0.79rem");
      sp(card, "font-weight", "500");
      sp(card, "font-family", "'Inter', sans-serif");
      sp(card, "color", "#2a2218");
      sp(card, "cursor", "pointer");
      sp(card, "text-align", "center");
      sp(card, "line-height", "1.3");
      sp(card, "box-sizing", "border-box");
      card.addEventListener("mouseover", function() {
        if (selectedCat !== cat.label) {
          this.style.setProperty("background", "#f0ebe0", "important");
          this.style.setProperty("border-color", "#c8b99a", "important");
        }
      });
      card.addEventListener("mouseout", function() {
        if (selectedCat !== cat.label) {
          this.style.setProperty("background", "#f9f6f0", "important");
          this.style.setProperty("border-color", "#e2d9c8", "important");
        }
      });
      card.addEventListener("click", function() {
        catCards.forEach(function(c) {
          c.style.setProperty("background", "#f9f6f0", "important");
          c.style.setProperty("border-color", "#e2d9c8", "important");
          c.style.setProperty("color", "#2a2218", "important");
        });
        this.style.setProperty("background", "#c8a060", "important");
        this.style.setProperty("border-color", "#c8a060", "important");
        this.style.setProperty("color", "#ffffff", "important");
        selectedCat = cat.label;
        if (cat.preload === null) {
          chronicleNotice.style.setProperty("display", "block", "important");
        } else {
          chronicleNotice.style.setProperty("display", "none", "important");
        }
        updateBtn();
      });
      catCards.push(card);
      grid.appendChild(card);
    });
    wrap.appendChild(grid);
    var chronicleNotice = document.createElement("p");
    chronicleNotice.textContent = "Chronicle documents use a timeline format. No infobox template will be applied.";
    sp(chronicleNotice, "display", "none");
    sp(chronicleNotice, "font-size", "0.75rem");
    sp(chronicleNotice, "color", "#9c8e80");
    sp(chronicleNotice, "font-style", "italic");
    sp(chronicleNotice, "margin", "0.375rem 0 0 0");
    sp(chronicleNotice, "font-family", "'Inter', sans-serif");
    wrap.appendChild(chronicleNotice);
    var divider = document.createElement("div");
    sp(divider, "border-top", "1px solid #e2d9c8");
    sp(divider, "margin", "1.5rem 0 1.25rem");
    wrap.appendChild(divider);
    var titleLabel = document.createElement("p");
    titleLabel.textContent = "Document Title";
    sp(titleLabel, "font-size", "0.6875rem");
    sp(titleLabel, "font-weight", "500");
    sp(titleLabel, "color", "#9c8e80");
    sp(titleLabel, "margin", "0 0 0.5rem 0");
    sp(titleLabel, "letter-spacing", "0.1em");
    sp(titleLabel, "text-transform", "uppercase");
    sp(titleLabel, "font-family", "'Inter', sans-serif");
    wrap.appendChild(titleLabel);
    var titleInput = document.createElement("input");
    titleInput.type = "text";
    titleInput.placeholder = "Enter document title...";
    sp(titleInput, "width", "100%");
    sp(titleInput, "padding", "0.625rem 0.875rem");
    sp(titleInput, "border", "1.5px solid #e2d9c8");
    sp(titleInput, "border-radius", "6px");
    sp(titleInput, "font-size", "0.9375rem");
    sp(titleInput, "font-family", "'Inter', sans-serif");
    sp(titleInput, "color", "#2a2218");
    sp(titleInput, "background", "#ffffff");
    sp(titleInput, "box-sizing", "border-box");
    sp(titleInput, "outline", "none");
    sp(titleInput, "margin-bottom", "1.25rem");
    titleInput.addEventListener("focus", function() { this.style.setProperty("border-color", "#c8a060", "important"); });
    titleInput.addEventListener("blur",  function() { this.style.setProperty("border-color", "#e2d9c8", "important"); });
    titleInput.addEventListener("input", updateBtn);
    wrap.appendChild(titleInput);
    var createBtn = document.createElement("button");
    createBtn.textContent = "Create Article";
    sp(createBtn, "display", "block");
    sp(createBtn, "width", "100%");
    sp(createBtn, "padding", "0.75rem");
    sp(createBtn, "background", "#e2d9c8");
    sp(createBtn, "border", "none");
    sp(createBtn, "border-radius", "6px");
    sp(createBtn, "font-size", "0.9375rem");
    sp(createBtn, "font-weight", "600");
    sp(createBtn, "font-family", "'Inter', sans-serif");
    sp(createBtn, "color", "#9c8e80");
    sp(createBtn, "cursor", "not-allowed");
    sp(createBtn, "box-sizing", "border-box");
    createBtn.addEventListener("click", function() {
      if (!selectedCat) return;
      var title = titleInput.value.trim();
      if (!title) return;
      var catObj = null;
      for (var i = 0; i < CATEGORIES.length; i++) {
        if (CATEGORIES[i].label === selectedCat) { catObj = CATEGORIES[i]; break; }
      }
      if (!catObj) return;
      var url = "/index.php?title=" + encodeURIComponent(title) + "&action=edit";
      if (catObj.preload) url += "&preload=" + encodeURIComponent(catObj.preload);
      window.location.href = url;
    });
    wrap.appendChild(createBtn);
    target.appendChild(wrap);
    function updateBtn() {
      var ready = selectedCat && titleInput.value.trim().length > 0;
      if (ready) {
        createBtn.style.setProperty("background", "#c8a060", "important");
        createBtn.style.setProperty("color", "#ffffff", "important");
        createBtn.style.setProperty("cursor", "pointer", "important");
      } else {
        createBtn.style.setProperty("background", "#e2d9c8", "important");
        createBtn.style.setProperty("color", "#9c8e80", "important");
        createBtn.style.setProperty("cursor", "not-allowed", "important");
      }
    }
  }
  // ─── 메인페이지 빌드 ───
  function buildMainPage() {
    if (pageName !== "Main_Page") return;
    var firstHeading = document.getElementById("firstHeading");
    if (firstHeading) sp(firstHeading, "display", "none");
    var parseOutput = document.querySelector(".mw-parser-output");
    if (parseOutput) sp(parseOutput, "display", "none");
    var target = document.getElementById("mw-content-text");
    if (!target) return;
    var CATS = [
      { label: "Characters",    icon: "ti-user",    href: "/index.php?title=Category:Characters",    highlight: false },
      { label: "Nations",      icon: "ti-building", href: "/index.php?title=Category:Nations",      highlight: false },
      { label: "Regions",      icon: "ti-map",      href: "/index.php?title=Category:Regions",      highlight: false },
      { label: "Races",        icon: "ti-dna",      href: "/index.php?title=Category:Races",        highlight: false },
      { label: "Chronicle",    icon: "ti-clock",    href: "/index.php?title=Category:Chronicle",    highlight: true  },
      { label: "Organizations", icon: "ti-users",    href: "/index.php?title=Category:Organizations", highlight: false },
      { label: "Events",        icon: "ti-sword",    href: "/index.php?title=Category:Events",        highlight: false },
      { label: "Lore",          icon: "ti-book",    href: "/index.php?title=Category:Lore",          highlight: false },
      { label: "Artifacts",    icon: "ti-diamond",  href: "/index.php?title=Category:Artifacts",    highlight: false },
    ];
    var tablerLink = document.createElement("link");
    tablerLink.rel = "stylesheet";
    tablerLink.href = "https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css";
    document.head.appendChild(tablerLink);
    var wrap = document.createElement("div");
    sp(wrap, "max-width", "860px");
    sp(wrap, "margin", "0 auto");
    sp(wrap, "padding", "2.5rem 2rem 4rem");
    sp(wrap, "font-family", "'Inter', sans-serif");
    sp(wrap, "box-sizing", "border-box");
    var hero = document.createElement("div");
    sp(hero, "background", "#f0ebe0");
    sp(hero, "border", "1px solid #e2d9c8");
    sp(hero, "border-radius", "6px");
    sp(hero, "padding", "2rem 2.5rem");
    sp(hero, "margin-bottom", "2.5rem");
    sp(hero, "display", "flex");
    sp(hero, "align-items", "center");
    sp(hero, "justify-content", "space-between");
    sp(hero, "box-sizing", "border-box");
    var heroText = document.createElement("div");
    var heroTitle = document.createElement("h1");
    heroTitle.textContent = "Erakh Codex";
    sp(heroTitle, "font-size", "1.5rem");
    sp(heroTitle, "font-weight", "600");
    sp(heroTitle, "color", "#2a2218");
    sp(heroTitle, "letter-spacing", "-0.01em");
    sp(heroTitle, "margin", "0 0 0.5rem 0");
    sp(heroTitle, "font-family", "'Inter', sans-serif");
    var heroDesc = document.createElement("p");
    heroDesc.textContent = "The official encyclopedia of the Mirtess Chronicle. Explore the world, its history, peoples, and events — all in one place.";
    sp(heroDesc, "font-size", "0.8125rem");
    sp(heroDesc, "color", "#6b5e4e");
    sp(heroDesc, "line-height", "1.7");
    sp(heroDesc, "margin", "0");
    sp(heroDesc, "max-width", "480px");
    sp(heroDesc, "font-family", "'Inter', sans-serif");
    heroText.appendChild(heroTitle);
    heroText.appendChild(heroDesc);
    var heroStat = document.createElement("div");
    sp(heroStat, "text-align", "center");
    sp(heroStat, "border-left", "1px solid #c8b99a");
    sp(heroStat, "padding-left", "2.5rem");
    sp(heroStat, "flex-shrink", "0");
    var statNum = document.createElement("div");
    statNum.textContent = "—";
    sp(statNum, "font-size", "2rem");
    sp(statNum, "font-weight", "600");
    sp(statNum, "color", "#c8a060");
    sp(statNum, "letter-spacing", "-0.02em");
    sp(statNum, "font-family", "'Inter', sans-serif");
    var statLbl = document.createElement("div");
    statLbl.textContent = "Documents";
    sp(statLbl, "font-size", "0.6875rem");
    sp(statLbl, "color", "#9c8e80");
    sp(statLbl, "text-transform", "uppercase");
    sp(statLbl, "letter-spacing", "0.08em");
    sp(statLbl, "margin-top", "0.25rem");
    sp(statLbl, "font-family", "'Inter', sans-serif");
    heroStat.appendChild(statNum);
    heroStat.appendChild(statLbl);
    hero.appendChild(heroText);
    hero.appendChild(heroStat);
    wrap.appendChild(hero);
    function makeSectionTitle(text) {
      var t = document.createElement("div");
      t.textContent = text;
      sp(t, "font-size", "0.6875rem");
      sp(t, "font-weight", "500");
      sp(t, "color", "#9c8e80");
      sp(t, "text-transform", "uppercase");
      sp(t, "letter-spacing", "0.1em");
      sp(t, "margin-bottom", "1rem");
      sp(t, "padding-bottom", "0.4rem");
      sp(t, "border-bottom", "1px solid #e2d9c8");
      sp(t, "font-family", "'Inter', sans-serif");
      return t;
    }
    wrap.appendChild(makeSectionTitle("Browse by Category"));
    var catGrid = document.createElement("div");
    sp(catGrid, "display", "grid");
    sp(catGrid, "grid-template-columns", "repeat(3, 1fr)");
    sp(catGrid, "gap", "8px");
    sp(catGrid, "margin-bottom", "2.5rem");
    var countEls = {};
    CATS.forEach(function(cat) {
      var card = document.createElement("a");
      card.href = cat.href;
      sp(card, "border", cat.highlight ? "1.5px solid #c8a060" : "1px solid #e2d9c8");
      sp(card, "border-radius", "6px");
      sp(card, "background", cat.highlight ? "#f5ede0" : "#ffffff");
      sp(card, "padding", "1rem 0.5rem");
      sp(card, "text-align", "center");
      sp(card, "text-decoration", "none");
      sp(card, "display", "block");
      sp(card, "box-sizing", "border-box");
      if (cat.highlight) {
        card.addEventListener("mouseover", function() { this.style.setProperty("background", "#ede0cc", "important"); this.style.setProperty("border-color", "#a07840", "important"); });
        card.addEventListener("mouseout",  function() { this.style.setProperty("background", "#f5ede0", "important"); this.style.setProperty("border-color", "#c8a060", "important"); });
      } else {
        card.addEventListener("mouseover", function() { this.style.setProperty("background", "#f0ebe0", "important"); this.style.setProperty("border-color", "#c8b99a", "important"); });
        card.addEventListener("mouseout",  function() { this.style.setProperty("background", "#ffffff", "important"); this.style.setProperty("border-color", "#e2d9c8", "important"); });
      }
      var icon = document.createElement("i");
      icon.className = "ti " + cat.icon;
      sp(icon, "font-size", "1.25rem");
      sp(icon, "color", cat.highlight ? "#a07840" : "#c8a060");
      sp(icon, "display", "block");
      sp(icon, "margin-bottom", "0.4rem");
      var name = document.createElement("div");
      name.textContent = cat.label;
      sp(name, "font-size", "0.75rem");
      sp(name, "font-weight", "500");
      sp(name, "color", cat.highlight ? "#a07840" : "#2a2218");
      sp(name, "font-family", "'Inter', sans-serif");
      var count = document.createElement("div");
      count.textContent = "—";
      sp(count, "font-size", "0.65rem");
      sp(count, "color", "#9c8e80");
      sp(count, "margin-top", "0.2rem");
      sp(count, "font-family", "'Inter', sans-serif");
      countEls[cat.label] = count;
      card.appendChild(icon);
      card.appendChild(name);
      card.appendChild(count);
      catGrid.appendChild(card);
    });
    wrap.appendChild(catGrid);
    var catTitles = CATS.map(function(c) { return "Category:" + c.label; }).join("|");
    fetch("/api.php?action=query&prop=categoryinfo&titles=" + encodeURIComponent(catTitles) + "&format=json")
      .then(function(r) { return r.json(); })
      .then(function(data) {
        var pages = data.query && data.query.pages ? data.query.pages : {};
        var totalDocs = 0;
        Object.keys(pages).forEach(function(id) {
          var page = pages[id];
          var catName = (page.title || "").replace("Category:", "");
          var cnt = (page.categoryinfo && page.categoryinfo.pages) ? page.categoryinfo.pages : 0;
          if (countEls[catName]) {
            countEls[catName].textContent = cnt + (cnt === 1 ? " article" : " articles");
          }
          totalDocs += cnt;
        });
        statNum.textContent = totalDocs;
      })
      .catch(function() {});
    var twoCol = document.createElement("div");
    sp(twoCol, "display", "grid");
    sp(twoCol, "grid-template-columns", "1fr 1fr");
    sp(twoCol, "gap", "1.5rem");
    var welcomeWrap = document.createElement("div");
    welcomeWrap.appendChild(makeSectionTitle("New to Erakh Codex?"));
    var welcomeBox = document.createElement("div");
    sp(welcomeBox, "background", "#ffffff");
    sp(welcomeBox, "border", "1px solid #e2d9c8");
    sp(welcomeBox, "border-radius", "6px");
    sp(welcomeBox, "padding", "1.25rem 1.5rem");
    sp(welcomeBox, "display", "flex");
    sp(welcomeBox, "align-items", "flex-start");
    sp(welcomeBox, "gap", "0.875rem");
    sp(welcomeBox, "box-sizing", "border-box");
    var wIcon = document.createElement("i");
    wIcon.className = "ti ti-info-circle";
    sp(wIcon, "font-size", "1.125rem");
    sp(wIcon, "color", "#c8a060");
    sp(wIcon, "flex-shrink", "0");
    sp(wIcon, "margin-top", "0.1rem");
    var wText = document.createElement("div");
    var wTitle = document.createElement("div");
    wTitle.textContent = "Welcome to Erakh Codex";
    sp(wTitle, "font-size", "0.875rem");
    sp(wTitle, "font-weight", "500");
    sp(wTitle, "color", "#2a2218");
    sp(wTitle, "margin-bottom", "0.3rem");
    sp(wTitle, "font-family", "'Inter', sans-serif");
    var wDesc = document.createElement("div");
    sp(wDesc, "font-size", "0.775rem");
    sp(wDesc, "color", "#6b5e4e");
    sp(wDesc, "line-height", "1.65");
    sp(wDesc, "font-family", "'Inter', sans-serif");
    wDesc.innerHTML = "The official encyclopedia of the Mirtess Chronicle, maintained by the author. Before contributing, please read the <a href='/index.php?title=Erakh_Codex:Policies' style='color:#c8a060;text-decoration:none;font-weight:500;'>Policies & Guidelines</a>.";
    wText.appendChild(wTitle);
    wText.appendChild(wDesc);
    welcomeBox.appendChild(wIcon);
    welcomeBox.appendChild(wText);
    welcomeWrap.appendChild(welcomeBox);
    twoCol.appendChild(welcomeWrap);
    var recentWrap = document.createElement("div");
    recentWrap.appendChild(makeSectionTitle("Recent Changes"));
    var recentBox = document.createElement("div");
    sp(recentBox, "background", "#ffffff");
    sp(recentBox, "border", "1px solid #e2d9c8");
    sp(recentBox, "border-radius", "6px");
    sp(recentBox, "overflow", "hidden");
    var loadingMsg = document.createElement("div");
    loadingMsg.textContent = "Loading...";
    sp(loadingMsg, "padding", "1rem");
    sp(loadingMsg, "font-size", "0.78rem");
    sp(loadingMsg, "color", "#9c8e80");
    sp(loadingMsg, "font-family", "'Inter', sans-serif");
    recentBox.appendChild(loadingMsg);
    fetch("/api.php?action=query&list=recentchanges&rcnamespace=0&rclimit=10&rcprop=title|timestamp&rctype=edit|new&format=json")
      .then(function(r) { return r.json(); })
      .then(function(data) {
        recentBox.innerHTML = "";
        var changes = data.query && data.query.recentchanges ? data.query.recentchanges : [];
        var seen = {};
        var unique = [];
        changes.forEach(function(c) {
          if (!seen[c.title]) { seen[c.title] = true; unique.push(c); }
        });
        if (unique.length === 0) {
          var empty = document.createElement("div");
          empty.textContent = "No documents yet.";
          sp(empty, "padding", "1.5rem 1rem");
          sp(empty, "font-size", "0.78rem");
          sp(empty, "color", "#9c8e80");
          sp(empty, "text-align", "center");
          sp(empty, "font-family", "'Inter', sans-serif");
          recentBox.appendChild(empty);
          return;
        }
        unique.forEach(function(c, i) {
          var row = document.createElement("a");
          row.href = "/index.php?title=" + encodeURIComponent(c.title.replace(/ /g, "_"));
          sp(row, "display", "flex");
          sp(row, "align-items", "center");
          sp(row, "justify-content", "space-between");
          sp(row, "padding", "0.55rem 1rem");
          sp(row, "border-bottom", i < unique.length - 1 ? "1px solid #f0ebe0" : "none");
          sp(row, "text-decoration", "none");
          row.addEventListener("mouseover", function() { this.style.setProperty("background", "#f0ebe0", "important"); });
          row.addEventListener("mouseout",  function() { this.style.setProperty("background", "transparent", "important"); });
          var title = document.createElement("div");
          title.textContent = c.title;
          sp(title, "font-size", "0.78rem");
          sp(title, "color", "#2a2218");
          sp(title, "font-family", "'Inter', sans-serif");
          var date = document.createElement("div");
          var d = new Date(c.timestamp);
          date.textContent = d.getFullYear() + "." + String(d.getMonth() + 1).padStart(2, "0") + "." + String(d.getDate()).padStart(2, "0");
          sp(date, "font-size", "0.7rem");
          sp(date, "color", "#9c8e80");
          sp(date, "flex-shrink", "0");
          sp(date, "margin-left", "1rem");
          sp(date, "font-family", "'Inter', sans-serif");
          row.appendChild(title);
          row.appendChild(date);
          recentBox.appendChild(row);
        });
      })
      .catch(function() {
        recentBox.innerHTML = "";
        var err = document.createElement("div");
        err.textContent = "Could not load recent changes.";
        sp(err, "padding", "1rem");
        sp(err, "font-size", "0.78rem");
        sp(err, "color", "#9c8e80");
        sp(err, "font-family", "'Inter', sans-serif");
        recentBox.appendChild(err);
      });
    recentWrap.appendChild(recentBox);
    twoCol.appendChild(recentWrap);
    wrap.appendChild(twoCol);
    target.appendChild(wrap);
  }
  // ─── 실행 ───
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", function() {
      buildLayout();
      buildCreatePage();
      buildMainPage();
    });
  } else {
    buildLayout();
    buildCreatePage();
    buildMainPage();
  }
})();
})();

Latest revision as of 18:03, 13 June 2026

(function () {
  var fontLink = document.createElement("link");
  fontLink.rel = "stylesheet";
  fontLink.href = "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap";
  document.head.insertBefore(fontLink, document.head.firstChild);

  function getCookie(name) {
    var match = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)"));
    return match ? decodeURIComponent(match[1]) : null;
  }

  var user = null;
  try {
    var raw = getCookie("erakh_user");
    if (raw) user = JSON.parse(raw);
  } catch (e) {}

  var pageName = mw.config.get("wgPageName");
  var isSysop  = (mw.config.get("wgUserGroups") || []).indexOf("sysop") !== -1;

  // proactive 토큰 갱신
  if (user && user.exp) {
    var now = Math.floor(Date.now() / 1000);
    var remaining = user.exp - now;
    if (remaining < 5 * 60) {
      fetch("https://erakh.com/api/auth/refresh", { method: "POST", credentials: "include" })
        .catch(function() {});
    } else {
      var delay = (remaining - 5 * 60) * 1000;
      setTimeout(function() {
        fetch("https://erakh.com/api/auth/refresh", { method: "POST", credentials: "include" })
          .catch(function() {});
      }, delay);
    }
  }

  function sp(el, prop, val) {
    el.style.setProperty(prop, val, "important");
  }

  // ─── 헤더 ───
  var header = document.createElement("header");
  header.id = "erakh-header";
  sp(header, "position", "fixed");
  sp(header, "top", "0");
  sp(header, "left", "0");
  sp(header, "right", "0");
  sp(header, "z-index", "9999");
  sp(header, "width", "100%");
  sp(header, "background", "#f9f6f0");
  sp(header, "border-bottom", "1px solid #e2d9c8");
  sp(header, "display", "flex");
  sp(header, "align-items", "center");
  sp(header, "justify-content", "space-between");
  sp(header, "padding", "0 2rem");
  sp(header, "height", "56px");
  sp(header, "box-sizing", "border-box");

  var left = document.createElement("div");
  sp(left, "display", "flex");
  sp(left, "align-items", "center");
  sp(left, "gap", "1.75rem");

  var logo = document.createElement("a");
  logo.href = "https://erakh.com";
  logo.textContent = "Erakh Archive";
  sp(logo, "font-family", "'Inter', sans-serif");
  sp(logo, "font-size", "1.125rem");
  sp(logo, "font-weight", "500");
  sp(logo, "color", "#2a2218");
  sp(logo, "text-decoration", "none");
  sp(logo, "letter-spacing", "-0.01em");
  sp(logo, "text-transform", "none");
  sp(logo, "font-variant", "normal");
  sp(logo, "font-variant-caps", "normal");
  sp(logo, "line-height", "1");

  var nav = document.createElement("nav");
  sp(nav, "display", "flex");
  sp(nav, "align-items", "center");
  sp(nav, "gap", "1.5rem");

  var codexLink = document.createElement("a");
  codexLink.href = "https://codex.erakh.com";
  codexLink.textContent = "Codex";
  sp(codexLink, "font-family", "'Inter', sans-serif");
  sp(codexLink, "font-size", "1rem");
  sp(codexLink, "color", "#6b5e4e");
  sp(codexLink, "text-decoration", "none");
  sp(codexLink, "text-transform", "none");
  sp(codexLink, "font-variant", "normal");
  sp(codexLink, "line-height", "1");
  codexLink.addEventListener("mouseover", function() { this.style.setProperty("color", "#2a2218", "important"); });
  codexLink.addEventListener("mouseout",  function() { this.style.setProperty("color", "#6b5e4e", "important"); });

  nav.appendChild(codexLink);
  left.appendChild(logo);
  left.appendChild(nav);

  var right = document.createElement("div");
  sp(right, "display", "flex");
  sp(right, "align-items", "center");

  if (user && user.nickname) {
    var profile = document.createElement("div");
    sp(profile, "position", "relative");

    var avatar = document.createElement("div");
    avatar.textContent = user.nickname.slice(0, 1).toUpperCase();
    sp(avatar, "width", "32px");
    sp(avatar, "height", "32px");
    sp(avatar, "border-radius", "50%");
    sp(avatar, "background", "#e2d9c8");
    sp(avatar, "border", "1px solid #c8b99a");
    sp(avatar, "display", "flex");
    sp(avatar, "align-items", "center");
    sp(avatar, "justify-content", "center");
    sp(avatar, "cursor", "pointer");
    sp(avatar, "font-family", "'Inter', sans-serif");
    sp(avatar, "font-size", "0.75rem");
    sp(avatar, "font-weight", "500");
    sp(avatar, "color", "#2a2218");
    sp(avatar, "box-sizing", "border-box");

    var dropdown = document.createElement("div");
    sp(dropdown, "display", "none");
    sp(dropdown, "position", "absolute");
    sp(dropdown, "top", "calc(100% + 8px)");
    sp(dropdown, "right", "0");
    sp(dropdown, "background", "#f9f6f0");
    sp(dropdown, "border", "1px solid #e2d9c8");
    sp(dropdown, "min-width", "220px");
    sp(dropdown, "padding", "0");
    sp(dropdown, "box-shadow", "0 4px 16px rgba(0,0,0,0.06)");
    sp(dropdown, "z-index", "10000");
    sp(dropdown, "box-sizing", "border-box");

    var dpProfile = document.createElement("div");
    sp(dpProfile, "padding", "1rem");
    sp(dpProfile, "display", "flex");
    sp(dpProfile, "flex-direction", "column");
    sp(dpProfile, "align-items", "center");
    sp(dpProfile, "gap", "0.25rem");
    sp(dpProfile, "border-bottom", "1px solid #e2d9c8");

    var dpAvatar = document.createElement("div");
    dpAvatar.textContent = user.nickname.slice(0, 1).toUpperCase();
    sp(dpAvatar, "width", "44px");
    sp(dpAvatar, "height", "44px");
    sp(dpAvatar, "border-radius", "50%");
    sp(dpAvatar, "background", "#f0ebe0");
    sp(dpAvatar, "border", "1px solid #c8b99a");
    sp(dpAvatar, "display", "flex");
    sp(dpAvatar, "align-items", "center");
    sp(dpAvatar, "justify-content", "center");
    sp(dpAvatar, "font-family", "'Inter', sans-serif");
    sp(dpAvatar, "font-size", "1rem");
    sp(dpAvatar, "font-weight", "500");
    sp(dpAvatar, "color", "#c8a060");
    sp(dpAvatar, "margin-bottom", "0.25rem");

    var dpName = document.createElement("div");
    dpName.textContent = user.nickname;
    sp(dpName, "font-family", "'Inter', sans-serif");
    sp(dpName, "font-size", "0.875rem");
    sp(dpName, "font-weight", "500");
    sp(dpName, "color", "#2a2218");

    var dpTitle = document.createElement("div");
    dpTitle.textContent = user.title || "Chronicler";
    sp(dpTitle, "font-family", "'Inter', sans-serif");
    sp(dpTitle, "font-size", "0.6875rem");
    sp(dpTitle, "color", "#9c8e80");
    sp(dpTitle, "font-style", "italic");

    dpProfile.appendChild(dpAvatar);
    dpProfile.appendChild(dpName);
    dpProfile.appendChild(dpTitle);

    var dpStats = document.createElement("div");
    sp(dpStats, "display", "grid");
    sp(dpStats, "grid-template-columns", "1fr 1fr");
    sp(dpStats, "border-bottom", "1px solid #e2d9c8");

    function makeStatCell(val, lbl, borderRight, borderBottom) {
      var cell = document.createElement("div");
      sp(cell, "padding", "0.5rem 0");
      sp(cell, "text-align", "center");
      if (borderRight) sp(cell, "border-right", "1px solid #e2d9c8");
      if (borderBottom) sp(cell, "border-bottom", "1px solid #e2d9c8");
      var valEl = document.createElement("div");
      valEl.textContent = val;
      valEl.className = "dp-stat-val";
      sp(valEl, "font-family", "'Inter', sans-serif");
      sp(valEl, "font-size", "0.8125rem");
      sp(valEl, "font-weight", "500");
      sp(valEl, "color", "#2a2218");
      sp(valEl, "margin-bottom", "2px");
      var lblEl = document.createElement("div");
      lblEl.textContent = lbl;
      sp(lblEl, "font-family", "'Inter', sans-serif");
      sp(lblEl, "font-size", "0.5625rem");
      sp(lblEl, "letter-spacing", "0.1em");
      sp(lblEl, "color", "#9c8e80");
      sp(lblEl, "text-transform", "uppercase");
      cell.appendChild(valEl);
      cell.appendChild(lblEl);
      return cell;
    }

    var cellLevel = makeStatCell("—", "Level",       true,  true);
    var cellExp   = makeStatCell("—", "EXP",         false, true);
    var cellDays  = makeStatCell("—", "Days Active", true,  false);
    var cellEdits = makeStatCell("—", "Edits",       false, false);

    dpStats.appendChild(cellLevel);
    dpStats.appendChild(cellExp);
    dpStats.appendChild(cellDays);
    dpStats.appendChild(cellEdits);

    var dpMenu = document.createElement("div");
    sp(dpMenu, "padding", "0.25rem 0");

    var profileLink = document.createElement("a");
    profileLink.href = "https://erakh.com/profile";
    profileLink.textContent = "Profile";
    sp(profileLink, "display", "block");
    sp(profileLink, "width", "100%");
    sp(profileLink, "padding", "0.5rem 1rem");
    sp(profileLink, "font-family", "'Inter', sans-serif");
    sp(profileLink, "font-size", "0.8125rem");
    sp(profileLink, "color", "#2a2218");
    sp(profileLink, "text-decoration", "none");
    sp(profileLink, "box-sizing", "border-box");
    profileLink.addEventListener("mouseover", function() { this.style.setProperty("background", "#f0ebe0", "important"); });
    profileLink.addEventListener("mouseout",  function() { this.style.setProperty("background", "transparent", "important"); });

    var signOutBtn = document.createElement("button");
    signOutBtn.textContent = "Sign out";
    sp(signOutBtn, "display", "block");
    sp(signOutBtn, "width", "100%");
    sp(signOutBtn, "padding", "0.5rem 1rem");
    sp(signOutBtn, "font-family", "'Inter', sans-serif");
    sp(signOutBtn, "font-size", "0.8125rem");
    sp(signOutBtn, "color", "#a04040");
    sp(signOutBtn, "text-align", "left");
    sp(signOutBtn, "background", "transparent");
    sp(signOutBtn, "border", "none");
    sp(signOutBtn, "cursor", "pointer");
    sp(signOutBtn, "box-sizing", "border-box");
    signOutBtn.addEventListener("mouseover", function() { this.style.setProperty("background", "#f0ebe0", "important"); });
    signOutBtn.addEventListener("mouseout",  function() { this.style.setProperty("background", "transparent", "important"); });
    signOutBtn.addEventListener("click", function () {
      fetch("https://erakh.com/api/auth/logout", { method: "POST", credentials: "include" })
        .then(function () { window.location.href = "https://erakh.com"; });
    });

    dpMenu.appendChild(profileLink);
    dpMenu.appendChild(signOutBtn);

    dropdown.appendChild(dpProfile);
    dropdown.appendChild(dpStats);
    dropdown.appendChild(dpMenu);

    var profileLoaded = false;

    avatar.addEventListener("click", function () {
      var isHidden = dropdown.style.display === "none";
      dropdown.style.display = isHidden ? "block" : "none";
      if (isHidden && !profileLoaded) {
        fetch("https://erakh.com/api/auth/me", { method: "GET", credentials: "include" })
          .then(function(r) { return r.json(); })
          .then(function(data) {
            if (data.isNew) return;
            profileLoaded = true;
            cellLevel.querySelector(".dp-stat-val").textContent = "Lv. " + data.level;
            cellExp.querySelector(".dp-stat-val").textContent   = data.exp_in_level + " / " + data.exp_to_next;
            cellDays.querySelector(".dp-stat-val").textContent  = data.login_days;
            cellEdits.querySelector(".dp-stat-val").textContent = data.edit_count;
          })
          .catch(function() {});
      }
    });

    document.addEventListener("click", function (e) {
      if (!profile.contains(e.target)) {
        dropdown.style.display = "none";
      }
    });

    profile.appendChild(avatar);
    profile.appendChild(dropdown);
    right.appendChild(profile);

  } else {
    var loginBtn = document.createElement("a");
    loginBtn.href = "https://erakh.com/login?redirect=" + encodeURIComponent(window.location.href);
    loginBtn.textContent = "Sign in";
    sp(loginBtn, "font-family", "'Inter', sans-serif");
    sp(loginBtn, "font-size", "0.8125rem");
    sp(loginBtn, "font-weight", "500");
    sp(loginBtn, "color", "#2a2218");
    sp(loginBtn, "text-decoration", "none");
    sp(loginBtn, "padding", "0.4rem 1rem");
    sp(loginBtn, "border", "1px solid #c8b99a");
    sp(loginBtn, "background", "transparent");
    sp(loginBtn, "cursor", "pointer");
    sp(loginBtn, "box-sizing", "border-box");
    sp(loginBtn, "line-height", "1");
    loginBtn.addEventListener("mouseover", function() {
      this.style.setProperty("border-color", "#c8a060", "important");
      this.style.setProperty("background", "#f0ebe0", "important");
    });
    loginBtn.addEventListener("mouseout", function() {
      this.style.setProperty("border-color", "#c8b99a", "important");
      this.style.setProperty("background", "transparent", "important");
    });
    right.appendChild(loginBtn);
  }

  header.appendChild(left);
  header.appendChild(right);
  document.body.insertBefore(header, document.body.firstChild);

  // ─── 서브바 ───
  var subbar = document.createElement("div");
  subbar.id = "erakh-subbar";
  sp(subbar, "position", "fixed");
  sp(subbar, "top", "56px");
  sp(subbar, "left", "0");
  sp(subbar, "right", "0");
  sp(subbar, "z-index", "9998");
  sp(subbar, "height", "44px");
  sp(subbar, "background", "#f0ebe0");
  sp(subbar, "border-bottom", "1px solid #e2d9c8");
  sp(subbar, "display", "flex");
  sp(subbar, "align-items", "center");
  sp(subbar, "gap", "1rem");
  sp(subbar, "padding", "0 1.5rem");
  sp(subbar, "box-sizing", "border-box");
  sp(subbar, "width", "100%");

  var hbBtn = document.createElement("button");
  sp(hbBtn, "display", "flex");
  sp(hbBtn, "flex-direction", "column");
  sp(hbBtn, "justify-content", "center");
  sp(hbBtn, "align-items", "center");
  sp(hbBtn, "gap", "5px");
  sp(hbBtn, "width", "32px");
  sp(hbBtn, "height", "32px");
  sp(hbBtn, "border", "none");
  sp(hbBtn, "background", "transparent");
  sp(hbBtn, "cursor", "pointer");
  sp(hbBtn, "padding", "4px");
  sp(hbBtn, "border-radius", "4px");
  sp(hbBtn, "flex-shrink", "0");
  sp(hbBtn, "box-sizing", "border-box");

  function makeBar() {
    var b = document.createElement("span");
    b.style.setProperty("display", "block", "important");
    b.style.setProperty("width", "18px", "important");
    b.style.setProperty("height", "1.5px", "important");
    b.style.setProperty("background", "#6b5e4e", "important");
    b.style.setProperty("border-radius", "1px", "important");
    return b;
  }
  hbBtn.appendChild(makeBar());
  hbBtn.appendChild(makeBar());
  hbBtn.appendChild(makeBar());
  hbBtn.addEventListener("mouseover", function() { this.style.setProperty("background", "#e2d9c8", "important"); });
  hbBtn.addEventListener("mouseout",  function() { this.style.setProperty("background", "transparent", "important"); });

  var searchWrap = document.createElement("div");
  sp(searchWrap, "position", "relative");
  sp(searchWrap, "flex", "1");
  sp(searchWrap, "max-width", "520px");

  var searchIconEl = document.createElement("span");
  searchIconEl.innerHTML = "&#9906;";
  sp(searchIconEl, "position", "absolute");
  sp(searchIconEl, "left", "0.6rem");
  sp(searchIconEl, "top", "50%");
  sp(searchIconEl, "transform", "translateY(-50%)");
  sp(searchIconEl, "font-size", "13px");
  sp(searchIconEl, "color", "#9c8e80");
  sp(searchIconEl, "pointer-events", "none");
  sp(searchIconEl, "line-height", "1");

  var searchInput = document.createElement("input");
  searchInput.type = "text";
  searchInput.placeholder = "Search Erakh Codex...";
  sp(searchInput, "width", "100%");
  sp(searchInput, "height", "30px");
  sp(searchInput, "padding", "0 0.75rem 0 2rem");
  sp(searchInput, "border", "1px solid #c8b99a");
  sp(searchInput, "border-radius", "3px");
  sp(searchInput, "background", "#f9f6f0");
  sp(searchInput, "font-family", "'Inter', sans-serif");
  sp(searchInput, "font-size", "0.8125rem");
  sp(searchInput, "color", "#2a2218");
  sp(searchInput, "outline", "none");
  sp(searchInput, "box-sizing", "border-box");

  var suggestBox = document.createElement("div");
  sp(suggestBox, "position", "absolute");
  sp(suggestBox, "top", "calc(100% + 2px)");
  sp(suggestBox, "left", "0");
  sp(suggestBox, "right", "0");
  sp(suggestBox, "background", "#f9f6f0");
  sp(suggestBox, "border", "1px solid #c8b99a");
  sp(suggestBox, "border-radius", "3px");
  sp(suggestBox, "z-index", "10001");
  sp(suggestBox, "display", "none");
  sp(suggestBox, "box-shadow", "0 4px 12px rgba(0,0,0,0.08)");
  searchWrap.appendChild(suggestBox);

  var suggestItems = [];
  var suggestIndex = -1;

  function renderSuggestions(titles) {
    suggestBox.innerHTML = "";
    suggestItems = titles;
    suggestIndex = -1;
    if (titles.length === 0) { suggestBox.style.setProperty("display", "none", "important"); return; }
    titles.forEach(function(title, i) {
      var item = document.createElement("div");
      item.textContent = title;
      sp(item, "padding", "0.4rem 0.75rem");
      sp(item, "font-family", "'Inter', sans-serif");
      sp(item, "font-size", "0.8125rem");
      sp(item, "color", "#2a2218");
      sp(item, "cursor", "pointer");
      sp(item, "border-bottom", i < titles.length - 1 ? "1px solid #f0ebe0" : "none");
      item.addEventListener("mouseover", function() { this.style.setProperty("background", "#f0ebe0", "important"); });
      item.addEventListener("mouseout",  function() { this.style.setProperty("background", "transparent", "important"); });
      item.addEventListener("mousedown", function(e) {
        e.preventDefault();
        window.location.href = "/index.php?title=" + encodeURIComponent(title.replace(/ /g, "_"));
      });
      suggestBox.appendChild(item);
    });
    suggestBox.style.setProperty("display", "block", "important");
  }

  var suggestTimer = null;
  searchInput.addEventListener("input", function() {
    var q = this.value.trim();
    clearTimeout(suggestTimer);
    if (q.length < 2) { suggestBox.style.setProperty("display", "none", "important"); return; }
    suggestTimer = setTimeout(function() {
      fetch("/api.php?action=opensearch&search=" + encodeURIComponent(q) + "&limit=8&namespace=0&format=json")
        .then(function(r) { return r.json(); })
        .then(function(data) { renderSuggestions(data[1] || []); })
        .catch(function() {});
    }, 200);
  });

  searchInput.addEventListener("focus", function() { this.style.setProperty("border-color", "#c8a060", "important"); });
  searchInput.addEventListener("blur",  function() {
    this.style.setProperty("border-color", "#c8b99a", "important");
    setTimeout(function() { suggestBox.style.setProperty("display", "none", "important"); }, 150);
  });
  searchInput.addEventListener("keydown", function(e) {
    var items = suggestBox.querySelectorAll("div");
    if (e.key === "ArrowDown") {
      e.preventDefault();
      suggestIndex = Math.min(suggestIndex + 1, items.length - 1);
      items.forEach(function(el, i) { el.style.setProperty("background", i === suggestIndex ? "#f0ebe0" : "transparent", "important"); });
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      suggestIndex = Math.max(suggestIndex - 1, -1);
      items.forEach(function(el, i) { el.style.setProperty("background", i === suggestIndex ? "#f0ebe0" : "transparent", "important"); });
    } else if (e.key === "Enter") {
      e.preventDefault();
      if (suggestIndex >= 0 && suggestItems[suggestIndex]) {
        window.location.href = "/index.php?title=" + encodeURIComponent(suggestItems[suggestIndex].replace(/ /g, "_"));
      } else if (this.value.trim()) {
        window.location.href = "/index.php?title=Special%3ASearch&search=" + encodeURIComponent(this.value.trim());
      }
    } else if (e.key === "Escape") {
      suggestBox.style.setProperty("display", "none", "important");
    }
  });

  searchWrap.appendChild(searchIconEl);
  searchWrap.appendChild(searchInput);
  subbar.appendChild(hbBtn);
  subbar.appendChild(searchWrap);

  if (isSysop) {
    var createDocBtn = document.createElement("a");
    createDocBtn.href = "/index.php?title=Erakh_Codex:Create";
    createDocBtn.textContent = "+ Create";
    sp(createDocBtn, "display", "flex");
    sp(createDocBtn, "align-items", "center");
    sp(createDocBtn, "height", "44px");
    sp(createDocBtn, "padding", "0 1.25rem");
    sp(createDocBtn, "margin-left", "auto");
    sp(createDocBtn, "font-family", "'Inter', sans-serif");
    sp(createDocBtn, "font-size", "0.8125rem");
    sp(createDocBtn, "font-weight", "500");
    sp(createDocBtn, "color", "#c8a060");
    sp(createDocBtn, "text-decoration", "none");
    sp(createDocBtn, "border-left", "1px solid #e2d9c8");
    sp(createDocBtn, "flex-shrink", "0");
    sp(createDocBtn, "box-sizing", "border-box");
    sp(createDocBtn, "letter-spacing", "-0.01em");
    createDocBtn.addEventListener("mouseover", function() {
      this.style.setProperty("background", "#e2d9c8", "important");
      this.style.setProperty("color", "#a07840", "important");
    });
    createDocBtn.addEventListener("mouseout", function() {
      this.style.setProperty("background", "transparent", "important");
      this.style.setProperty("color", "#c8a060", "important");
    });
    subbar.appendChild(createDocBtn);
  }

  // ─── 드로어 ───
  var drawerOverlay = document.createElement("div");
  sp(drawerOverlay, "position", "fixed");
  sp(drawerOverlay, "top", "100px");
  sp(drawerOverlay, "left", "0");
  sp(drawerOverlay, "right", "0");
  sp(drawerOverlay, "bottom", "0");
  sp(drawerOverlay, "background", "rgba(0,0,0,0.18)");
  sp(drawerOverlay, "z-index", "9996");
  sp(drawerOverlay, "display", "none");

  var drawer = document.createElement("nav");
  sp(drawer, "position", "fixed");
  sp(drawer, "top", "100px");
  sp(drawer, "left", "0");
  sp(drawer, "bottom", "0");
  sp(drawer, "width", "220px");
  sp(drawer, "background", "#f9f6f0");
  sp(drawer, "border-right", "1px solid #e2d9c8");
  sp(drawer, "z-index", "9997");
  sp(drawer, "display", "none");
  sp(drawer, "overflow-y", "auto");

  var drawerHead = document.createElement("div");
  sp(drawerHead, "padding", "0.75rem 1rem 0.5rem");
  sp(drawerHead, "border-bottom", "1px solid #e2d9c8");
  var drawerLabel = document.createElement("span");
  drawerLabel.textContent = "Menu";
  sp(drawerLabel, "font-family", "'Inter', sans-serif");
  sp(drawerLabel, "font-size", "0.78rem");
  sp(drawerLabel, "font-weight", "500");
  sp(drawerLabel, "color", "#2a2218");
  sp(drawerLabel, "display", "inline-block");
  sp(drawerLabel, "padding-bottom", "0.25rem");
  sp(drawerLabel, "border-bottom", "2px solid #c8a060");
  drawerHead.appendChild(drawerLabel);
  drawer.appendChild(drawerHead);

  var drawerMenus = [
    {
      items: [
        { label: "Main Page",       href: "/" },
        { label: "Categories",      href: "/index.php?title=Special:Categories" },
        { label: "Timeline",        href: "/index.php?title=Category:Chronicle" },
        { label: "Recent Changes",  href: "/index.php?title=Special:RecentChanges" },
        { label: "Random Article",  href: "/index.php?title=Special:Random" },
      ],
      gold: true
    },
    {
      items: [
        { label: "Policies & Guidelines", href: "/index.php?title=Erakh_Codex:Policies" },
      ],
      gold: false
    }
  ];

  drawerMenus.forEach(function(section, si) {
    var sec = document.createElement("div");
    sp(sec, "padding", "0.5rem 0");
    if (si < drawerMenus.length - 1) sp(sec, "border-bottom", "1px solid #e2d9c8");
    section.items.forEach(function(item) {
      var a = document.createElement("a");
      a.href = item.href;
      a.textContent = item.label;
      sp(a, "display", "block");
      sp(a, "padding", "0.45rem 1.25rem");
      sp(a, "font-family", "'Inter', sans-serif");
      sp(a, "font-size", "0.8125rem");
      sp(a, "color", section.gold ? "#c8a060" : "#6b5e4e");
      sp(a, "text-decoration", "none");
      a.addEventListener("mouseover", function() { this.style.setProperty("background", "#f0ebe0", "important"); });
      a.addEventListener("mouseout",  function() { this.style.setProperty("background", "transparent", "important"); });
      sec.appendChild(a);
    });
    drawer.appendChild(sec);
  });

  var drawerOpen = false;
  function toggleDrawer() {
    drawerOpen = !drawerOpen;
    drawer.style.setProperty("display",        drawerOpen ? "block" : "none", "important");
    drawerOverlay.style.setProperty("display", drawerOpen ? "block" : "none", "important");
  }
  hbBtn.addEventListener("click", toggleDrawer);
  drawerOverlay.addEventListener("click", toggleDrawer);

  document.body.insertBefore(subbar, document.body.children[1]);
  document.body.appendChild(drawerOverlay);
  document.body.appendChild(drawer);

  // ─── 3컬럼 레이아웃 (JS 직접 목차 빌드) ───
  function buildToc(headings) {
    var toc = document.createElement("nav");
    toc.id = "erakh-toc";
    var ul = document.createElement("ul");
    sp(ul, "list-style", "none");
    sp(ul, "margin", "0");
    sp(ul, "padding", "0");

    headings.forEach(function(h, i) {
      var level = parseInt(h.tagName.replace("H", ""));
      var id = h.id || ("erakh-section-" + i);
      h.id = id;

      var li = document.createElement("li");
      li.style.setProperty("padding", "0.25rem 0 0.25rem " + (level === 2 ? "0.5rem" : "1.25rem"), "important");
      li.style.setProperty("border-left", "2px solid transparent", "important");
      li.style.setProperty("line-height", "1.4", "important");
      li.dataset.id = id;

      var a = document.createElement("a");
      a.href = "#" + id;
      a.textContent = h.textContent.replace(/\[edit\]/g, "").trim();
      sp(a, "font-family", "'Inter', sans-serif");
      sp(a, "font-size", level === 2 ? "0.78rem" : "0.74rem");
      sp(a, "color", level === 2 ? "#6b5e4e" : "#9c8e80");
      sp(a, "text-decoration", "none");
      sp(a, "display", "block");
      a.addEventListener("mouseover", function() { this.style.setProperty("color", "#2a2218", "important"); });
      a.addEventListener("mouseout",  function() { this.style.setProperty("color", level === 2 ? "#6b5e4e" : "#9c8e80", "important"); });
      a.addEventListener("click", function(e) {
        e.preventDefault();
        var target = document.getElementById(id);
        if (target) {
          var _h = document.getElementById("erakh-header");
          var _s = document.getElementById("erakh-subbar");
          var _offset = (_h ? _h.offsetHeight : 56) + (_s ? _s.offsetHeight : 44) + 8;
          var top = target.getBoundingClientRect().top + window.scrollY - _offset;
          window.scrollTo({ top: top, behavior: "smooth" });
        }
      });

      li.appendChild(a);
      ul.appendChild(li);
    });

    toc.appendChild(ul);
    return toc;
  }

  function buildLayout() {
    // 메인페이지 / Create 페이지에서는 레이아웃 빌드 스킵
    if (pageName === "Erakh_Codex:Create") return;
    if (pageName === "Main_Page") return;
    // view 액션이 아닌 경우 (edit, delete 등) 레이아웃 빌드 스킵
    if (mw.config.get("wgAction") !== "view") return;

    var content = document.querySelector(".mw-body") || document.getElementById("content");
    if (!content) return;

    var headings = Array.from(content.querySelectorAll("h2, h3")).filter(function(h) {
      return !h.closest("#toc");
    });

    var layout = document.createElement("div");
    layout.className = "erakh-layout";

    var colLeft = document.createElement("aside");
    colLeft.className = "erakh-col-left";

    var colCenter = document.createElement("div");
    colCenter.className = "erakh-col-center";

    var colRight = document.createElement("aside");
    colRight.className = "erakh-col-right";

    var tocLabel = document.createElement("div");
    tocLabel.className = "erakh-toc-label";
    tocLabel.textContent = "Contents";
    colLeft.appendChild(tocLabel);

    if (headings.length > 0) {
      var toc = buildToc(headings);
      colLeft.appendChild(toc);

      var items = colLeft.querySelectorAll("li[data-id]");
      window.addEventListener("scroll", function() {
        var _h = document.getElementById("erakh-header");
        var _s = document.getElementById("erakh-subbar");
        var _offset = (_h ? _h.offsetHeight : 56) + (_s ? _s.offsetHeight : 44) + 8;
        var scrollY = window.scrollY + _offset;
        var current = null;
        headings.forEach(function(h) {
          if (h.offsetTop <= scrollY) current = h.id;
        });
        items.forEach(function(li) {
          if (li.dataset.id === current) {
            li.style.setProperty("border-left-color", "#c8a060", "important");
            li.querySelector("a").style.setProperty("color", "#2a2218", "important");
            li.querySelector("a").style.setProperty("font-weight", "500", "important");
          } else {
            li.style.setProperty("border-left-color", "transparent", "important");
            var level = li.style.paddingLeft === "0.5rem" ? 2 : 3;
            li.querySelector("a").style.setProperty("color", level === 2 ? "#6b5e4e" : "#9c8e80", "important");
            li.querySelector("a").style.setProperty("font-weight", "400", "important");
          }
        });
      });
    } else {
      var emptyMsg = document.createElement("div");
      sp(emptyMsg, "font-family", "'Inter', sans-serif");
      sp(emptyMsg, "font-size", "0.75rem");
      sp(emptyMsg, "color", "#9c8e80");
      sp(emptyMsg, "padding", "0.25rem 0.5rem");
      emptyMsg.textContent = "—";
      colLeft.appendChild(emptyMsg);
    }

    var sidebarLabel = document.createElement("div");
    sidebarLabel.className = "erakh-sidebar-label";
    sidebarLabel.textContent = "위젯 영역";
    colRight.appendChild(sidebarLabel);

    var ph1 = document.createElement("div");
    ph1.className = "erakh-sidebar-placeholder";
    ph1.textContent = "인기 문서 위젯 예정";
    colRight.appendChild(ph1);

    var ph2 = document.createElement("div");
    ph2.className = "erakh-sidebar-placeholder";
    ph2.textContent = "최근 변경 위젯 예정";
    colRight.appendChild(ph2);

    // ─── sysop 전용 [edit] · [delete] · [json] 버튼 ───
    if (isSysop) {
      var firstHeadingEl = document.getElementById("firstHeading");
      if (firstHeadingEl) {
        var actionWrap = document.createElement("span");
        sp(actionWrap, "margin-left", "0.75rem");
        sp(actionWrap, "font-size", "0.8125rem");
        sp(actionWrap, "font-weight", "400");

        // [edit]
        var editActionLink = document.createElement("a");
        editActionLink.href = "/index.php?title=" + encodeURIComponent(pageName) + "&action=edit";
        editActionLink.textContent = "[edit]";
        sp(editActionLink, "font-family", "'Inter', sans-serif");
        sp(editActionLink, "font-size", "0.8125rem");
        sp(editActionLink, "color", "#9c8e80");
        sp(editActionLink, "text-decoration", "none");
        editActionLink.addEventListener("mouseover", function() { this.style.setProperty("color", "#c8a060", "important"); });
        editActionLink.addEventListener("mouseout",  function() { this.style.setProperty("color", "#9c8e80", "important"); });

        // ·
        var sep1 = document.createElement("span");
        sep1.textContent = " · ";
        sp(sep1, "color", "#c8b99a");
        sp(sep1, "font-family", "'Inter', sans-serif");

        // [delete]
        var deleteActionLink = document.createElement("a");
        deleteActionLink.href = "/index.php?title=" + encodeURIComponent(pageName) + "&action=delete";
        deleteActionLink.textContent = "[delete]";
        sp(deleteActionLink, "font-family", "'Inter', sans-serif");
        sp(deleteActionLink, "font-size", "0.8125rem");
        sp(deleteActionLink, "color", "#9c8e80");
        sp(deleteActionLink, "text-decoration", "none");
        deleteActionLink.addEventListener("mouseover", function() { this.style.setProperty("color", "#a04040", "important"); });
        deleteActionLink.addEventListener("mouseout",  function() { this.style.setProperty("color", "#9c8e80", "important"); });

        // ·
        var sep2 = document.createElement("span");
        sep2.textContent = " · ";
        sp(sep2, "color", "#c8b99a");
        sp(sep2, "font-family", "'Inter', sans-serif");

        // [json] — API URL 클립보드 복사
        var apiUrl = "https://codex.erakh.com/api.php?action=query&titles=" + encodeURIComponent(pageName) + "&prop=revisions&rvprop=content&format=json";

        var jsonBtn = document.createElement("span");
        jsonBtn.textContent = "[json]";
        sp(jsonBtn, "font-family", "'Inter', sans-serif");
        sp(jsonBtn, "font-size", "0.8125rem");
        sp(jsonBtn, "color", "#9c8e80");
        sp(jsonBtn, "cursor", "pointer");
        sp(jsonBtn, "user-select", "none");
        jsonBtn.addEventListener("mouseover", function() { this.style.setProperty("color", "#c8a060", "important"); });
        jsonBtn.addEventListener("mouseout",  function() { this.style.setProperty("color", "#9c8e80", "important"); });
        jsonBtn.addEventListener("click", function() {
          navigator.clipboard.writeText(apiUrl).then(function() {
            jsonBtn.textContent = "[copied!]";
            sp(jsonBtn, "color", "#c8a060");
            setTimeout(function() {
              jsonBtn.textContent = "[json]";
              sp(jsonBtn, "color", "#9c8e80");
            }, 1500);
          }).catch(function() {
            // clipboard API 실패 시 fallback
            var ta = document.createElement("textarea");
            ta.value = apiUrl;
            ta.style.position = "fixed";
            ta.style.opacity = "0";
            document.body.appendChild(ta);
            ta.select();
            document.execCommand("copy");
            document.body.removeChild(ta);
            jsonBtn.textContent = "[copied!]";
            sp(jsonBtn, "color", "#c8a060");
            setTimeout(function() {
              jsonBtn.textContent = "[json]";
              sp(jsonBtn, "color", "#9c8e80");
            }, 1500);
          });
        });

        actionWrap.appendChild(editActionLink);
        actionWrap.appendChild(sep1);
        actionWrap.appendChild(deleteActionLink);
        actionWrap.appendChild(sep2);
        actionWrap.appendChild(jsonBtn);
        firstHeadingEl.appendChild(actionWrap);
      }
    }

    content.parentNode.insertBefore(layout, content);
    colCenter.appendChild(content);

    layout.appendChild(colLeft);
    layout.appendChild(colCenter);
    layout.appendChild(colRight);
  }

  // ─── 문서 생성 페이지 UI ───
  function buildCreatePage() {
    if (pageName !== "Erakh_Codex:Create") return;

    var CATEGORIES = [
      { label: "Characters",    preload: "Template:Preload/Character"    },
      { label: "Nations",       preload: "Template:Preload/Nation"       },
      { label: "Regions",       preload: "Template:Preload/Region"       },
      { label: "Races",         preload: "Template:Preload/Race"         },
      { label: "Organizations", preload: "Template:Preload/Organization" },
      { label: "Events",        preload: "Template:Preload/Event"        },
      { label: "Lore",          preload: "Template:Preload/Lore"         },
      { label: "Artifacts",     preload: "Template:Preload/Artifact"     },
      { label: "Chronicle",     preload: null                            },
    ];

    var firstHeading = document.getElementById("firstHeading");
    if (firstHeading) sp(firstHeading, "display", "none");
    var parseOutput = document.querySelector(".mw-parser-output");
    if (parseOutput) sp(parseOutput, "display", "none");
    var noArticle = document.querySelector(".noarticletext");
    if (noArticle) sp(noArticle, "display", "none");
    var pViews = document.getElementById("p-views");
    if (pViews) sp(pViews, "display", "none");
    var pCactions = document.getElementById("p-cactions");
    if (pCactions) sp(pCactions, "display", "none");

    var target = document.getElementById("mw-content-text");
    if (!target) return;

    if (!isSysop) {
      var denied = document.createElement("div");
      sp(denied, "padding", "60px 40px");
      sp(denied, "text-align", "center");
      sp(denied, "font-family", "'Inter', sans-serif");
      sp(denied, "font-size", "0.875rem");
      sp(denied, "color", "#9c8e80");
      denied.textContent = "Document creation is restricted to administrators.";
      target.appendChild(denied);
      return;
    }

    var selectedCat = null;

    var wrap = document.createElement("div");
    sp(wrap, "max-width", "680px");
    sp(wrap, "margin", "40px auto");
    sp(wrap, "padding", "0 24px 48px");
    sp(wrap, "font-family", "'Inter', sans-serif");
    sp(wrap, "color", "#2a2218");
    sp(wrap, "box-sizing", "border-box");

    var heading = document.createElement("h2");
    heading.textContent = "Create New Document";
    sp(heading, "font-size", "1.375rem");
    sp(heading, "font-weight", "600");
    sp(heading, "margin", "0 0 2rem 0");
    sp(heading, "color", "#2a2218");
    sp(heading, "letter-spacing", "-0.01em");
    sp(heading, "font-family", "'Inter', sans-serif");
    wrap.appendChild(heading);

    var catLabel = document.createElement("p");
    catLabel.textContent = "Select Category";
    sp(catLabel, "font-size", "0.6875rem");
    sp(catLabel, "font-weight", "500");
    sp(catLabel, "color", "#9c8e80");
    sp(catLabel, "margin", "0 0 0.625rem 0");
    sp(catLabel, "letter-spacing", "0.1em");
    sp(catLabel, "text-transform", "uppercase");
    sp(catLabel, "font-family", "'Inter', sans-serif");
    wrap.appendChild(catLabel);

    var grid = document.createElement("div");
    sp(grid, "display", "grid");
    sp(grid, "grid-template-columns", "repeat(5, 1fr)");
    sp(grid, "gap", "8px");
    sp(grid, "margin-bottom", "0.625rem");

    var catCards = [];

    CATEGORIES.forEach(function(cat) {
      var card = document.createElement("button");
      card.textContent = cat.label;
      sp(card, "border", "1.5px solid #e2d9c8");
      sp(card, "border-radius", "6px");
      sp(card, "background", "#f9f6f0");
      sp(card, "padding", "0.75rem 0.375rem");
      sp(card, "font-size", "0.79rem");
      sp(card, "font-weight", "500");
      sp(card, "font-family", "'Inter', sans-serif");
      sp(card, "color", "#2a2218");
      sp(card, "cursor", "pointer");
      sp(card, "text-align", "center");
      sp(card, "line-height", "1.3");
      sp(card, "box-sizing", "border-box");

      card.addEventListener("mouseover", function() {
        if (selectedCat !== cat.label) {
          this.style.setProperty("background", "#f0ebe0", "important");
          this.style.setProperty("border-color", "#c8b99a", "important");
        }
      });
      card.addEventListener("mouseout", function() {
        if (selectedCat !== cat.label) {
          this.style.setProperty("background", "#f9f6f0", "important");
          this.style.setProperty("border-color", "#e2d9c8", "important");
        }
      });
      card.addEventListener("click", function() {
        catCards.forEach(function(c) {
          c.style.setProperty("background", "#f9f6f0", "important");
          c.style.setProperty("border-color", "#e2d9c8", "important");
          c.style.setProperty("color", "#2a2218", "important");
        });
        this.style.setProperty("background", "#c8a060", "important");
        this.style.setProperty("border-color", "#c8a060", "important");
        this.style.setProperty("color", "#ffffff", "important");
        selectedCat = cat.label;
        if (cat.preload === null) {
          chronicleNotice.style.setProperty("display", "block", "important");
        } else {
          chronicleNotice.style.setProperty("display", "none", "important");
        }
        updateBtn();
      });

      catCards.push(card);
      grid.appendChild(card);
    });

    wrap.appendChild(grid);

    var chronicleNotice = document.createElement("p");
    chronicleNotice.textContent = "Chronicle documents use a timeline format. No infobox template will be applied.";
    sp(chronicleNotice, "display", "none");
    sp(chronicleNotice, "font-size", "0.75rem");
    sp(chronicleNotice, "color", "#9c8e80");
    sp(chronicleNotice, "font-style", "italic");
    sp(chronicleNotice, "margin", "0.375rem 0 0 0");
    sp(chronicleNotice, "font-family", "'Inter', sans-serif");
    wrap.appendChild(chronicleNotice);

    var divider = document.createElement("div");
    sp(divider, "border-top", "1px solid #e2d9c8");
    sp(divider, "margin", "1.5rem 0 1.25rem");
    wrap.appendChild(divider);

    var titleLabel = document.createElement("p");
    titleLabel.textContent = "Document Title";
    sp(titleLabel, "font-size", "0.6875rem");
    sp(titleLabel, "font-weight", "500");
    sp(titleLabel, "color", "#9c8e80");
    sp(titleLabel, "margin", "0 0 0.5rem 0");
    sp(titleLabel, "letter-spacing", "0.1em");
    sp(titleLabel, "text-transform", "uppercase");
    sp(titleLabel, "font-family", "'Inter', sans-serif");
    wrap.appendChild(titleLabel);

    var titleInput = document.createElement("input");
    titleInput.type = "text";
    titleInput.placeholder = "Enter document title...";
    sp(titleInput, "width", "100%");
    sp(titleInput, "padding", "0.625rem 0.875rem");
    sp(titleInput, "border", "1.5px solid #e2d9c8");
    sp(titleInput, "border-radius", "6px");
    sp(titleInput, "font-size", "0.9375rem");
    sp(titleInput, "font-family", "'Inter', sans-serif");
    sp(titleInput, "color", "#2a2218");
    sp(titleInput, "background", "#ffffff");
    sp(titleInput, "box-sizing", "border-box");
    sp(titleInput, "outline", "none");
    sp(titleInput, "margin-bottom", "1.25rem");
    titleInput.addEventListener("focus", function() { this.style.setProperty("border-color", "#c8a060", "important"); });
    titleInput.addEventListener("blur",  function() { this.style.setProperty("border-color", "#e2d9c8", "important"); });
    titleInput.addEventListener("input", updateBtn);
    wrap.appendChild(titleInput);

    var createBtn = document.createElement("button");
    createBtn.textContent = "Create Article";
    sp(createBtn, "display", "block");
    sp(createBtn, "width", "100%");
    sp(createBtn, "padding", "0.75rem");
    sp(createBtn, "background", "#e2d9c8");
    sp(createBtn, "border", "none");
    sp(createBtn, "border-radius", "6px");
    sp(createBtn, "font-size", "0.9375rem");
    sp(createBtn, "font-weight", "600");
    sp(createBtn, "font-family", "'Inter', sans-serif");
    sp(createBtn, "color", "#9c8e80");
    sp(createBtn, "cursor", "not-allowed");
    sp(createBtn, "box-sizing", "border-box");

    createBtn.addEventListener("click", function() {
      if (!selectedCat) return;
      var title = titleInput.value.trim();
      if (!title) return;
      var catObj = null;
      for (var i = 0; i < CATEGORIES.length; i++) {
        if (CATEGORIES[i].label === selectedCat) { catObj = CATEGORIES[i]; break; }
      }
      if (!catObj) return;
      var url = "/index.php?title=" + encodeURIComponent(title) + "&action=edit";
      if (catObj.preload) url += "&preload=" + encodeURIComponent(catObj.preload);
      window.location.href = url;
    });

    wrap.appendChild(createBtn);
    target.appendChild(wrap);

    function updateBtn() {
      var ready = selectedCat && titleInput.value.trim().length > 0;
      if (ready) {
        createBtn.style.setProperty("background", "#c8a060", "important");
        createBtn.style.setProperty("color", "#ffffff", "important");
        createBtn.style.setProperty("cursor", "pointer", "important");
      } else {
        createBtn.style.setProperty("background", "#e2d9c8", "important");
        createBtn.style.setProperty("color", "#9c8e80", "important");
        createBtn.style.setProperty("cursor", "not-allowed", "important");
      }
    }
  }

  // ─── 메인페이지 빌드 ───
  function buildMainPage() {
    if (pageName !== "Main_Page") return;

    var firstHeading = document.getElementById("firstHeading");
    if (firstHeading) sp(firstHeading, "display", "none");
    var parseOutput = document.querySelector(".mw-parser-output");
    if (parseOutput) sp(parseOutput, "display", "none");

    var target = document.getElementById("mw-content-text");
    if (!target) return;

    var CATS = [
      { label: "Characters",    icon: "ti-user",     href: "/index.php?title=Category:Characters",    highlight: false },
      { label: "Nations",       icon: "ti-building", href: "/index.php?title=Category:Nations",       highlight: false },
      { label: "Regions",       icon: "ti-map",      href: "/index.php?title=Category:Regions",       highlight: false },
      { label: "Races",         icon: "ti-dna",      href: "/index.php?title=Category:Races",         highlight: false },
      { label: "Chronicle",     icon: "ti-clock",    href: "/index.php?title=Category:Chronicle",     highlight: true  },
      { label: "Organizations", icon: "ti-users",    href: "/index.php?title=Category:Organizations", highlight: false },
      { label: "Events",        icon: "ti-sword",    href: "/index.php?title=Category:Events",        highlight: false },
      { label: "Lore",          icon: "ti-book",     href: "/index.php?title=Category:Lore",          highlight: false },
      { label: "Artifacts",     icon: "ti-diamond",  href: "/index.php?title=Category:Artifacts",     highlight: false },
    ];

    var tablerLink = document.createElement("link");
    tablerLink.rel = "stylesheet";
    tablerLink.href = "https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css";
    document.head.appendChild(tablerLink);

    var wrap = document.createElement("div");
    sp(wrap, "max-width", "860px");
    sp(wrap, "margin", "0 auto");
    sp(wrap, "padding", "2.5rem 2rem 4rem");
    sp(wrap, "font-family", "'Inter', sans-serif");
    sp(wrap, "box-sizing", "border-box");

    var hero = document.createElement("div");
    sp(hero, "background", "#f0ebe0");
    sp(hero, "border", "1px solid #e2d9c8");
    sp(hero, "border-radius", "6px");
    sp(hero, "padding", "2rem 2.5rem");
    sp(hero, "margin-bottom", "2.5rem");
    sp(hero, "display", "flex");
    sp(hero, "align-items", "center");
    sp(hero, "justify-content", "space-between");
    sp(hero, "box-sizing", "border-box");

    var heroText = document.createElement("div");

    var heroTitle = document.createElement("h1");
    heroTitle.textContent = "Erakh Codex";
    sp(heroTitle, "font-size", "1.5rem");
    sp(heroTitle, "font-weight", "600");
    sp(heroTitle, "color", "#2a2218");
    sp(heroTitle, "letter-spacing", "-0.01em");
    sp(heroTitle, "margin", "0 0 0.5rem 0");
    sp(heroTitle, "font-family", "'Inter', sans-serif");

    var heroDesc = document.createElement("p");
    heroDesc.textContent = "The official encyclopedia of the Mirtess Chronicle. Explore the world, its history, peoples, and events — all in one place.";
    sp(heroDesc, "font-size", "0.8125rem");
    sp(heroDesc, "color", "#6b5e4e");
    sp(heroDesc, "line-height", "1.7");
    sp(heroDesc, "margin", "0");
    sp(heroDesc, "max-width", "480px");
    sp(heroDesc, "font-family", "'Inter', sans-serif");

    heroText.appendChild(heroTitle);
    heroText.appendChild(heroDesc);

    var heroStat = document.createElement("div");
    sp(heroStat, "text-align", "center");
    sp(heroStat, "border-left", "1px solid #c8b99a");
    sp(heroStat, "padding-left", "2.5rem");
    sp(heroStat, "flex-shrink", "0");

    var statNum = document.createElement("div");
    statNum.textContent = "—";
    sp(statNum, "font-size", "2rem");
    sp(statNum, "font-weight", "600");
    sp(statNum, "color", "#c8a060");
    sp(statNum, "letter-spacing", "-0.02em");
    sp(statNum, "font-family", "'Inter', sans-serif");

    var statLbl = document.createElement("div");
    statLbl.textContent = "Documents";
    sp(statLbl, "font-size", "0.6875rem");
    sp(statLbl, "color", "#9c8e80");
    sp(statLbl, "text-transform", "uppercase");
    sp(statLbl, "letter-spacing", "0.08em");
    sp(statLbl, "margin-top", "0.25rem");
    sp(statLbl, "font-family", "'Inter', sans-serif");

    heroStat.appendChild(statNum);
    heroStat.appendChild(statLbl);
    hero.appendChild(heroText);
    hero.appendChild(heroStat);
    wrap.appendChild(hero);

    function makeSectionTitle(text) {
      var t = document.createElement("div");
      t.textContent = text;
      sp(t, "font-size", "0.6875rem");
      sp(t, "font-weight", "500");
      sp(t, "color", "#9c8e80");
      sp(t, "text-transform", "uppercase");
      sp(t, "letter-spacing", "0.1em");
      sp(t, "margin-bottom", "1rem");
      sp(t, "padding-bottom", "0.4rem");
      sp(t, "border-bottom", "1px solid #e2d9c8");
      sp(t, "font-family", "'Inter', sans-serif");
      return t;
    }

    wrap.appendChild(makeSectionTitle("Browse by Category"));

    var catGrid = document.createElement("div");
    sp(catGrid, "display", "grid");
    sp(catGrid, "grid-template-columns", "repeat(3, 1fr)");
    sp(catGrid, "gap", "8px");
    sp(catGrid, "margin-bottom", "2.5rem");

    var countEls = {};

    CATS.forEach(function(cat) {
      var card = document.createElement("a");
      card.href = cat.href;
      sp(card, "border", cat.highlight ? "1.5px solid #c8a060" : "1px solid #e2d9c8");
      sp(card, "border-radius", "6px");
      sp(card, "background", cat.highlight ? "#f5ede0" : "#ffffff");
      sp(card, "padding", "1rem 0.5rem");
      sp(card, "text-align", "center");
      sp(card, "text-decoration", "none");
      sp(card, "display", "block");
      sp(card, "box-sizing", "border-box");
      if (cat.highlight) {
        card.addEventListener("mouseover", function() { this.style.setProperty("background", "#ede0cc", "important"); this.style.setProperty("border-color", "#a07840", "important"); });
        card.addEventListener("mouseout",  function() { this.style.setProperty("background", "#f5ede0", "important"); this.style.setProperty("border-color", "#c8a060", "important"); });
      } else {
        card.addEventListener("mouseover", function() { this.style.setProperty("background", "#f0ebe0", "important"); this.style.setProperty("border-color", "#c8b99a", "important"); });
        card.addEventListener("mouseout",  function() { this.style.setProperty("background", "#ffffff", "important"); this.style.setProperty("border-color", "#e2d9c8", "important"); });
      }

      var icon = document.createElement("i");
      icon.className = "ti " + cat.icon;
      sp(icon, "font-size", "1.25rem");
      sp(icon, "color", cat.highlight ? "#a07840" : "#c8a060");
      sp(icon, "display", "block");
      sp(icon, "margin-bottom", "0.4rem");

      var name = document.createElement("div");
      name.textContent = cat.label;
      sp(name, "font-size", "0.75rem");
      sp(name, "font-weight", "500");
      sp(name, "color", cat.highlight ? "#a07840" : "#2a2218");
      sp(name, "font-family", "'Inter', sans-serif");

      var count = document.createElement("div");
      count.textContent = "—";
      sp(count, "font-size", "0.65rem");
      sp(count, "color", "#9c8e80");
      sp(count, "margin-top", "0.2rem");
      sp(count, "font-family", "'Inter', sans-serif");
      countEls[cat.label] = count;

      card.appendChild(icon);
      card.appendChild(name);
      card.appendChild(count);
      catGrid.appendChild(card);
    });

    wrap.appendChild(catGrid);

    var catTitles = CATS.map(function(c) { return "Category:" + c.label; }).join("|");
    fetch("/api.php?action=query&prop=categoryinfo&titles=" + encodeURIComponent(catTitles) + "&format=json")
      .then(function(r) { return r.json(); })
      .then(function(data) {
        var pages = data.query && data.query.pages ? data.query.pages : {};
        var totalDocs = 0;
        Object.keys(pages).forEach(function(id) {
          var page = pages[id];
          var catName = (page.title || "").replace("Category:", "");
          var cnt = (page.categoryinfo && page.categoryinfo.pages) ? page.categoryinfo.pages : 0;
          if (countEls[catName]) {
            countEls[catName].textContent = cnt + (cnt === 1 ? " article" : " articles");
          }
          totalDocs += cnt;
        });
        statNum.textContent = totalDocs;
      })
      .catch(function() {});

    var twoCol = document.createElement("div");
    sp(twoCol, "display", "grid");
    sp(twoCol, "grid-template-columns", "1fr 1fr");
    sp(twoCol, "gap", "1.5rem");

    var welcomeWrap = document.createElement("div");
    welcomeWrap.appendChild(makeSectionTitle("New to Erakh Codex?"));

    var welcomeBox = document.createElement("div");
    sp(welcomeBox, "background", "#ffffff");
    sp(welcomeBox, "border", "1px solid #e2d9c8");
    sp(welcomeBox, "border-radius", "6px");
    sp(welcomeBox, "padding", "1.25rem 1.5rem");
    sp(welcomeBox, "display", "flex");
    sp(welcomeBox, "align-items", "flex-start");
    sp(welcomeBox, "gap", "0.875rem");
    sp(welcomeBox, "box-sizing", "border-box");

    var wIcon = document.createElement("i");
    wIcon.className = "ti ti-info-circle";
    sp(wIcon, "font-size", "1.125rem");
    sp(wIcon, "color", "#c8a060");
    sp(wIcon, "flex-shrink", "0");
    sp(wIcon, "margin-top", "0.1rem");

    var wText = document.createElement("div");

    var wTitle = document.createElement("div");
    wTitle.textContent = "Welcome to Erakh Codex";
    sp(wTitle, "font-size", "0.875rem");
    sp(wTitle, "font-weight", "500");
    sp(wTitle, "color", "#2a2218");
    sp(wTitle, "margin-bottom", "0.3rem");
    sp(wTitle, "font-family", "'Inter', sans-serif");

    var wDesc = document.createElement("div");
    sp(wDesc, "font-size", "0.775rem");
    sp(wDesc, "color", "#6b5e4e");
    sp(wDesc, "line-height", "1.65");
    sp(wDesc, "font-family", "'Inter', sans-serif");
    wDesc.innerHTML = "The official encyclopedia of the Mirtess Chronicle, maintained by the author. Before contributing, please read the <a href='/index.php?title=Erakh_Codex:Policies' style='color:#c8a060;text-decoration:none;font-weight:500;'>Policies & Guidelines</a>.";

    wText.appendChild(wTitle);
    wText.appendChild(wDesc);
    welcomeBox.appendChild(wIcon);
    welcomeBox.appendChild(wText);
    welcomeWrap.appendChild(welcomeBox);
    twoCol.appendChild(welcomeWrap);

    var recentWrap = document.createElement("div");
    recentWrap.appendChild(makeSectionTitle("Recent Changes"));

    var recentBox = document.createElement("div");
    sp(recentBox, "background", "#ffffff");
    sp(recentBox, "border", "1px solid #e2d9c8");
    sp(recentBox, "border-radius", "6px");
    sp(recentBox, "overflow", "hidden");

    var loadingMsg = document.createElement("div");
    loadingMsg.textContent = "Loading...";
    sp(loadingMsg, "padding", "1rem");
    sp(loadingMsg, "font-size", "0.78rem");
    sp(loadingMsg, "color", "#9c8e80");
    sp(loadingMsg, "font-family", "'Inter', sans-serif");
    recentBox.appendChild(loadingMsg);

    fetch("/api.php?action=query&list=recentchanges&rcnamespace=0&rclimit=10&rcprop=title|timestamp&rctype=edit|new&format=json")
      .then(function(r) { return r.json(); })
      .then(function(data) {
        recentBox.innerHTML = "";
        var changes = data.query && data.query.recentchanges ? data.query.recentchanges : [];

        var seen = {};
        var unique = [];
        changes.forEach(function(c) {
          if (!seen[c.title]) { seen[c.title] = true; unique.push(c); }
        });

        if (unique.length === 0) {
          var empty = document.createElement("div");
          empty.textContent = "No documents yet.";
          sp(empty, "padding", "1.5rem 1rem");
          sp(empty, "font-size", "0.78rem");
          sp(empty, "color", "#9c8e80");
          sp(empty, "text-align", "center");
          sp(empty, "font-family", "'Inter', sans-serif");
          recentBox.appendChild(empty);
          return;
        }

        unique.forEach(function(c, i) {
          var row = document.createElement("a");
          row.href = "/index.php?title=" + encodeURIComponent(c.title.replace(/ /g, "_"));
          sp(row, "display", "flex");
          sp(row, "align-items", "center");
          sp(row, "justify-content", "space-between");
          sp(row, "padding", "0.55rem 1rem");
          sp(row, "border-bottom", i < unique.length - 1 ? "1px solid #f0ebe0" : "none");
          sp(row, "text-decoration", "none");
          row.addEventListener("mouseover", function() { this.style.setProperty("background", "#f0ebe0", "important"); });
          row.addEventListener("mouseout",  function() { this.style.setProperty("background", "transparent", "important"); });

          var title = document.createElement("div");
          title.textContent = c.title;
          sp(title, "font-size", "0.78rem");
          sp(title, "color", "#2a2218");
          sp(title, "font-family", "'Inter', sans-serif");

          var date = document.createElement("div");
          var d = new Date(c.timestamp);
          date.textContent = d.getFullYear() + "." + String(d.getMonth() + 1).padStart(2, "0") + "." + String(d.getDate()).padStart(2, "0");
          sp(date, "font-size", "0.7rem");
          sp(date, "color", "#9c8e80");
          sp(date, "flex-shrink", "0");
          sp(date, "margin-left", "1rem");
          sp(date, "font-family", "'Inter', sans-serif");

          row.appendChild(title);
          row.appendChild(date);
          recentBox.appendChild(row);
        });
      })
      .catch(function() {
        recentBox.innerHTML = "";
        var err = document.createElement("div");
        err.textContent = "Could not load recent changes.";
        sp(err, "padding", "1rem");
        sp(err, "font-size", "0.78rem");
        sp(err, "color", "#9c8e80");
        sp(err, "font-family", "'Inter', sans-serif");
        recentBox.appendChild(err);
      });

    recentWrap.appendChild(recentBox);
    twoCol.appendChild(recentWrap);
    wrap.appendChild(twoCol);

    target.appendChild(wrap);
  }

  // ─── 실행 ───
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", function() {
      buildLayout();
      buildCreatePage();
      buildMainPage();
    });
  } else {
    buildLayout();
    buildCreatePage();
    buildMainPage();
  }

})();