DESKTOP-T61HUSC\user il y a 1 mois
commit
3a1ca83da8
100 fichiers modifiés avec 52530 ajouts et 0 suppressions
  1. 33 0
      .gitignore
  2. 17764 0
      .pnp.cjs
  3. 2126 0
      .pnp.loader.mjs
  4. 3 0
      .vscode/settings.json
  5. 75 0
      README.md
  6. 71 0
      app/app.vue
  7. 7529 0
      app/assets/scss/admin.scss
  8. 0 0
      app/assets/scss/media.scss
  9. 142 0
      app/assets/scss/style.scss
  10. 214 0
      app/assets/scss/suneditor-content.scss
  11. 40 0
      app/components/ColorPicker.vue
  12. 22 0
      app/components/ImageTabSwitch.vue
  13. 419 0
      app/components/Popup.vue
  14. 294 0
      app/components/SwiperBanner.vue
  15. 496 0
      app/components/SwiperBanner2.vue
  16. 272 0
      app/components/SwiperBanner3.vue
  17. 498 0
      app/components/SwiperBanner4.vue
  18. 169 0
      app/components/admin/AccountLockedModal.vue
  19. 78 0
      app/components/admin/AdminAlertModal.vue
  20. 25 0
      app/components/admin/AdminLoadingOverlay.vue
  21. 479 0
      app/components/admin/AdminModal.vue
  22. 197 0
      app/components/admin/DatePicker.vue
  23. 337 0
      app/components/admin/PasswordChangeRecommendModal.vue
  24. 336 0
      app/components/admin/PasswordModal.vue
  25. 203 0
      app/components/admin/SunEditor.vue
  26. 179 0
      app/components/block/mainSwiper.vue
  27. 127 0
      app/components/block/mainSwiperLincoln.vue
  28. 96 0
      app/components/breadCrumbs.vue
  29. 185 0
      app/components/event.vue
  30. 420 0
      app/components/footer.vue
  31. 23 0
      app/components/header.vue
  32. 3 0
      app/components/sitemap.vue
  33. 173 0
      app/composables/useApi.js
  34. 6 0
      app/composables/useCompany.js
  35. 47 0
      app/composables/useImage.js
  36. 38 0
      app/composables/useLoading.js
  37. 120 0
      app/composables/useSalesData.js
  38. 19 0
      app/composables/useTimeSwitch.js
  39. 422 0
      app/layouts/admin.vue
  40. 14 0
      app/layouts/default.vue
  41. 38 0
      app/middleware/auth.js
  42. 459 0
      app/pages/ford/about/company.vue
  43. 136 0
      app/pages/ford/about/ourPurpose.vue
  44. 120 0
      app/pages/ford/index.vue
  45. 589 0
      app/pages/ford/network/index.vue
  46. 928 0
      app/pages/ford/network/location.vue
  47. 194 0
      app/pages/ford/owner/accident.vue
  48. 1110 0
      app/pages/ford/owner/consumableParts.vue
  49. 89 0
      app/pages/ford/owner/contact.vue
  50. 391 0
      app/pages/ford/owner/event/[id].vue
  51. 10 0
      app/pages/ford/owner/event/index.vue
  52. 211 0
      app/pages/ford/owner/genuine.vue
  53. 202 0
      app/pages/ford/owner/index.vue
  54. 926 0
      app/pages/ford/owner/navigation.vue
  55. 101 0
      app/pages/ford/owner/recall.vue
  56. 325 0
      app/pages/ford/owner/tfService.vue
  57. 74 0
      app/pages/ford/owner/warranty.vue
  58. 73 0
      app/pages/ford/shopping/brochure.vue
  59. 313 0
      app/pages/ford/shopping/tire.vue
  60. 279 0
      app/pages/ford/vehicle/bronco.vue
  61. 315 0
      app/pages/ford/vehicle/expedition.vue
  62. 591 0
      app/pages/ford/vehicle/explorer.vue
  63. 375 0
      app/pages/ford/vehicle/explorer2.vue
  64. 383 0
      app/pages/ford/vehicle/mustang.vue
  65. 345 0
      app/pages/ford/vehicle/ranger.vue
  66. 20 0
      app/pages/index.vue
  67. 471 0
      app/pages/site-manager/admins/index.vue
  68. 487 0
      app/pages/site-manager/basic/popup/create.vue
  69. 558 0
      app/pages/site-manager/basic/popup/edit/[id].vue
  70. 319 0
      app/pages/site-manager/basic/popup/index.vue
  71. 324 0
      app/pages/site-manager/basic/site-info.vue
  72. 310 0
      app/pages/site-manager/board/event/create.vue
  73. 381 0
      app/pages/site-manager/board/event/edit/[id].vue
  74. 221 0
      app/pages/site-manager/board/event/index.vue
  75. 251 0
      app/pages/site-manager/board/ir/create.vue
  76. 307 0
      app/pages/site-manager/board/ir/edit/[id].vue
  77. 221 0
      app/pages/site-manager/board/ir/index.vue
  78. 261 0
      app/pages/site-manager/board/news/create.vue
  79. 317 0
      app/pages/site-manager/board/news/edit/[id].vue
  80. 221 0
      app/pages/site-manager/board/news/index.vue
  81. 276 0
      app/pages/site-manager/board/notice/create.vue
  82. 340 0
      app/pages/site-manager/board/notice/edit/[id].vue
  83. 225 0
      app/pages/site-manager/board/notice/index.vue
  84. 202 0
      app/pages/site-manager/branch/create.vue
  85. 238 0
      app/pages/site-manager/branch/edit/[id].vue
  86. 336 0
      app/pages/site-manager/branch/list.vue
  87. 400 0
      app/pages/site-manager/branch/manager/create.vue
  88. 362 0
      app/pages/site-manager/branch/manager/edit/[id].vue
  89. 257 0
      app/pages/site-manager/branch/manager/index.vue
  90. 83 0
      app/pages/site-manager/dashboard.vue
  91. 216 0
      app/pages/site-manager/index.vue
  92. 293 0
      app/pages/site-manager/service-center/create.vue
  93. 327 0
      app/pages/site-manager/service-center/edit/[id].vue
  94. 337 0
      app/pages/site-manager/service-center/list.vue
  95. 234 0
      app/pages/site-manager/service/brochure.vue
  96. 317 0
      app/pages/site-manager/showroom/create.vue
  97. 337 0
      app/pages/site-manager/showroom/edit/[id].vue
  98. 337 0
      app/pages/site-manager/showroom/list.vue
  99. 219 0
      app/pages/site-manager/staff/advisor/create.vue
  100. 254 0
      app/pages/site-manager/staff/advisor/edit/[id].vue

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+# Nuxt dev/build outputs
+.output
+.data
+.nuxt
+.nitro
+.cache
+dist
+
+# Node dependencies
+node_modules
+
+# Logs
+logs
+*.log
+
+# Misc
+.DS_Store
+.fleet
+.idea
+
+# Local env files
+.env
+.env.*
+!.env.example
+
+# css
+*.css
+*.css.map
+AGENTS.MD
+CLAUDE.MD
+rules/CLEAN-CODE.MD
+rules/GUIDELINE.MD
+rules/STEP-BY-STEP.MD

Fichier diff supprimé car celui-ci est trop grand
+ 17764 - 0
.pnp.cjs


+ 2126 - 0
.pnp.loader.mjs

@@ -0,0 +1,2126 @@
+/* eslint-disable */
+// @ts-nocheck
+
+import fs from 'fs';
+import { URL as URL$1, fileURLToPath, pathToFileURL } from 'url';
+import path from 'path';
+import { createHash } from 'crypto';
+import { EOL } from 'os';
+import esmModule, { createRequire, isBuiltin } from 'module';
+import assert from 'assert';
+
+const SAFE_TIME = 456789e3;
+
+const PortablePath = {
+  root: `/`,
+  dot: `.`,
+  parent: `..`
+};
+const npath = Object.create(path);
+const ppath = Object.create(path.posix);
+npath.cwd = () => process.cwd();
+ppath.cwd = process.platform === `win32` ? () => toPortablePath(process.cwd()) : process.cwd;
+if (process.platform === `win32`) {
+  ppath.resolve = (...segments) => {
+    if (segments.length > 0 && ppath.isAbsolute(segments[0])) {
+      return path.posix.resolve(...segments);
+    } else {
+      return path.posix.resolve(ppath.cwd(), ...segments);
+    }
+  };
+}
+const contains = function(pathUtils, from, to) {
+  from = pathUtils.normalize(from);
+  to = pathUtils.normalize(to);
+  if (from === to)
+    return `.`;
+  if (!from.endsWith(pathUtils.sep))
+    from = from + pathUtils.sep;
+  if (to.startsWith(from)) {
+    return to.slice(from.length);
+  } else {
+    return null;
+  }
+};
+npath.contains = (from, to) => contains(npath, from, to);
+ppath.contains = (from, to) => contains(ppath, from, to);
+const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/;
+const UNC_WINDOWS_PATH_REGEXP = /^\/\/(\.\/)?(.*)$/;
+const PORTABLE_PATH_REGEXP = /^\/([a-zA-Z]:.*)$/;
+const UNC_PORTABLE_PATH_REGEXP = /^\/unc\/(\.dot\/)?(.*)$/;
+function fromPortablePathWin32(p) {
+  let portablePathMatch, uncPortablePathMatch;
+  if (portablePathMatch = p.match(PORTABLE_PATH_REGEXP))
+    p = portablePathMatch[1];
+  else if (uncPortablePathMatch = p.match(UNC_PORTABLE_PATH_REGEXP))
+    p = `\\\\${uncPortablePathMatch[1] ? `.\\` : ``}${uncPortablePathMatch[2]}`;
+  else
+    return p;
+  return p.replace(/\//g, `\\`);
+}
+function toPortablePathWin32(p) {
+  p = p.replace(/\\/g, `/`);
+  let windowsPathMatch, uncWindowsPathMatch;
+  if (windowsPathMatch = p.match(WINDOWS_PATH_REGEXP))
+    p = `/${windowsPathMatch[1]}`;
+  else if (uncWindowsPathMatch = p.match(UNC_WINDOWS_PATH_REGEXP))
+    p = `/unc/${uncWindowsPathMatch[1] ? `.dot/` : ``}${uncWindowsPathMatch[2]}`;
+  return p;
+}
+const toPortablePath = process.platform === `win32` ? toPortablePathWin32 : (p) => p;
+const fromPortablePath = process.platform === `win32` ? fromPortablePathWin32 : (p) => p;
+npath.fromPortablePath = fromPortablePath;
+npath.toPortablePath = toPortablePath;
+function convertPath(targetPathUtils, sourcePath) {
+  return targetPathUtils === npath ? fromPortablePath(sourcePath) : toPortablePath(sourcePath);
+}
+
+const defaultTime = new Date(SAFE_TIME * 1e3);
+const defaultTimeMs = defaultTime.getTime();
+async function copyPromise(destinationFs, destination, sourceFs, source, opts) {
+  const normalizedDestination = destinationFs.pathUtils.normalize(destination);
+  const normalizedSource = sourceFs.pathUtils.normalize(source);
+  const prelayout = [];
+  const postlayout = [];
+  const { atime, mtime } = opts.stableTime ? { atime: defaultTime, mtime: defaultTime } : await sourceFs.lstatPromise(normalizedSource);
+  await destinationFs.mkdirpPromise(destinationFs.pathUtils.dirname(destination), { utimes: [atime, mtime] });
+  await copyImpl(prelayout, postlayout, destinationFs, normalizedDestination, sourceFs, normalizedSource, { ...opts, didParentExist: true });
+  for (const operation of prelayout)
+    await operation();
+  await Promise.all(postlayout.map((operation) => {
+    return operation();
+  }));
+}
+async function copyImpl(prelayout, postlayout, destinationFs, destination, sourceFs, source, opts) {
+  const destinationStat = opts.didParentExist ? await maybeLStat(destinationFs, destination) : null;
+  const sourceStat = await sourceFs.lstatPromise(source);
+  const { atime, mtime } = opts.stableTime ? { atime: defaultTime, mtime: defaultTime } : sourceStat;
+  let updated;
+  switch (true) {
+    case sourceStat.isDirectory():
+      {
+        updated = await copyFolder(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts);
+      }
+      break;
+    case sourceStat.isFile():
+      {
+        updated = await copyFile(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts);
+      }
+      break;
+    case sourceStat.isSymbolicLink():
+      {
+        updated = await copySymlink(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts);
+      }
+      break;
+    default: {
+      throw new Error(`Unsupported file type (${sourceStat.mode})`);
+    }
+  }
+  if (opts.linkStrategy?.type !== `HardlinkFromIndex` || !sourceStat.isFile()) {
+    if (updated || destinationStat?.mtime?.getTime() !== mtime.getTime() || destinationStat?.atime?.getTime() !== atime.getTime()) {
+      postlayout.push(() => destinationFs.lutimesPromise(destination, atime, mtime));
+      updated = true;
+    }
+    if (destinationStat === null || (destinationStat.mode & 511) !== (sourceStat.mode & 511)) {
+      postlayout.push(() => destinationFs.chmodPromise(destination, sourceStat.mode & 511));
+      updated = true;
+    }
+  }
+  return updated;
+}
+async function maybeLStat(baseFs, p) {
+  try {
+    return await baseFs.lstatPromise(p);
+  } catch (e) {
+    return null;
+  }
+}
+async function copyFolder(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts) {
+  if (destinationStat !== null && !destinationStat.isDirectory()) {
+    if (opts.overwrite) {
+      prelayout.push(async () => destinationFs.removePromise(destination));
+      destinationStat = null;
+    } else {
+      return false;
+    }
+  }
+  let updated = false;
+  if (destinationStat === null) {
+    prelayout.push(async () => {
+      try {
+        await destinationFs.mkdirPromise(destination, { mode: sourceStat.mode });
+      } catch (err) {
+        if (err.code !== `EEXIST`) {
+          throw err;
+        }
+      }
+    });
+    updated = true;
+  }
+  const entries = await sourceFs.readdirPromise(source);
+  const nextOpts = opts.didParentExist && !destinationStat ? { ...opts, didParentExist: false } : opts;
+  if (opts.stableSort) {
+    for (const entry of entries.sort()) {
+      if (await copyImpl(prelayout, postlayout, destinationFs, destinationFs.pathUtils.join(destination, entry), sourceFs, sourceFs.pathUtils.join(source, entry), nextOpts)) {
+        updated = true;
+      }
+    }
+  } else {
+    const entriesUpdateStatus = await Promise.all(entries.map(async (entry) => {
+      await copyImpl(prelayout, postlayout, destinationFs, destinationFs.pathUtils.join(destination, entry), sourceFs, sourceFs.pathUtils.join(source, entry), nextOpts);
+    }));
+    if (entriesUpdateStatus.some((status) => status)) {
+      updated = true;
+    }
+  }
+  return updated;
+}
+async function copyFileViaIndex(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts, linkStrategy) {
+  const sourceHash = await sourceFs.checksumFilePromise(source, { algorithm: `sha1` });
+  const defaultMode = 420;
+  const sourceMode = sourceStat.mode & 511;
+  const indexFileName = `${sourceHash}${sourceMode !== defaultMode ? sourceMode.toString(8) : ``}`;
+  const indexPath = destinationFs.pathUtils.join(linkStrategy.indexPath, sourceHash.slice(0, 2), `${indexFileName}.dat`);
+  let AtomicBehavior;
+  ((AtomicBehavior2) => {
+    AtomicBehavior2[AtomicBehavior2["Lock"] = 0] = "Lock";
+    AtomicBehavior2[AtomicBehavior2["Rename"] = 1] = "Rename";
+  })(AtomicBehavior || (AtomicBehavior = {}));
+  let atomicBehavior = 1 /* Rename */;
+  let indexStat = await maybeLStat(destinationFs, indexPath);
+  if (destinationStat) {
+    const isDestinationHardlinkedFromIndex = indexStat && destinationStat.dev === indexStat.dev && destinationStat.ino === indexStat.ino;
+    const isIndexModified = indexStat?.mtimeMs !== defaultTimeMs;
+    if (isDestinationHardlinkedFromIndex) {
+      if (isIndexModified && linkStrategy.autoRepair) {
+        atomicBehavior = 0 /* Lock */;
+        indexStat = null;
+      }
+    }
+    if (!isDestinationHardlinkedFromIndex) {
+      if (opts.overwrite) {
+        prelayout.push(async () => destinationFs.removePromise(destination));
+        destinationStat = null;
+      } else {
+        return false;
+      }
+    }
+  }
+  const tempPath = !indexStat && atomicBehavior === 1 /* Rename */ ? `${indexPath}.${Math.floor(Math.random() * 4294967296).toString(16).padStart(8, `0`)}` : null;
+  let tempPathCleaned = false;
+  prelayout.push(async () => {
+    if (!indexStat) {
+      if (atomicBehavior === 0 /* Lock */) {
+        await destinationFs.lockPromise(indexPath, async () => {
+          const content = await sourceFs.readFilePromise(source);
+          await destinationFs.writeFilePromise(indexPath, content);
+        });
+      }
+      if (atomicBehavior === 1 /* Rename */ && tempPath) {
+        const content = await sourceFs.readFilePromise(source);
+        await destinationFs.writeFilePromise(tempPath, content);
+        try {
+          await destinationFs.linkPromise(tempPath, indexPath);
+        } catch (err) {
+          if (err.code === `EEXIST`) {
+            tempPathCleaned = true;
+            await destinationFs.unlinkPromise(tempPath);
+          } else {
+            throw err;
+          }
+        }
+      }
+    }
+    if (!destinationStat) {
+      await destinationFs.linkPromise(indexPath, destination);
+    }
+  });
+  postlayout.push(async () => {
+    if (!indexStat) {
+      await destinationFs.lutimesPromise(indexPath, defaultTime, defaultTime);
+      if (sourceMode !== defaultMode) {
+        await destinationFs.chmodPromise(indexPath, sourceMode);
+      }
+    }
+    if (tempPath && !tempPathCleaned) {
+      await destinationFs.unlinkPromise(tempPath);
+    }
+  });
+  return false;
+}
+async function copyFileDirect(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts) {
+  if (destinationStat !== null) {
+    if (opts.overwrite) {
+      prelayout.push(async () => destinationFs.removePromise(destination));
+      destinationStat = null;
+    } else {
+      return false;
+    }
+  }
+  prelayout.push(async () => {
+    const content = await sourceFs.readFilePromise(source);
+    await destinationFs.writeFilePromise(destination, content);
+  });
+  return true;
+}
+async function copyFile(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts) {
+  if (opts.linkStrategy?.type === `HardlinkFromIndex`) {
+    return copyFileViaIndex(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts, opts.linkStrategy);
+  } else {
+    return copyFileDirect(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts);
+  }
+}
+async function copySymlink(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts) {
+  if (destinationStat !== null) {
+    if (opts.overwrite) {
+      prelayout.push(async () => destinationFs.removePromise(destination));
+      destinationStat = null;
+    } else {
+      return false;
+    }
+  }
+  prelayout.push(async () => {
+    await destinationFs.symlinkPromise(convertPath(destinationFs.pathUtils, await sourceFs.readlinkPromise(source)), destination);
+  });
+  return true;
+}
+
+class FakeFS {
+  pathUtils;
+  constructor(pathUtils) {
+    this.pathUtils = pathUtils;
+  }
+  async *genTraversePromise(init, { stableSort = false } = {}) {
+    const stack = [init];
+    while (stack.length > 0) {
+      const p = stack.shift();
+      const entry = await this.lstatPromise(p);
+      if (entry.isDirectory()) {
+        const entries = await this.readdirPromise(p);
+        if (stableSort) {
+          for (const entry2 of entries.sort()) {
+            stack.push(this.pathUtils.join(p, entry2));
+          }
+        } else {
+          throw new Error(`Not supported`);
+        }
+      } else {
+        yield p;
+      }
+    }
+  }
+  async checksumFilePromise(path, { algorithm = `sha512` } = {}) {
+    const fd = await this.openPromise(path, `r`);
+    try {
+      const CHUNK_SIZE = 65536;
+      const chunk = Buffer.allocUnsafeSlow(CHUNK_SIZE);
+      const hash = createHash(algorithm);
+      let bytesRead = 0;
+      while ((bytesRead = await this.readPromise(fd, chunk, 0, CHUNK_SIZE)) !== 0)
+        hash.update(bytesRead === CHUNK_SIZE ? chunk : chunk.slice(0, bytesRead));
+      return hash.digest(`hex`);
+    } finally {
+      await this.closePromise(fd);
+    }
+  }
+  async removePromise(p, { recursive = true, maxRetries = 5 } = {}) {
+    let stat;
+    try {
+      stat = await this.lstatPromise(p);
+    } catch (error) {
+      if (error.code === `ENOENT`) {
+        return;
+      } else {
+        throw error;
+      }
+    }
+    if (stat.isDirectory()) {
+      if (recursive) {
+        const entries = await this.readdirPromise(p);
+        await Promise.all(entries.map((entry) => {
+          return this.removePromise(this.pathUtils.resolve(p, entry));
+        }));
+      }
+      for (let t = 0; t <= maxRetries; t++) {
+        try {
+          await this.rmdirPromise(p);
+          break;
+        } catch (error) {
+          if (error.code !== `EBUSY` && error.code !== `ENOTEMPTY`) {
+            throw error;
+          } else if (t < maxRetries) {
+            await new Promise((resolve) => setTimeout(resolve, t * 100));
+          }
+        }
+      }
+    } else {
+      await this.unlinkPromise(p);
+    }
+  }
+  removeSync(p, { recursive = true } = {}) {
+    let stat;
+    try {
+      stat = this.lstatSync(p);
+    } catch (error) {
+      if (error.code === `ENOENT`) {
+        return;
+      } else {
+        throw error;
+      }
+    }
+    if (stat.isDirectory()) {
+      if (recursive)
+        for (const entry of this.readdirSync(p))
+          this.removeSync(this.pathUtils.resolve(p, entry));
+      this.rmdirSync(p);
+    } else {
+      this.unlinkSync(p);
+    }
+  }
+  async mkdirpPromise(p, { chmod, utimes } = {}) {
+    p = this.resolve(p);
+    if (p === this.pathUtils.dirname(p))
+      return void 0;
+    const parts = p.split(this.pathUtils.sep);
+    let createdDirectory;
+    for (let u = 2; u <= parts.length; ++u) {
+      const subPath = parts.slice(0, u).join(this.pathUtils.sep);
+      if (!this.existsSync(subPath)) {
+        try {
+          await this.mkdirPromise(subPath);
+        } catch (error) {
+          if (error.code === `EEXIST`) {
+            continue;
+          } else {
+            throw error;
+          }
+        }
+        createdDirectory ??= subPath;
+        if (chmod != null)
+          await this.chmodPromise(subPath, chmod);
+        if (utimes != null) {
+          await this.utimesPromise(subPath, utimes[0], utimes[1]);
+        } else {
+          const parentStat = await this.statPromise(this.pathUtils.dirname(subPath));
+          await this.utimesPromise(subPath, parentStat.atime, parentStat.mtime);
+        }
+      }
+    }
+    return createdDirectory;
+  }
+  mkdirpSync(p, { chmod, utimes } = {}) {
+    p = this.resolve(p);
+    if (p === this.pathUtils.dirname(p))
+      return void 0;
+    const parts = p.split(this.pathUtils.sep);
+    let createdDirectory;
+    for (let u = 2; u <= parts.length; ++u) {
+      const subPath = parts.slice(0, u).join(this.pathUtils.sep);
+      if (!this.existsSync(subPath)) {
+        try {
+          this.mkdirSync(subPath);
+        } catch (error) {
+          if (error.code === `EEXIST`) {
+            continue;
+          } else {
+            throw error;
+          }
+        }
+        createdDirectory ??= subPath;
+        if (chmod != null)
+          this.chmodSync(subPath, chmod);
+        if (utimes != null) {
+          this.utimesSync(subPath, utimes[0], utimes[1]);
+        } else {
+          const parentStat = this.statSync(this.pathUtils.dirname(subPath));
+          this.utimesSync(subPath, parentStat.atime, parentStat.mtime);
+        }
+      }
+    }
+    return createdDirectory;
+  }
+  async copyPromise(destination, source, { baseFs = this, overwrite = true, stableSort = false, stableTime = false, linkStrategy = null } = {}) {
+    return await copyPromise(this, destination, baseFs, source, { overwrite, stableSort, stableTime, linkStrategy });
+  }
+  copySync(destination, source, { baseFs = this, overwrite = true } = {}) {
+    const stat = baseFs.lstatSync(source);
+    const exists = this.existsSync(destination);
+    if (stat.isDirectory()) {
+      this.mkdirpSync(destination);
+      const directoryListing = baseFs.readdirSync(source);
+      for (const entry of directoryListing) {
+        this.copySync(this.pathUtils.join(destination, entry), baseFs.pathUtils.join(source, entry), { baseFs, overwrite });
+      }
+    } else if (stat.isFile()) {
+      if (!exists || overwrite) {
+        if (exists)
+          this.removeSync(destination);
+        const content = baseFs.readFileSync(source);
+        this.writeFileSync(destination, content);
+      }
+    } else if (stat.isSymbolicLink()) {
+      if (!exists || overwrite) {
+        if (exists)
+          this.removeSync(destination);
+        const target = baseFs.readlinkSync(source);
+        this.symlinkSync(convertPath(this.pathUtils, target), destination);
+      }
+    } else {
+      throw new Error(`Unsupported file type (file: ${source}, mode: 0o${stat.mode.toString(8).padStart(6, `0`)})`);
+    }
+    const mode = stat.mode & 511;
+    this.chmodSync(destination, mode);
+  }
+  async changeFilePromise(p, content, opts = {}) {
+    if (Buffer.isBuffer(content)) {
+      return this.changeFileBufferPromise(p, content, opts);
+    } else {
+      return this.changeFileTextPromise(p, content, opts);
+    }
+  }
+  async changeFileBufferPromise(p, content, { mode } = {}) {
+    let current = Buffer.alloc(0);
+    try {
+      current = await this.readFilePromise(p);
+    } catch (error) {
+    }
+    if (Buffer.compare(current, content) === 0)
+      return;
+    await this.writeFilePromise(p, content, { mode });
+  }
+  async changeFileTextPromise(p, content, { automaticNewlines, mode } = {}) {
+    let current = ``;
+    try {
+      current = await this.readFilePromise(p, `utf8`);
+    } catch (error) {
+    }
+    const normalizedContent = automaticNewlines ? normalizeLineEndings(current, content) : content;
+    if (current === normalizedContent)
+      return;
+    await this.writeFilePromise(p, normalizedContent, { mode });
+  }
+  changeFileSync(p, content, opts = {}) {
+    if (Buffer.isBuffer(content)) {
+      return this.changeFileBufferSync(p, content, opts);
+    } else {
+      return this.changeFileTextSync(p, content, opts);
+    }
+  }
+  changeFileBufferSync(p, content, { mode } = {}) {
+    let current = Buffer.alloc(0);
+    try {
+      current = this.readFileSync(p);
+    } catch (error) {
+    }
+    if (Buffer.compare(current, content) === 0)
+      return;
+    this.writeFileSync(p, content, { mode });
+  }
+  changeFileTextSync(p, content, { automaticNewlines = false, mode } = {}) {
+    let current = ``;
+    try {
+      current = this.readFileSync(p, `utf8`);
+    } catch (error) {
+    }
+    const normalizedContent = automaticNewlines ? normalizeLineEndings(current, content) : content;
+    if (current === normalizedContent)
+      return;
+    this.writeFileSync(p, normalizedContent, { mode });
+  }
+  async movePromise(fromP, toP) {
+    try {
+      await this.renamePromise(fromP, toP);
+    } catch (error) {
+      if (error.code === `EXDEV`) {
+        await this.copyPromise(toP, fromP);
+        await this.removePromise(fromP);
+      } else {
+        throw error;
+      }
+    }
+  }
+  moveSync(fromP, toP) {
+    try {
+      this.renameSync(fromP, toP);
+    } catch (error) {
+      if (error.code === `EXDEV`) {
+        this.copySync(toP, fromP);
+        this.removeSync(fromP);
+      } else {
+        throw error;
+      }
+    }
+  }
+  async lockPromise(affectedPath, callback) {
+    const lockPath = `${affectedPath}.flock`;
+    const interval = 1e3 / 60;
+    const startTime = Date.now();
+    let fd = null;
+    const isAlive = async () => {
+      let pid;
+      try {
+        [pid] = await this.readJsonPromise(lockPath);
+      } catch (error) {
+        return Date.now() - startTime < 500;
+      }
+      try {
+        process.kill(pid, 0);
+        return true;
+      } catch (error) {
+        return false;
+      }
+    };
+    while (fd === null) {
+      try {
+        fd = await this.openPromise(lockPath, `wx`);
+      } catch (error) {
+        if (error.code === `EEXIST`) {
+          if (!await isAlive()) {
+            try {
+              await this.unlinkPromise(lockPath);
+              continue;
+            } catch (error2) {
+            }
+          }
+          if (Date.now() - startTime < 60 * 1e3) {
+            await new Promise((resolve) => setTimeout(resolve, interval));
+          } else {
+            throw new Error(`Couldn't acquire a lock in a reasonable time (via ${lockPath})`);
+          }
+        } else {
+          throw error;
+        }
+      }
+    }
+    await this.writePromise(fd, JSON.stringify([process.pid]));
+    try {
+      return await callback();
+    } finally {
+      try {
+        await this.closePromise(fd);
+        await this.unlinkPromise(lockPath);
+      } catch (error) {
+      }
+    }
+  }
+  async readJsonPromise(p) {
+    const content = await this.readFilePromise(p, `utf8`);
+    try {
+      return JSON.parse(content);
+    } catch (error) {
+      error.message += ` (in ${p})`;
+      throw error;
+    }
+  }
+  readJsonSync(p) {
+    const content = this.readFileSync(p, `utf8`);
+    try {
+      return JSON.parse(content);
+    } catch (error) {
+      error.message += ` (in ${p})`;
+      throw error;
+    }
+  }
+  async writeJsonPromise(p, data, { compact = false } = {}) {
+    const space = compact ? 0 : 2;
+    return await this.writeFilePromise(p, `${JSON.stringify(data, null, space)}
+`);
+  }
+  writeJsonSync(p, data, { compact = false } = {}) {
+    const space = compact ? 0 : 2;
+    return this.writeFileSync(p, `${JSON.stringify(data, null, space)}
+`);
+  }
+  async preserveTimePromise(p, cb) {
+    const stat = await this.lstatPromise(p);
+    const result = await cb();
+    if (typeof result !== `undefined`)
+      p = result;
+    await this.lutimesPromise(p, stat.atime, stat.mtime);
+  }
+  async preserveTimeSync(p, cb) {
+    const stat = this.lstatSync(p);
+    const result = cb();
+    if (typeof result !== `undefined`)
+      p = result;
+    this.lutimesSync(p, stat.atime, stat.mtime);
+  }
+}
+class BasePortableFakeFS extends FakeFS {
+  constructor() {
+    super(ppath);
+  }
+}
+function getEndOfLine(content) {
+  const matches = content.match(/\r?\n/g);
+  if (matches === null)
+    return EOL;
+  const crlf = matches.filter((nl) => nl === `\r
+`).length;
+  const lf = matches.length - crlf;
+  return crlf > lf ? `\r
+` : `
+`;
+}
+function normalizeLineEndings(originalContent, newContent) {
+  return newContent.replace(/\r?\n/g, getEndOfLine(originalContent));
+}
+
+class ProxiedFS extends FakeFS {
+  getExtractHint(hints) {
+    return this.baseFs.getExtractHint(hints);
+  }
+  resolve(path) {
+    return this.mapFromBase(this.baseFs.resolve(this.mapToBase(path)));
+  }
+  getRealPath() {
+    return this.mapFromBase(this.baseFs.getRealPath());
+  }
+  async openPromise(p, flags, mode) {
+    return this.baseFs.openPromise(this.mapToBase(p), flags, mode);
+  }
+  openSync(p, flags, mode) {
+    return this.baseFs.openSync(this.mapToBase(p), flags, mode);
+  }
+  async opendirPromise(p, opts) {
+    return Object.assign(await this.baseFs.opendirPromise(this.mapToBase(p), opts), { path: p });
+  }
+  opendirSync(p, opts) {
+    return Object.assign(this.baseFs.opendirSync(this.mapToBase(p), opts), { path: p });
+  }
+  async readPromise(fd, buffer, offset, length, position) {
+    return await this.baseFs.readPromise(fd, buffer, offset, length, position);
+  }
+  readSync(fd, buffer, offset, length, position) {
+    return this.baseFs.readSync(fd, buffer, offset, length, position);
+  }
+  async writePromise(fd, buffer, offset, length, position) {
+    if (typeof buffer === `string`) {
+      return await this.baseFs.writePromise(fd, buffer, offset);
+    } else {
+      return await this.baseFs.writePromise(fd, buffer, offset, length, position);
+    }
+  }
+  writeSync(fd, buffer, offset, length, position) {
+    if (typeof buffer === `string`) {
+      return this.baseFs.writeSync(fd, buffer, offset);
+    } else {
+      return this.baseFs.writeSync(fd, buffer, offset, length, position);
+    }
+  }
+  async closePromise(fd) {
+    return this.baseFs.closePromise(fd);
+  }
+  closeSync(fd) {
+    this.baseFs.closeSync(fd);
+  }
+  createReadStream(p, opts) {
+    return this.baseFs.createReadStream(p !== null ? this.mapToBase(p) : p, opts);
+  }
+  createWriteStream(p, opts) {
+    return this.baseFs.createWriteStream(p !== null ? this.mapToBase(p) : p, opts);
+  }
+  async realpathPromise(p) {
+    return this.mapFromBase(await this.baseFs.realpathPromise(this.mapToBase(p)));
+  }
+  realpathSync(p) {
+    return this.mapFromBase(this.baseFs.realpathSync(this.mapToBase(p)));
+  }
+  async existsPromise(p) {
+    return this.baseFs.existsPromise(this.mapToBase(p));
+  }
+  existsSync(p) {
+    return this.baseFs.existsSync(this.mapToBase(p));
+  }
+  accessSync(p, mode) {
+    return this.baseFs.accessSync(this.mapToBase(p), mode);
+  }
+  async accessPromise(p, mode) {
+    return this.baseFs.accessPromise(this.mapToBase(p), mode);
+  }
+  async statPromise(p, opts) {
+    return this.baseFs.statPromise(this.mapToBase(p), opts);
+  }
+  statSync(p, opts) {
+    return this.baseFs.statSync(this.mapToBase(p), opts);
+  }
+  async fstatPromise(fd, opts) {
+    return this.baseFs.fstatPromise(fd, opts);
+  }
+  fstatSync(fd, opts) {
+    return this.baseFs.fstatSync(fd, opts);
+  }
+  lstatPromise(p, opts) {
+    return this.baseFs.lstatPromise(this.mapToBase(p), opts);
+  }
+  lstatSync(p, opts) {
+    return this.baseFs.lstatSync(this.mapToBase(p), opts);
+  }
+  async fchmodPromise(fd, mask) {
+    return this.baseFs.fchmodPromise(fd, mask);
+  }
+  fchmodSync(fd, mask) {
+    return this.baseFs.fchmodSync(fd, mask);
+  }
+  async chmodPromise(p, mask) {
+    return this.baseFs.chmodPromise(this.mapToBase(p), mask);
+  }
+  chmodSync(p, mask) {
+    return this.baseFs.chmodSync(this.mapToBase(p), mask);
+  }
+  async fchownPromise(fd, uid, gid) {
+    return this.baseFs.fchownPromise(fd, uid, gid);
+  }
+  fchownSync(fd, uid, gid) {
+    return this.baseFs.fchownSync(fd, uid, gid);
+  }
+  async chownPromise(p, uid, gid) {
+    return this.baseFs.chownPromise(this.mapToBase(p), uid, gid);
+  }
+  chownSync(p, uid, gid) {
+    return this.baseFs.chownSync(this.mapToBase(p), uid, gid);
+  }
+  async renamePromise(oldP, newP) {
+    return this.baseFs.renamePromise(this.mapToBase(oldP), this.mapToBase(newP));
+  }
+  renameSync(oldP, newP) {
+    return this.baseFs.renameSync(this.mapToBase(oldP), this.mapToBase(newP));
+  }
+  async copyFilePromise(sourceP, destP, flags = 0) {
+    return this.baseFs.copyFilePromise(this.mapToBase(sourceP), this.mapToBase(destP), flags);
+  }
+  copyFileSync(sourceP, destP, flags = 0) {
+    return this.baseFs.copyFileSync(this.mapToBase(sourceP), this.mapToBase(destP), flags);
+  }
+  async appendFilePromise(p, content, opts) {
+    return this.baseFs.appendFilePromise(this.fsMapToBase(p), content, opts);
+  }
+  appendFileSync(p, content, opts) {
+    return this.baseFs.appendFileSync(this.fsMapToBase(p), content, opts);
+  }
+  async writeFilePromise(p, content, opts) {
+    return this.baseFs.writeFilePromise(this.fsMapToBase(p), content, opts);
+  }
+  writeFileSync(p, content, opts) {
+    return this.baseFs.writeFileSync(this.fsMapToBase(p), content, opts);
+  }
+  async unlinkPromise(p) {
+    return this.baseFs.unlinkPromise(this.mapToBase(p));
+  }
+  unlinkSync(p) {
+    return this.baseFs.unlinkSync(this.mapToBase(p));
+  }
+  async utimesPromise(p, atime, mtime) {
+    return this.baseFs.utimesPromise(this.mapToBase(p), atime, mtime);
+  }
+  utimesSync(p, atime, mtime) {
+    return this.baseFs.utimesSync(this.mapToBase(p), atime, mtime);
+  }
+  async lutimesPromise(p, atime, mtime) {
+    return this.baseFs.lutimesPromise(this.mapToBase(p), atime, mtime);
+  }
+  lutimesSync(p, atime, mtime) {
+    return this.baseFs.lutimesSync(this.mapToBase(p), atime, mtime);
+  }
+  async mkdirPromise(p, opts) {
+    return this.baseFs.mkdirPromise(this.mapToBase(p), opts);
+  }
+  mkdirSync(p, opts) {
+    return this.baseFs.mkdirSync(this.mapToBase(p), opts);
+  }
+  async rmdirPromise(p, opts) {
+    return this.baseFs.rmdirPromise(this.mapToBase(p), opts);
+  }
+  rmdirSync(p, opts) {
+    return this.baseFs.rmdirSync(this.mapToBase(p), opts);
+  }
+  async rmPromise(p, opts) {
+    return this.baseFs.rmPromise(this.mapToBase(p), opts);
+  }
+  rmSync(p, opts) {
+    return this.baseFs.rmSync(this.mapToBase(p), opts);
+  }
+  async linkPromise(existingP, newP) {
+    return this.baseFs.linkPromise(this.mapToBase(existingP), this.mapToBase(newP));
+  }
+  linkSync(existingP, newP) {
+    return this.baseFs.linkSync(this.mapToBase(existingP), this.mapToBase(newP));
+  }
+  async symlinkPromise(target, p, type) {
+    const mappedP = this.mapToBase(p);
+    if (this.pathUtils.isAbsolute(target))
+      return this.baseFs.symlinkPromise(this.mapToBase(target), mappedP, type);
+    const mappedAbsoluteTarget = this.mapToBase(this.pathUtils.join(this.pathUtils.dirname(p), target));
+    const mappedTarget = this.baseFs.pathUtils.relative(this.baseFs.pathUtils.dirname(mappedP), mappedAbsoluteTarget);
+    return this.baseFs.symlinkPromise(mappedTarget, mappedP, type);
+  }
+  symlinkSync(target, p, type) {
+    const mappedP = this.mapToBase(p);
+    if (this.pathUtils.isAbsolute(target))
+      return this.baseFs.symlinkSync(this.mapToBase(target), mappedP, type);
+    const mappedAbsoluteTarget = this.mapToBase(this.pathUtils.join(this.pathUtils.dirname(p), target));
+    const mappedTarget = this.baseFs.pathUtils.relative(this.baseFs.pathUtils.dirname(mappedP), mappedAbsoluteTarget);
+    return this.baseFs.symlinkSync(mappedTarget, mappedP, type);
+  }
+  async readFilePromise(p, encoding) {
+    return this.baseFs.readFilePromise(this.fsMapToBase(p), encoding);
+  }
+  readFileSync(p, encoding) {
+    return this.baseFs.readFileSync(this.fsMapToBase(p), encoding);
+  }
+  readdirPromise(p, opts) {
+    return this.baseFs.readdirPromise(this.mapToBase(p), opts);
+  }
+  readdirSync(p, opts) {
+    return this.baseFs.readdirSync(this.mapToBase(p), opts);
+  }
+  async readlinkPromise(p) {
+    return this.mapFromBase(await this.baseFs.readlinkPromise(this.mapToBase(p)));
+  }
+  readlinkSync(p) {
+    return this.mapFromBase(this.baseFs.readlinkSync(this.mapToBase(p)));
+  }
+  async truncatePromise(p, len) {
+    return this.baseFs.truncatePromise(this.mapToBase(p), len);
+  }
+  truncateSync(p, len) {
+    return this.baseFs.truncateSync(this.mapToBase(p), len);
+  }
+  async ftruncatePromise(fd, len) {
+    return this.baseFs.ftruncatePromise(fd, len);
+  }
+  ftruncateSync(fd, len) {
+    return this.baseFs.ftruncateSync(fd, len);
+  }
+  watch(p, a, b) {
+    return this.baseFs.watch(
+      this.mapToBase(p),
+      // @ts-expect-error
+      a,
+      b
+    );
+  }
+  watchFile(p, a, b) {
+    return this.baseFs.watchFile(
+      this.mapToBase(p),
+      // @ts-expect-error
+      a,
+      b
+    );
+  }
+  unwatchFile(p, cb) {
+    return this.baseFs.unwatchFile(this.mapToBase(p), cb);
+  }
+  fsMapToBase(p) {
+    if (typeof p === `number`) {
+      return p;
+    } else {
+      return this.mapToBase(p);
+    }
+  }
+}
+
+function direntToPortable(dirent) {
+  const portableDirent = dirent;
+  if (typeof dirent.path === `string`)
+    portableDirent.path = npath.toPortablePath(dirent.path);
+  return portableDirent;
+}
+class NodeFS extends BasePortableFakeFS {
+  realFs;
+  constructor(realFs = fs) {
+    super();
+    this.realFs = realFs;
+  }
+  getExtractHint() {
+    return false;
+  }
+  getRealPath() {
+    return PortablePath.root;
+  }
+  resolve(p) {
+    return ppath.resolve(p);
+  }
+  async openPromise(p, flags, mode) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.open(npath.fromPortablePath(p), flags, mode, this.makeCallback(resolve, reject));
+    });
+  }
+  openSync(p, flags, mode) {
+    return this.realFs.openSync(npath.fromPortablePath(p), flags, mode);
+  }
+  async opendirPromise(p, opts) {
+    return await new Promise((resolve, reject) => {
+      if (typeof opts !== `undefined`) {
+        this.realFs.opendir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
+      } else {
+        this.realFs.opendir(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
+      }
+    }).then((dir) => {
+      const dirWithFixedPath = dir;
+      Object.defineProperty(dirWithFixedPath, `path`, {
+        value: p,
+        configurable: true,
+        writable: true
+      });
+      return dirWithFixedPath;
+    });
+  }
+  opendirSync(p, opts) {
+    const dir = typeof opts !== `undefined` ? this.realFs.opendirSync(npath.fromPortablePath(p), opts) : this.realFs.opendirSync(npath.fromPortablePath(p));
+    const dirWithFixedPath = dir;
+    Object.defineProperty(dirWithFixedPath, `path`, {
+      value: p,
+      configurable: true,
+      writable: true
+    });
+    return dirWithFixedPath;
+  }
+  async readPromise(fd, buffer, offset = 0, length = 0, position = -1) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.read(fd, buffer, offset, length, position, (error, bytesRead) => {
+        if (error) {
+          reject(error);
+        } else {
+          resolve(bytesRead);
+        }
+      });
+    });
+  }
+  readSync(fd, buffer, offset, length, position) {
+    return this.realFs.readSync(fd, buffer, offset, length, position);
+  }
+  async writePromise(fd, buffer, offset, length, position) {
+    return await new Promise((resolve, reject) => {
+      if (typeof buffer === `string`) {
+        return this.realFs.write(fd, buffer, offset, this.makeCallback(resolve, reject));
+      } else {
+        return this.realFs.write(fd, buffer, offset, length, position, this.makeCallback(resolve, reject));
+      }
+    });
+  }
+  writeSync(fd, buffer, offset, length, position) {
+    if (typeof buffer === `string`) {
+      return this.realFs.writeSync(fd, buffer, offset);
+    } else {
+      return this.realFs.writeSync(fd, buffer, offset, length, position);
+    }
+  }
+  async closePromise(fd) {
+    await new Promise((resolve, reject) => {
+      this.realFs.close(fd, this.makeCallback(resolve, reject));
+    });
+  }
+  closeSync(fd) {
+    this.realFs.closeSync(fd);
+  }
+  createReadStream(p, opts) {
+    const realPath = p !== null ? npath.fromPortablePath(p) : p;
+    return this.realFs.createReadStream(realPath, opts);
+  }
+  createWriteStream(p, opts) {
+    const realPath = p !== null ? npath.fromPortablePath(p) : p;
+    return this.realFs.createWriteStream(realPath, opts);
+  }
+  async realpathPromise(p) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.realpath(npath.fromPortablePath(p), {}, this.makeCallback(resolve, reject));
+    }).then((path) => {
+      return npath.toPortablePath(path);
+    });
+  }
+  realpathSync(p) {
+    return npath.toPortablePath(this.realFs.realpathSync(npath.fromPortablePath(p), {}));
+  }
+  async existsPromise(p) {
+    return await new Promise((resolve) => {
+      this.realFs.exists(npath.fromPortablePath(p), resolve);
+    });
+  }
+  accessSync(p, mode) {
+    return this.realFs.accessSync(npath.fromPortablePath(p), mode);
+  }
+  async accessPromise(p, mode) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.access(npath.fromPortablePath(p), mode, this.makeCallback(resolve, reject));
+    });
+  }
+  existsSync(p) {
+    return this.realFs.existsSync(npath.fromPortablePath(p));
+  }
+  async statPromise(p, opts) {
+    return await new Promise((resolve, reject) => {
+      if (opts) {
+        this.realFs.stat(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
+      } else {
+        this.realFs.stat(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
+      }
+    });
+  }
+  statSync(p, opts) {
+    if (opts) {
+      return this.realFs.statSync(npath.fromPortablePath(p), opts);
+    } else {
+      return this.realFs.statSync(npath.fromPortablePath(p));
+    }
+  }
+  async fstatPromise(fd, opts) {
+    return await new Promise((resolve, reject) => {
+      if (opts) {
+        this.realFs.fstat(fd, opts, this.makeCallback(resolve, reject));
+      } else {
+        this.realFs.fstat(fd, this.makeCallback(resolve, reject));
+      }
+    });
+  }
+  fstatSync(fd, opts) {
+    if (opts) {
+      return this.realFs.fstatSync(fd, opts);
+    } else {
+      return this.realFs.fstatSync(fd);
+    }
+  }
+  async lstatPromise(p, opts) {
+    return await new Promise((resolve, reject) => {
+      if (opts) {
+        this.realFs.lstat(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
+      } else {
+        this.realFs.lstat(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
+      }
+    });
+  }
+  lstatSync(p, opts) {
+    if (opts) {
+      return this.realFs.lstatSync(npath.fromPortablePath(p), opts);
+    } else {
+      return this.realFs.lstatSync(npath.fromPortablePath(p));
+    }
+  }
+  async fchmodPromise(fd, mask) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.fchmod(fd, mask, this.makeCallback(resolve, reject));
+    });
+  }
+  fchmodSync(fd, mask) {
+    return this.realFs.fchmodSync(fd, mask);
+  }
+  async chmodPromise(p, mask) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.chmod(npath.fromPortablePath(p), mask, this.makeCallback(resolve, reject));
+    });
+  }
+  chmodSync(p, mask) {
+    return this.realFs.chmodSync(npath.fromPortablePath(p), mask);
+  }
+  async fchownPromise(fd, uid, gid) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.fchown(fd, uid, gid, this.makeCallback(resolve, reject));
+    });
+  }
+  fchownSync(fd, uid, gid) {
+    return this.realFs.fchownSync(fd, uid, gid);
+  }
+  async chownPromise(p, uid, gid) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.chown(npath.fromPortablePath(p), uid, gid, this.makeCallback(resolve, reject));
+    });
+  }
+  chownSync(p, uid, gid) {
+    return this.realFs.chownSync(npath.fromPortablePath(p), uid, gid);
+  }
+  async renamePromise(oldP, newP) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.rename(npath.fromPortablePath(oldP), npath.fromPortablePath(newP), this.makeCallback(resolve, reject));
+    });
+  }
+  renameSync(oldP, newP) {
+    return this.realFs.renameSync(npath.fromPortablePath(oldP), npath.fromPortablePath(newP));
+  }
+  async copyFilePromise(sourceP, destP, flags = 0) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.copyFile(npath.fromPortablePath(sourceP), npath.fromPortablePath(destP), flags, this.makeCallback(resolve, reject));
+    });
+  }
+  copyFileSync(sourceP, destP, flags = 0) {
+    return this.realFs.copyFileSync(npath.fromPortablePath(sourceP), npath.fromPortablePath(destP), flags);
+  }
+  async appendFilePromise(p, content, opts) {
+    return await new Promise((resolve, reject) => {
+      const fsNativePath = typeof p === `string` ? npath.fromPortablePath(p) : p;
+      if (opts) {
+        this.realFs.appendFile(fsNativePath, content, opts, this.makeCallback(resolve, reject));
+      } else {
+        this.realFs.appendFile(fsNativePath, content, this.makeCallback(resolve, reject));
+      }
+    });
+  }
+  appendFileSync(p, content, opts) {
+    const fsNativePath = typeof p === `string` ? npath.fromPortablePath(p) : p;
+    if (opts) {
+      this.realFs.appendFileSync(fsNativePath, content, opts);
+    } else {
+      this.realFs.appendFileSync(fsNativePath, content);
+    }
+  }
+  async writeFilePromise(p, content, opts) {
+    return await new Promise((resolve, reject) => {
+      const fsNativePath = typeof p === `string` ? npath.fromPortablePath(p) : p;
+      if (opts) {
+        this.realFs.writeFile(fsNativePath, content, opts, this.makeCallback(resolve, reject));
+      } else {
+        this.realFs.writeFile(fsNativePath, content, this.makeCallback(resolve, reject));
+      }
+    });
+  }
+  writeFileSync(p, content, opts) {
+    const fsNativePath = typeof p === `string` ? npath.fromPortablePath(p) : p;
+    if (opts) {
+      this.realFs.writeFileSync(fsNativePath, content, opts);
+    } else {
+      this.realFs.writeFileSync(fsNativePath, content);
+    }
+  }
+  async unlinkPromise(p) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.unlink(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
+    });
+  }
+  unlinkSync(p) {
+    return this.realFs.unlinkSync(npath.fromPortablePath(p));
+  }
+  async utimesPromise(p, atime, mtime) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.utimes(npath.fromPortablePath(p), atime, mtime, this.makeCallback(resolve, reject));
+    });
+  }
+  utimesSync(p, atime, mtime) {
+    this.realFs.utimesSync(npath.fromPortablePath(p), atime, mtime);
+  }
+  async lutimesPromise(p, atime, mtime) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.lutimes(npath.fromPortablePath(p), atime, mtime, this.makeCallback(resolve, reject));
+    });
+  }
+  lutimesSync(p, atime, mtime) {
+    this.realFs.lutimesSync(npath.fromPortablePath(p), atime, mtime);
+  }
+  async mkdirPromise(p, opts) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.mkdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
+    });
+  }
+  mkdirSync(p, opts) {
+    return this.realFs.mkdirSync(npath.fromPortablePath(p), opts);
+  }
+  async rmdirPromise(p, opts) {
+    return await new Promise((resolve, reject) => {
+      if (opts) {
+        this.realFs.rmdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
+      } else {
+        this.realFs.rmdir(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
+      }
+    });
+  }
+  rmdirSync(p, opts) {
+    return this.realFs.rmdirSync(npath.fromPortablePath(p), opts);
+  }
+  async rmPromise(p, opts) {
+    return await new Promise((resolve, reject) => {
+      if (opts) {
+        this.realFs.rm(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
+      } else {
+        this.realFs.rm(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
+      }
+    });
+  }
+  rmSync(p, opts) {
+    return this.realFs.rmSync(npath.fromPortablePath(p), opts);
+  }
+  async linkPromise(existingP, newP) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.link(npath.fromPortablePath(existingP), npath.fromPortablePath(newP), this.makeCallback(resolve, reject));
+    });
+  }
+  linkSync(existingP, newP) {
+    return this.realFs.linkSync(npath.fromPortablePath(existingP), npath.fromPortablePath(newP));
+  }
+  async symlinkPromise(target, p, type) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.symlink(npath.fromPortablePath(target.replace(/\/+$/, ``)), npath.fromPortablePath(p), type, this.makeCallback(resolve, reject));
+    });
+  }
+  symlinkSync(target, p, type) {
+    return this.realFs.symlinkSync(npath.fromPortablePath(target.replace(/\/+$/, ``)), npath.fromPortablePath(p), type);
+  }
+  async readFilePromise(p, encoding) {
+    return await new Promise((resolve, reject) => {
+      const fsNativePath = typeof p === `string` ? npath.fromPortablePath(p) : p;
+      this.realFs.readFile(fsNativePath, encoding, this.makeCallback(resolve, reject));
+    });
+  }
+  readFileSync(p, encoding) {
+    const fsNativePath = typeof p === `string` ? npath.fromPortablePath(p) : p;
+    return this.realFs.readFileSync(fsNativePath, encoding);
+  }
+  async readdirPromise(p, opts) {
+    return await new Promise((resolve, reject) => {
+      if (opts) {
+        if (opts.recursive && process.platform === `win32`) {
+          if (opts.withFileTypes) {
+            this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(direntToPortable)), reject));
+          } else {
+            this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(npath.toPortablePath)), reject));
+          }
+        } else {
+          this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
+        }
+      } else {
+        this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
+      }
+    });
+  }
+  readdirSync(p, opts) {
+    if (opts) {
+      if (opts.recursive && process.platform === `win32`) {
+        if (opts.withFileTypes) {
+          return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(direntToPortable);
+        } else {
+          return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(npath.toPortablePath);
+        }
+      } else {
+        return this.realFs.readdirSync(npath.fromPortablePath(p), opts);
+      }
+    } else {
+      return this.realFs.readdirSync(npath.fromPortablePath(p));
+    }
+  }
+  async readlinkPromise(p) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.readlink(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
+    }).then((path) => {
+      return npath.toPortablePath(path);
+    });
+  }
+  readlinkSync(p) {
+    return npath.toPortablePath(this.realFs.readlinkSync(npath.fromPortablePath(p)));
+  }
+  async truncatePromise(p, len) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.truncate(npath.fromPortablePath(p), len, this.makeCallback(resolve, reject));
+    });
+  }
+  truncateSync(p, len) {
+    return this.realFs.truncateSync(npath.fromPortablePath(p), len);
+  }
+  async ftruncatePromise(fd, len) {
+    return await new Promise((resolve, reject) => {
+      this.realFs.ftruncate(fd, len, this.makeCallback(resolve, reject));
+    });
+  }
+  ftruncateSync(fd, len) {
+    return this.realFs.ftruncateSync(fd, len);
+  }
+  watch(p, a, b) {
+    return this.realFs.watch(
+      npath.fromPortablePath(p),
+      // @ts-expect-error
+      a,
+      b
+    );
+  }
+  watchFile(p, a, b) {
+    return this.realFs.watchFile(
+      npath.fromPortablePath(p),
+      // @ts-expect-error
+      a,
+      b
+    );
+  }
+  unwatchFile(p, cb) {
+    return this.realFs.unwatchFile(npath.fromPortablePath(p), cb);
+  }
+  makeCallback(resolve, reject) {
+    return (err, result) => {
+      if (err) {
+        reject(err);
+      } else {
+        resolve(result);
+      }
+    };
+  }
+}
+
+const NUMBER_REGEXP = /^[0-9]+$/;
+const VIRTUAL_REGEXP = /^(\/(?:[^/]+\/)*?(?:\$\$virtual|__virtual__))((?:\/((?:[^/]+-)?[a-f0-9]+)(?:\/([^/]+))?)?((?:\/.*)?))$/;
+const VALID_COMPONENT = /^([^/]+-)?[a-f0-9]+$/;
+class VirtualFS extends ProxiedFS {
+  baseFs;
+  static makeVirtualPath(base, component, to) {
+    if (ppath.basename(base) !== `__virtual__`)
+      throw new Error(`Assertion failed: Virtual folders must be named "__virtual__"`);
+    if (!ppath.basename(component).match(VALID_COMPONENT))
+      throw new Error(`Assertion failed: Virtual components must be ended by an hexadecimal hash`);
+    const target = ppath.relative(ppath.dirname(base), to);
+    const segments = target.split(`/`);
+    let depth = 0;
+    while (depth < segments.length && segments[depth] === `..`)
+      depth += 1;
+    const finalSegments = segments.slice(depth);
+    const fullVirtualPath = ppath.join(base, component, String(depth), ...finalSegments);
+    return fullVirtualPath;
+  }
+  static resolveVirtual(p) {
+    const match = p.match(VIRTUAL_REGEXP);
+    if (!match || !match[3] && match[5])
+      return p;
+    const target = ppath.dirname(match[1]);
+    if (!match[3] || !match[4])
+      return target;
+    const isnum = NUMBER_REGEXP.test(match[4]);
+    if (!isnum)
+      return p;
+    const depth = Number(match[4]);
+    const backstep = `../`.repeat(depth);
+    const subpath = match[5] || `.`;
+    return VirtualFS.resolveVirtual(ppath.join(target, backstep, subpath));
+  }
+  constructor({ baseFs = new NodeFS() } = {}) {
+    super(ppath);
+    this.baseFs = baseFs;
+  }
+  getExtractHint(hints) {
+    return this.baseFs.getExtractHint(hints);
+  }
+  getRealPath() {
+    return this.baseFs.getRealPath();
+  }
+  realpathSync(p) {
+    const match = p.match(VIRTUAL_REGEXP);
+    if (!match)
+      return this.baseFs.realpathSync(p);
+    if (!match[5])
+      return p;
+    const realpath = this.baseFs.realpathSync(this.mapToBase(p));
+    return VirtualFS.makeVirtualPath(match[1], match[3], realpath);
+  }
+  async realpathPromise(p) {
+    const match = p.match(VIRTUAL_REGEXP);
+    if (!match)
+      return await this.baseFs.realpathPromise(p);
+    if (!match[5])
+      return p;
+    const realpath = await this.baseFs.realpathPromise(this.mapToBase(p));
+    return VirtualFS.makeVirtualPath(match[1], match[3], realpath);
+  }
+  mapToBase(p) {
+    if (p === ``)
+      return p;
+    if (this.pathUtils.isAbsolute(p))
+      return VirtualFS.resolveVirtual(p);
+    const resolvedRoot = VirtualFS.resolveVirtual(this.baseFs.resolve(PortablePath.dot));
+    const resolvedP = VirtualFS.resolveVirtual(this.baseFs.resolve(p));
+    return ppath.relative(resolvedRoot, resolvedP) || PortablePath.dot;
+  }
+  mapFromBase(p) {
+    return p;
+  }
+}
+
+const URL = Number(process.versions.node.split('.', 1)[0]) < 20 ? URL$1 : globalThis.URL;
+
+const [major, minor] = process.versions.node.split(`.`).map((value) => parseInt(value, 10));
+const WATCH_MODE_MESSAGE_USES_ARRAYS = major > 19 || major === 19 && minor >= 2 || major === 18 && minor >= 13;
+const HAS_LAZY_LOADED_TRANSLATORS = major === 20 && minor < 6 || major === 19 && minor >= 3;
+const SUPPORTS_IMPORT_ATTRIBUTES = major >= 21 || major === 20 && minor >= 10 || major === 18 && minor >= 20;
+const SUPPORTS_IMPORT_ATTRIBUTES_ONLY = major >= 22;
+
+function readPackageScope(checkPath) {
+  const rootSeparatorIndex = checkPath.indexOf(npath.sep);
+  let separatorIndex;
+  do {
+    separatorIndex = checkPath.lastIndexOf(npath.sep);
+    checkPath = checkPath.slice(0, separatorIndex);
+    if (checkPath.endsWith(`${npath.sep}node_modules`))
+      return false;
+    const pjson = readPackage(checkPath + npath.sep);
+    if (pjson) {
+      return {
+        data: pjson,
+        path: checkPath
+      };
+    }
+  } while (separatorIndex > rootSeparatorIndex);
+  return false;
+}
+function readPackage(requestPath) {
+  const jsonPath = npath.resolve(requestPath, `package.json`);
+  if (!fs.existsSync(jsonPath))
+    return null;
+  return JSON.parse(fs.readFileSync(jsonPath, `utf8`));
+}
+
+async function tryReadFile$1(path2) {
+  try {
+    return await fs.promises.readFile(path2, `utf8`);
+  } catch (error) {
+    if (error.code === `ENOENT`)
+      return null;
+    throw error;
+  }
+}
+function tryParseURL(str, base) {
+  try {
+    return new URL(str, base);
+  } catch {
+    return null;
+  }
+}
+let entrypointPath = null;
+function setEntrypointPath(file) {
+  entrypointPath = file;
+}
+function getFileFormat(filepath) {
+  const ext = path.extname(filepath);
+  switch (ext) {
+    case `.mjs`: {
+      return `module`;
+    }
+    case `.cjs`: {
+      return `commonjs`;
+    }
+    case `.wasm`: {
+      throw new Error(
+        `Unknown file extension ".wasm" for ${filepath}`
+      );
+    }
+    case `.json`: {
+      return `json`;
+    }
+    case `.js`: {
+      const pkg = readPackageScope(filepath);
+      if (!pkg)
+        return `commonjs`;
+      return pkg.data.type ?? `commonjs`;
+    }
+    default: {
+      if (entrypointPath !== filepath)
+        return null;
+      const pkg = readPackageScope(filepath);
+      if (!pkg)
+        return `commonjs`;
+      if (pkg.data.type === `module`)
+        return null;
+      return pkg.data.type ?? `commonjs`;
+    }
+  }
+}
+
+async function load$1(urlString, context, nextLoad) {
+  const url = tryParseURL(urlString);
+  if (url?.protocol !== `file:`)
+    return nextLoad(urlString, context, nextLoad);
+  const filePath = fileURLToPath(url);
+  const format = getFileFormat(filePath);
+  if (!format)
+    return nextLoad(urlString, context, nextLoad);
+  if (format === `json`) {
+    if (SUPPORTS_IMPORT_ATTRIBUTES_ONLY) {
+      if (context.importAttributes?.type !== `json`) {
+        const err = new TypeError(`[ERR_IMPORT_ATTRIBUTE_MISSING]: Module "${urlString}" needs an import attribute of "type: json"`);
+        err.code = `ERR_IMPORT_ATTRIBUTE_MISSING`;
+        throw err;
+      }
+    } else {
+      const type = `importAttributes` in context ? context.importAttributes?.type : context.importAssertions?.type;
+      if (type !== `json`) {
+        const err = new TypeError(`[ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "${urlString}" needs an import ${SUPPORTS_IMPORT_ATTRIBUTES ? `attribute` : `assertion`} of type "json"`);
+        err.code = `ERR_IMPORT_ASSERTION_TYPE_MISSING`;
+        throw err;
+      }
+    }
+  }
+  if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) {
+    const pathToSend = pathToFileURL(
+      npath.fromPortablePath(
+        VirtualFS.resolveVirtual(npath.toPortablePath(filePath))
+      )
+    ).href;
+    process.send({
+      "watch:import": WATCH_MODE_MESSAGE_USES_ARRAYS ? [pathToSend] : pathToSend
+    });
+  }
+  return {
+    format,
+    source: format === `commonjs` ? void 0 : await fs.promises.readFile(filePath, `utf8`),
+    shortCircuit: true
+  };
+}
+
+const ArrayIsArray = Array.isArray;
+const JSONStringify = JSON.stringify;
+const ObjectGetOwnPropertyNames = Object.getOwnPropertyNames;
+const ObjectPrototypeHasOwnProperty = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
+const RegExpPrototypeExec = (obj, string) => RegExp.prototype.exec.call(obj, string);
+const RegExpPrototypeSymbolReplace = (obj, ...rest) => RegExp.prototype[Symbol.replace].apply(obj, rest);
+const StringPrototypeEndsWith = (str, ...rest) => String.prototype.endsWith.apply(str, rest);
+const StringPrototypeIncludes = (str, ...rest) => String.prototype.includes.apply(str, rest);
+const StringPrototypeLastIndexOf = (str, ...rest) => String.prototype.lastIndexOf.apply(str, rest);
+const StringPrototypeIndexOf = (str, ...rest) => String.prototype.indexOf.apply(str, rest);
+const StringPrototypeReplace = (str, ...rest) => String.prototype.replace.apply(str, rest);
+const StringPrototypeSlice = (str, ...rest) => String.prototype.slice.apply(str, rest);
+const StringPrototypeStartsWith = (str, ...rest) => String.prototype.startsWith.apply(str, rest);
+const SafeMap = Map;
+const JSONParse = JSON.parse;
+
+function createErrorType(code, messageCreator, errorType) {
+  return class extends errorType {
+    constructor(...args) {
+      super(messageCreator(...args));
+      this.code = code;
+      this.name = `${errorType.name} [${code}]`;
+    }
+  };
+}
+const ERR_PACKAGE_IMPORT_NOT_DEFINED = createErrorType(
+  `ERR_PACKAGE_IMPORT_NOT_DEFINED`,
+  (specifier, packagePath, base) => {
+    return `Package import specifier "${specifier}" is not defined${packagePath ? ` in package ${packagePath}package.json` : ``} imported from ${base}`;
+  },
+  TypeError
+);
+const ERR_INVALID_MODULE_SPECIFIER = createErrorType(
+  `ERR_INVALID_MODULE_SPECIFIER`,
+  (request, reason, base = void 0) => {
+    return `Invalid module "${request}" ${reason}${base ? ` imported from ${base}` : ``}`;
+  },
+  TypeError
+);
+const ERR_INVALID_PACKAGE_TARGET = createErrorType(
+  `ERR_INVALID_PACKAGE_TARGET`,
+  (pkgPath, key, target, isImport = false, base = void 0) => {
+    const relError = typeof target === `string` && !isImport && target.length && !StringPrototypeStartsWith(target, `./`);
+    if (key === `.`) {
+      assert(isImport === false);
+      return `Invalid "exports" main target ${JSONStringify(target)} defined in the package config ${pkgPath}package.json${base ? ` imported from ${base}` : ``}${relError ? `; targets must start with "./"` : ``}`;
+    }
+    return `Invalid "${isImport ? `imports` : `exports`}" target ${JSONStringify(
+      target
+    )} defined for '${key}' in the package config ${pkgPath}package.json${base ? ` imported from ${base}` : ``}${relError ? `; targets must start with "./"` : ``}`;
+  },
+  Error
+);
+const ERR_INVALID_PACKAGE_CONFIG = createErrorType(
+  `ERR_INVALID_PACKAGE_CONFIG`,
+  (path, base, message) => {
+    return `Invalid package config ${path}${base ? ` while importing ${base}` : ``}${message ? `. ${message}` : ``}`;
+  },
+  Error
+);
+
+function filterOwnProperties(source, keys) {
+  const filtered = /* @__PURE__ */ Object.create(null);
+  for (let i = 0; i < keys.length; i++) {
+    const key = keys[i];
+    if (ObjectPrototypeHasOwnProperty(source, key)) {
+      filtered[key] = source[key];
+    }
+  }
+  return filtered;
+}
+
+const packageJSONCache = new SafeMap();
+function getPackageConfig(path, specifier, base, readFileSyncFn) {
+  const existing = packageJSONCache.get(path);
+  if (existing !== void 0) {
+    return existing;
+  }
+  const source = readFileSyncFn(path);
+  if (source === void 0) {
+    const packageConfig2 = {
+      pjsonPath: path,
+      exists: false,
+      main: void 0,
+      name: void 0,
+      type: "none",
+      exports: void 0,
+      imports: void 0
+    };
+    packageJSONCache.set(path, packageConfig2);
+    return packageConfig2;
+  }
+  let packageJSON;
+  try {
+    packageJSON = JSONParse(source);
+  } catch (error) {
+    throw new ERR_INVALID_PACKAGE_CONFIG(
+      path,
+      (base ? `"${specifier}" from ` : "") + fileURLToPath(base || specifier),
+      error.message
+    );
+  }
+  let { imports, main, name, type } = filterOwnProperties(packageJSON, [
+    "imports",
+    "main",
+    "name",
+    "type"
+  ]);
+  const exports = ObjectPrototypeHasOwnProperty(packageJSON, "exports") ? packageJSON.exports : void 0;
+  if (typeof imports !== "object" || imports === null) {
+    imports = void 0;
+  }
+  if (typeof main !== "string") {
+    main = void 0;
+  }
+  if (typeof name !== "string") {
+    name = void 0;
+  }
+  if (type !== "module" && type !== "commonjs") {
+    type = "none";
+  }
+  const packageConfig = {
+    pjsonPath: path,
+    exists: true,
+    main,
+    name,
+    type,
+    exports,
+    imports
+  };
+  packageJSONCache.set(path, packageConfig);
+  return packageConfig;
+}
+function getPackageScopeConfig(resolved, readFileSyncFn) {
+  let packageJSONUrl = new URL("./package.json", resolved);
+  while (true) {
+    const packageJSONPath2 = packageJSONUrl.pathname;
+    if (StringPrototypeEndsWith(packageJSONPath2, "node_modules/package.json")) {
+      break;
+    }
+    const packageConfig2 = getPackageConfig(
+      fileURLToPath(packageJSONUrl),
+      resolved,
+      void 0,
+      readFileSyncFn
+    );
+    if (packageConfig2.exists) {
+      return packageConfig2;
+    }
+    const lastPackageJSONUrl = packageJSONUrl;
+    packageJSONUrl = new URL("../package.json", packageJSONUrl);
+    if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) {
+      break;
+    }
+  }
+  const packageJSONPath = fileURLToPath(packageJSONUrl);
+  const packageConfig = {
+    pjsonPath: packageJSONPath,
+    exists: false,
+    main: void 0,
+    name: void 0,
+    type: "none",
+    exports: void 0,
+    imports: void 0
+  };
+  packageJSONCache.set(packageJSONPath, packageConfig);
+  return packageConfig;
+}
+
+function throwImportNotDefined(specifier, packageJSONUrl, base) {
+  throw new ERR_PACKAGE_IMPORT_NOT_DEFINED(
+    specifier,
+    packageJSONUrl && fileURLToPath(new URL(".", packageJSONUrl)),
+    fileURLToPath(base)
+  );
+}
+function throwInvalidSubpath(subpath, packageJSONUrl, internal, base) {
+  const reason = `request is not a valid subpath for the "${internal ? "imports" : "exports"}" resolution of ${fileURLToPath(packageJSONUrl)}`;
+  throw new ERR_INVALID_MODULE_SPECIFIER(
+    subpath,
+    reason,
+    base && fileURLToPath(base)
+  );
+}
+function throwInvalidPackageTarget(subpath, target, packageJSONUrl, internal, base) {
+  if (typeof target === "object" && target !== null) {
+    target = JSONStringify(target, null, "");
+  } else {
+    target = `${target}`;
+  }
+  throw new ERR_INVALID_PACKAGE_TARGET(
+    fileURLToPath(new URL(".", packageJSONUrl)),
+    subpath,
+    target,
+    internal,
+    base && fileURLToPath(base)
+  );
+}
+const invalidSegmentRegEx = /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i;
+const patternRegEx = /\*/g;
+function resolvePackageTargetString(target, subpath, match, packageJSONUrl, base, pattern, internal, conditions) {
+  if (subpath !== "" && !pattern && target[target.length - 1] !== "/")
+    throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
+  if (!StringPrototypeStartsWith(target, "./")) {
+    if (internal && !StringPrototypeStartsWith(target, "../") && !StringPrototypeStartsWith(target, "/")) {
+      let isURL = false;
+      try {
+        new URL(target);
+        isURL = true;
+      } catch {
+      }
+      if (!isURL) {
+        const exportTarget = pattern ? RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) : target + subpath;
+        return exportTarget;
+      }
+    }
+    throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
+  }
+  if (RegExpPrototypeExec(
+    invalidSegmentRegEx,
+    StringPrototypeSlice(target, 2)
+  ) !== null)
+    throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
+  const resolved = new URL(target, packageJSONUrl);
+  const resolvedPath = resolved.pathname;
+  const packagePath = new URL(".", packageJSONUrl).pathname;
+  if (!StringPrototypeStartsWith(resolvedPath, packagePath))
+    throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
+  if (subpath === "") return resolved;
+  if (RegExpPrototypeExec(invalidSegmentRegEx, subpath) !== null) {
+    const request = pattern ? StringPrototypeReplace(match, "*", () => subpath) : match + subpath;
+    throwInvalidSubpath(request, packageJSONUrl, internal, base);
+  }
+  if (pattern) {
+    return new URL(
+      RegExpPrototypeSymbolReplace(patternRegEx, resolved.href, () => subpath)
+    );
+  }
+  return new URL(subpath, resolved);
+}
+function isArrayIndex(key) {
+  const keyNum = +key;
+  if (`${keyNum}` !== key) return false;
+  return keyNum >= 0 && keyNum < 4294967295;
+}
+function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath, base, pattern, internal, conditions) {
+  if (typeof target === "string") {
+    return resolvePackageTargetString(
+      target,
+      subpath,
+      packageSubpath,
+      packageJSONUrl,
+      base,
+      pattern,
+      internal);
+  } else if (ArrayIsArray(target)) {
+    if (target.length === 0) {
+      return null;
+    }
+    let lastException;
+    for (let i = 0; i < target.length; i++) {
+      const targetItem = target[i];
+      let resolveResult;
+      try {
+        resolveResult = resolvePackageTarget(
+          packageJSONUrl,
+          targetItem,
+          subpath,
+          packageSubpath,
+          base,
+          pattern,
+          internal,
+          conditions
+        );
+      } catch (e) {
+        lastException = e;
+        if (e.code === "ERR_INVALID_PACKAGE_TARGET") {
+          continue;
+        }
+        throw e;
+      }
+      if (resolveResult === void 0) {
+        continue;
+      }
+      if (resolveResult === null) {
+        lastException = null;
+        continue;
+      }
+      return resolveResult;
+    }
+    if (lastException === void 0 || lastException === null)
+      return lastException;
+    throw lastException;
+  } else if (typeof target === "object" && target !== null) {
+    const keys = ObjectGetOwnPropertyNames(target);
+    for (let i = 0; i < keys.length; i++) {
+      const key = keys[i];
+      if (isArrayIndex(key)) {
+        throw new ERR_INVALID_PACKAGE_CONFIG(
+          fileURLToPath(packageJSONUrl),
+          base,
+          '"exports" cannot contain numeric property keys.'
+        );
+      }
+    }
+    for (let i = 0; i < keys.length; i++) {
+      const key = keys[i];
+      if (key === "default" || conditions.has(key)) {
+        const conditionalTarget = target[key];
+        const resolveResult = resolvePackageTarget(
+          packageJSONUrl,
+          conditionalTarget,
+          subpath,
+          packageSubpath,
+          base,
+          pattern,
+          internal,
+          conditions
+        );
+        if (resolveResult === void 0) continue;
+        return resolveResult;
+      }
+    }
+    return void 0;
+  } else if (target === null) {
+    return null;
+  }
+  throwInvalidPackageTarget(
+    packageSubpath,
+    target,
+    packageJSONUrl,
+    internal,
+    base
+  );
+}
+function patternKeyCompare(a, b) {
+  const aPatternIndex = StringPrototypeIndexOf(a, "*");
+  const bPatternIndex = StringPrototypeIndexOf(b, "*");
+  const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1;
+  const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1;
+  if (baseLenA > baseLenB) return -1;
+  if (baseLenB > baseLenA) return 1;
+  if (aPatternIndex === -1) return 1;
+  if (bPatternIndex === -1) return -1;
+  if (a.length > b.length) return -1;
+  if (b.length > a.length) return 1;
+  return 0;
+}
+function packageImportsResolve({ name, base, conditions, readFileSyncFn }) {
+  if (name === "#" || StringPrototypeStartsWith(name, "#/") || StringPrototypeEndsWith(name, "/")) {
+    const reason = "is not a valid internal imports specifier name";
+    throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base));
+  }
+  let packageJSONUrl;
+  const packageConfig = getPackageScopeConfig(base, readFileSyncFn);
+  if (packageConfig.exists) {
+    packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
+    const imports = packageConfig.imports;
+    if (imports) {
+      if (ObjectPrototypeHasOwnProperty(imports, name) && !StringPrototypeIncludes(name, "*")) {
+        const resolveResult = resolvePackageTarget(
+          packageJSONUrl,
+          imports[name],
+          "",
+          name,
+          base,
+          false,
+          true,
+          conditions
+        );
+        if (resolveResult != null) {
+          return resolveResult;
+        }
+      } else {
+        let bestMatch = "";
+        let bestMatchSubpath;
+        const keys = ObjectGetOwnPropertyNames(imports);
+        for (let i = 0; i < keys.length; i++) {
+          const key = keys[i];
+          const patternIndex = StringPrototypeIndexOf(key, "*");
+          if (patternIndex !== -1 && StringPrototypeStartsWith(
+            name,
+            StringPrototypeSlice(key, 0, patternIndex)
+          )) {
+            const patternTrailer = StringPrototypeSlice(key, patternIndex + 1);
+            if (name.length >= key.length && StringPrototypeEndsWith(name, patternTrailer) && patternKeyCompare(bestMatch, key) === 1 && StringPrototypeLastIndexOf(key, "*") === patternIndex) {
+              bestMatch = key;
+              bestMatchSubpath = StringPrototypeSlice(
+                name,
+                patternIndex,
+                name.length - patternTrailer.length
+              );
+            }
+          }
+        }
+        if (bestMatch) {
+          const target = imports[bestMatch];
+          const resolveResult = resolvePackageTarget(
+            packageJSONUrl,
+            target,
+            bestMatchSubpath,
+            bestMatch,
+            base,
+            true,
+            true,
+            conditions
+          );
+          if (resolveResult != null) {
+            return resolveResult;
+          }
+        }
+      }
+    }
+  }
+  throwImportNotDefined(name, packageJSONUrl, base);
+}
+
+let findPnpApi = esmModule.findPnpApi;
+if (!findPnpApi) {
+  const require = createRequire(import.meta.url);
+  const pnpApi = require(`./.pnp.cjs`);
+  pnpApi.setup();
+  findPnpApi = esmModule.findPnpApi;
+}
+const pathRegExp = /^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:node:)?(?:@[^/]+\/)?[^/]+)\/*(.*|)$/;
+const isRelativeRegexp = /^\.{0,2}\//;
+function tryReadFile(filePath) {
+  try {
+    return fs.readFileSync(filePath, `utf8`);
+  } catch (err) {
+    if (err.code === `ENOENT`)
+      return void 0;
+    throw err;
+  }
+}
+async function resolvePrivateRequest(specifier, issuer, context, nextResolve) {
+  const resolved = packageImportsResolve({
+    name: specifier,
+    base: pathToFileURL(issuer),
+    conditions: new Set(context.conditions),
+    readFileSyncFn: tryReadFile
+  });
+  if (resolved instanceof URL) {
+    return { url: resolved.href, shortCircuit: true };
+  } else {
+    if (resolved.startsWith(`#`))
+      throw new Error(`Mapping from one private import to another isn't allowed`);
+    return resolve$1(resolved, context, nextResolve);
+  }
+}
+async function resolve$1(originalSpecifier, context, nextResolve) {
+  if (!findPnpApi || isBuiltin(originalSpecifier))
+    return nextResolve(originalSpecifier, context, nextResolve);
+  let specifier = originalSpecifier;
+  const url = tryParseURL(specifier, isRelativeRegexp.test(specifier) ? context.parentURL : void 0);
+  if (url) {
+    if (url.protocol !== `file:`)
+      return nextResolve(originalSpecifier, context, nextResolve);
+    specifier = fileURLToPath(url);
+  }
+  const { parentURL, conditions = [] } = context;
+  const issuer = parentURL && tryParseURL(parentURL)?.protocol === `file:` ? fileURLToPath(parentURL) : process.cwd();
+  const pnpapi = findPnpApi(issuer) ?? (url ? findPnpApi(specifier) : null);
+  if (!pnpapi)
+    return nextResolve(originalSpecifier, context, nextResolve);
+  if (specifier.startsWith(`#`))
+    return resolvePrivateRequest(specifier, issuer, context, nextResolve);
+  const dependencyNameMatch = specifier.match(pathRegExp);
+  let allowLegacyResolve = false;
+  if (dependencyNameMatch) {
+    const [, dependencyName, subPath] = dependencyNameMatch;
+    if (subPath === `` && dependencyName !== `pnpapi`) {
+      const resolved = pnpapi.resolveToUnqualified(`${dependencyName}/package.json`, issuer);
+      if (resolved) {
+        const content = await tryReadFile$1(resolved);
+        if (content) {
+          const pkg = JSON.parse(content);
+          allowLegacyResolve = pkg.exports == null;
+        }
+      }
+    }
+  }
+  let result;
+  try {
+    result = pnpapi.resolveRequest(specifier, issuer, {
+      conditions: new Set(conditions),
+      // TODO: Handle --experimental-specifier-resolution=node
+      extensions: allowLegacyResolve ? void 0 : []
+    });
+  } catch (err) {
+    if (err instanceof Error && `code` in err && err.code === `MODULE_NOT_FOUND`)
+      err.code = `ERR_MODULE_NOT_FOUND`;
+    throw err;
+  }
+  if (!result)
+    throw new Error(`Resolving '${specifier}' from '${issuer}' failed`);
+  const resultURL = pathToFileURL(result);
+  if (url) {
+    resultURL.search = url.search;
+    resultURL.hash = url.hash;
+  }
+  if (!parentURL)
+    setEntrypointPath(fileURLToPath(resultURL));
+  return {
+    url: resultURL.href,
+    shortCircuit: true
+  };
+}
+
+if (!HAS_LAZY_LOADED_TRANSLATORS) {
+  const binding = process.binding(`fs`);
+  const originalReadFile = binding.readFileUtf8 || binding.readFileSync;
+  if (originalReadFile) {
+    binding[originalReadFile.name] = function(...args) {
+      try {
+        return fs.readFileSync(args[0], {
+          encoding: `utf8`,
+          // @ts-expect-error - The docs says it needs to be a string but
+          // links to https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#file-system-flags
+          // which says it can be a number which matches the implementation.
+          flag: args[1]
+        });
+      } catch {
+      }
+      return originalReadFile.apply(this, args);
+    };
+  } else {
+    const binding2 = process.binding(`fs`);
+    const originalfstat = binding2.fstat;
+    const ZIP_MASK = 4278190080;
+    const ZIP_MAGIC = 704643072;
+    binding2.fstat = function(...args) {
+      const [fd, useBigint, req] = args;
+      if ((fd & ZIP_MASK) === ZIP_MAGIC && useBigint === false && req === void 0) {
+        try {
+          const stats = fs.fstatSync(fd);
+          return new Float64Array([
+            stats.dev,
+            stats.mode,
+            stats.nlink,
+            stats.uid,
+            stats.gid,
+            stats.rdev,
+            stats.blksize,
+            stats.ino,
+            stats.size,
+            stats.blocks
+            // atime sec
+            // atime ns
+            // mtime sec
+            // mtime ns
+            // ctime sec
+            // ctime ns
+            // birthtime sec
+            // birthtime ns
+          ]);
+        } catch {
+        }
+      }
+      return originalfstat.apply(this, args);
+    };
+  }
+}
+
+const resolve = resolve$1;
+const load = load$1;
+
+export { load, resolve };

+ 3 - 0
.vscode/settings.json

@@ -0,0 +1,3 @@
+{
+    "typescript.tsdk": "node_modules\\typescript\\lib"
+}

+ 75 - 0
README.md

@@ -0,0 +1,75 @@
+# Nuxt Minimal Starter
+
+Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
+
+## Setup
+
+Make sure to install dependencies:
+
+```bash
+# npm
+npm install
+
+# pnpm
+pnpm install
+
+# yarn
+yarn install
+
+# bun
+bun install
+```
+
+## Development Server
+
+Start the development server on `http://localhost:3000`:
+
+```bash
+# npm
+npm run dev
+
+# pnpm
+pnpm dev
+
+# yarn
+yarn dev
+
+# bun
+bun run dev
+```
+
+## Production
+
+Build the application for production:
+
+```bash
+# npm
+npm run build
+
+# pnpm
+pnpm build
+
+# yarn
+yarn build
+
+# bun
+bun run build
+```
+
+Locally preview production build:
+
+```bash
+# npm
+npm run preview
+
+# pnpm
+pnpm preview
+
+# yarn
+yarn preview
+
+# bun
+bun run preview
+```
+
+Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

+ 71 - 0
app/app.vue

@@ -0,0 +1,71 @@
+<template>
+  <UApp>
+    <AdminLoadingOverlay />
+    <component :is="DynamicHeader" v-if="!isAdminPage && !isIndexPage" />
+    <NuxtLayout>
+      <NuxtPage />
+    </NuxtLayout>
+    <component :is="DynamicFooter" v-if="!isAdminPage" />
+  </UApp>
+</template>
+
+<script setup>
+  import { computed, defineAsyncComponent, watch } from "vue";
+  import { useRoute } from "vue-router";
+  import { useHead } from "#app";
+
+  const route = useRoute();
+  const config = useRuntimeConfig();
+
+  // admin 페이지 체크
+  const isAdminPage = computed(() => {
+    return route.path.startsWith("/site-manager");
+  });
+
+  // index 페이지 체크 (게이트 페이지)
+  const isIndexPage = computed(() => {
+    return route.path === "/";
+  });
+
+  // 파비콘 동적 설정
+  const favicon = computed(() => {
+    if (route.path.includes("/lincoln")) {
+      return "/favicon-lincoln.ico";
+    }
+    return "/favicon-ford.ico";
+  });
+
+  // 타이틀 동적 설정
+  const pageTitle = computed(() => {
+    return "파이럿존";
+  });
+
+  useHead(() => ({
+    title: pageTitle.value,
+    link: [
+      {
+        rel: "icon",
+        type: "image/x-icon",
+        href: favicon.value,
+      },
+    ],
+  }));
+
+  // 파비콘 강제 업데이트 (브라우저 캐시 우회)
+  watch(favicon, (newFavicon) => {
+    const link = document.querySelector("link[rel='icon']");
+    if (link) {
+      link.href = newFavicon + "?v=" + Date.now();
+    }
+  });
+
+  // 동적 Header 컴포넌트
+  const DynamicHeader = defineAsyncComponent(() => import(`~/components/header.vue`));
+
+  // 동적 Footer 컴포넌트
+  const DynamicFooter = defineAsyncComponent(() => import(`~/components/footer.vue`));
+</script>
+<style setup>
+  @import "tailwindcss";
+  @import "@nuxt/ui";
+</style>

+ 7529 - 0
app/assets/scss/admin.scss

@@ -0,0 +1,7529 @@
+@charset "UTF-8";
+@use "sass:math";
+
+
+// Utility classes for padding and margin (1-100)
+@for $i from 1 through 200 {
+
+  // Padding
+  .pt--#{$i} {
+    padding-top: #{$i}px !important;
+  }
+
+  .pr--#{$i} {
+    padding-right: #{$i}px !important;
+  }
+
+  .pb--#{$i} {
+    padding-bottom: #{$i}px !important;
+  }
+
+  .pl--#{$i} {
+    padding-left: #{$i}px !important;
+  }
+
+  .p--#{$i} {
+    padding: #{$i}px !important;
+  }
+
+  // Margin
+  .mt--#{$i} {
+    margin-top: #{$i}px !important;
+  }
+
+  .mr--#{$i} {
+    margin-right: #{$i}px !important;
+  }
+
+  .mb--#{$i} {
+    margin-bottom: #{$i}px !important;
+  }
+
+  .ml--#{$i} {
+    margin-left: #{$i}px !important;
+  }
+
+  .m--#{$i} {
+    margin: #{$i}px !important;
+  }
+
+  @media (max-width: 720px) {
+    .pt--#{$i} {
+      padding-top: #{math.div($i, 2)}px !important;
+    }
+
+    .pr--#{$i} {
+      padding-right: #{math.div($i, 2)}px !important;
+    }
+
+    .pb--#{$i} {
+      padding-bottom: #{math.div($i, 2)}px !important;
+    }
+
+    .pl--#{$i} {
+      padding-left: #{math.div($i, 2)}px !important;
+    }
+
+    .p--#{$i} {
+      padding: #{math.div($i, 2)}px !important;
+    }
+
+    .mt--#{$i} {
+      margin-top: #{math.div($i, 2)}px !important;
+    }
+
+    .mr--#{$i} {
+      margin-right: #{math.div($i, 2)}px !important;
+    }
+
+    .mb--#{$i} {
+      margin-bottom: #{math.div($i, 2)}px !important;
+    }
+
+    .ml--#{$i} {
+      margin-left: #{math.div($i, 2)}px !important;
+    }
+
+    .m--#{$i} {
+      margin: #{math.div($i, 2)}px !important;
+    }
+  }
+}
+
+@for $i from 12 through 40 {
+  .ft--#{$i} {
+    font-size : #{$i}px !important;
+  }
+}
+
+html {
+  scroll-behavior: smooth;
+}
+
+:root {
+  --max-content-width: 1920px;
+  --page-margin: 16px;
+
+  @media (min-width: 375px) {
+    --page-margin: 28px;
+  }
+
+  @media (min-width: 768px) {
+    --page-margin: 40px;
+  }
+
+  @media (min-width: 1024px) {
+    --page-margin: 60px;
+  }
+
+  @media (min-width: 1440px) {
+    --page-margin: 96px;
+  }
+
+  @media (min-width: 1920px) {
+    --page-margin: 96px;
+  }
+
+  @property --gradient-start {
+    syntax: '<color>';
+    initial-value: transparent;
+    inherits: false;
+  }
+
+  @property --gradient-end {
+    syntax: '<color>';
+    initial-value: transparent;
+    inherits: false;
+  }
+
+  --spacing-relative-2xs: 4px;
+  --spacing-relative-xs: 8px;
+  --spacing-relative-sm: 12px;
+  --spacing-relative-md: 16px;
+  --spacing-relative-lg: 24px;
+  --spacing-relative-xl: 28px;
+  --spacing-relative-2xl: 36px;
+  --spacing-relative-3xl: 40px;
+  --spacing-relative-4xl: 48px;
+  --spacing-relative-5xl: 64px;
+  --spacing-relative-6xl: 80px;
+  --spacing-relative-7xl: 96px;
+  --spacing-relative-8xl: 120px;
+
+  @media (min-width: 768px) {
+    --spacing-relative-3xl: 48px;
+    --spacing-relative-4xl: 64px;
+    --spacing-relative-5xl: 72px;
+    --spacing-relative-6xl: 88px;
+    --spacing-relative-7xl: 104px;
+    --spacing-relative-8xl: 136px;
+  }
+
+  @media (min-width: 1024px) {
+    --spacing-relative-2xl: 40px;
+    --spacing-relative-3xl: 56px;
+    --spacing-relative-4xl: 72px;
+    --spacing-relative-5xl: 88px;
+    --spacing-relative-6xl: 104px;
+    --spacing-relative-7xl: 128px;
+    --spacing-relative-8xl: 168px;
+  }
+
+  @media (min-width: 1440px) {
+    --spacing-relative-xl: 32px;
+    --spacing-relative-2xl: 56px;
+    --spacing-relative-3xl: 72px;
+    --spacing-relative-4xl: 88px;
+    --spacing-relative-5xl: 104px;
+    --spacing-relative-6xl: 120px;
+    --spacing-relative-7xl: 160px;
+    --spacing-relative-8xl: 216px;
+  }
+
+  @media (min-width: 1920px) {
+    --spacing-relative-xl: 40px;
+    --spacing-relative-2xl: 72px;
+    --spacing-relative-3xl: 88px;
+    --spacing-relative-4xl: 104px;
+    --spacing-relative-5xl: 120px;
+    --spacing-relative-6xl: 136px;
+    --spacing-relative-7xl: 176px;
+    --spacing-relative-8xl: 248px;
+  }
+}
+
+
+
+/*=================================================
+|컴포넌트 별 css
+|START
+=================================================*/
+
+
+
+
+
+
+
+/*=================================================
+|컴포넌트 별 css
+|END
+=================================================*/
+
+.radius--img--wrap {
+  overflow: hidden;
+  border-radius: 20px;
+}
+
+// 딜러 검색 드롭다운 스타일
+.dealer--search--section {
+  width: 100%;
+  max-width: 1920px;
+  margin: 0 auto;
+  padding: 80px 96px;
+  background-color: hsla(216, 23%, 8%, 1);
+}
+
+.dealer--search--container {
+  display: flex;
+  flex-direction: column;
+  gap: 48px;
+  border-top: 1px solid rgba(252, 252, 253, 0.15);
+}
+
+.dealer--category--wrap {
+  display: flex;
+  flex-direction: column;
+}
+
+.dealer--category--title {
+  font-size: 28px;
+  font-weight: 600;
+  margin: 0 0 24px 0;
+  color: rgb(252, 252, 253);
+  padding-bottom: 16px;
+  border-bottom: 2px solid rgba(252, 252, 253, 0.3);
+}
+
+.dealer--region--item {
+  border-bottom: 1px solid rgba(252, 252, 253, 0.15);
+
+
+}
+
+.dealer--region--header {
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 24px;
+  background-color: transparent;
+  border: none;
+  color: rgb(252, 252, 253);
+  cursor: pointer;
+  transition: all 0.3s ease;
+
+  &:hover {
+    background-color: rgba(252, 252, 253, 0.05);
+  }
+
+  &.active {
+    background-color: rgba(252, 252, 253, 0.08);
+  }
+}
+
+.dealer--region--name {
+  font-size: 18px;
+  font-weight: 500;
+}
+
+.dealer--toggle--icon {
+  font-size: 28px;
+  line-height: 1;
+  transition: transform 0.3s ease;
+}
+
+.dealer--list--content {
+  padding: 0 24px 24px;
+  animation: slideDown 0.3s ease;
+}
+
+@keyframes slideDown {
+  from {
+    opacity: 0;
+    transform: translateY(-10px);
+  }
+
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.dealer--list {
+  list-style: none;
+  padding: 0;
+  padding-left: 10px;
+  margin: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 0px;
+
+  li {
+    margin: 0;
+    position: relative;
+
+    &:before {
+      content: '>';
+      font-size: 12px;
+      margin-right: 10px;
+    }
+
+    a {
+      text-decoration: underline;
+    }
+
+  }
+}
+
+.dealer--link {
+  display: inline-block;
+  color: rgba(252, 252, 253, 0.7);
+  text-decoration: none;
+  font-size: 16px;
+  transition: all 0.2s ease;
+  padding: 8px 0;
+
+  &:hover {
+    color: rgb(252, 252, 253);
+    text-decoration: underline;
+  }
+}
+
+// 반응형 처리
+@media (max-width: 1024px) {
+  .dealer--search--section {
+    padding: 60px 48px;
+  }
+
+  .dealer--search--container {
+    gap: 40px;
+  }
+
+  .dealer--category--title {
+    font-size: 24px;
+    margin-bottom: 20px;
+    padding-bottom: 12px;
+  }
+
+  .dealer--region--header {
+    padding: 16px 20px;
+  }
+
+  .dealer--region--name {
+    font-size: 16px;
+  }
+
+  .dealer--list--content {
+    padding: 0 20px 20px;
+  }
+
+  .dealer--list {
+    gap: 6px;
+  }
+}
+
+@media (max-width: 768px) {
+  .dealer--search--section {
+    padding: 40px 24px;
+  }
+
+  .dealer--search--container {
+    gap: 32px;
+  }
+
+  .dealer--category--title {
+    font-size: 20px;
+    margin-bottom: 16px;
+    padding-bottom: 10px;
+  }
+
+  .dealer--region--header {
+    padding: 14px 16px;
+  }
+
+  .dealer--region--name {
+    font-size: 15px;
+  }
+
+  .dealer--toggle--icon {
+    font-size: 24px;
+  }
+
+  .dealer--list--content {
+    padding: 0 16px 16px;
+  }
+
+  .dealer--list {
+    gap: 6px;
+  }
+
+  .dealer--link {
+    font-size: 14px;
+    padding: 6px 0;
+  }
+}
+
+
+
+// 딜러 검색 페이지 스타일
+.dealer--search--section {
+  padding: 64px 96px;
+
+  .dealer--search--container {
+    margin: 0 auto;
+  }
+
+  .dealer--category--wrap {
+    display: flex;
+    flex-direction: column;
+    //gap: 16px;
+  }
+
+  .dealer--region--item {
+    border-bottom: 1px solid rgba(252, 252, 253, 0.1);
+  }
+
+  .dealer--region--header {
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 24px 0;
+    background: transparent;
+    border: none;
+    color: #fcfcfd;
+    font-size: 24px;
+    font-family: 'FORDKOREAType';
+    font-weight: 400;
+    transition: all 0.3s;
+
+    &:hover {
+      color: rgba(252, 252, 253, 0.7);
+    }
+
+    &.active {
+      color: #fcfcfd;
+    }
+  }
+
+  .dealer--region--name {
+    font-size: 24px;
+  }
+
+  .dealer--toggle--icon {
+    font-size: 32px;
+    line-height: 1;
+    font-weight: 300;
+  }
+
+  .dealer--list--content {
+    padding-bottom: 24px;
+  }
+
+  .dealer--list {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+
+    >li {
+      list-style: none;
+    }
+  }
+
+  .dealer--link {
+    display: inline-block;
+    color: rgba(252, 252, 253, 0.7);
+    font-size: 16px;
+    text-decoration: underline;
+    text-underline-offset: 4px;
+    transition: all 0.3s;
+    background: transparent;
+    border: none;
+    font-family: 'FORDKOREAType';
+    font-weight: 400;
+    padding: 0;
+    cursor: pointer;
+
+    &:hover {
+      color: #fcfcfd;
+    }
+  }
+}
+
+// 딜러 팝업 스타일
+.dealer--popup--overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 9999;
+  padding: 20px;
+}
+
+.dealer--popup--container {
+  position: relative;
+  background-color: #ffffff;
+  border-radius: 20px;
+  max-width: 1024px;
+  width: 100%;
+  max-height: 90vh;
+  overflow-y: auto;
+  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.15);
+
+  &::-webkit-scrollbar {
+    width: 8px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: transparent;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: rgba(0, 0, 0, 0.2);
+    border-radius: 4px;
+
+    &:hover {
+      background: rgba(0, 0, 0, 0.3);
+    }
+  }
+
+  @media (min-width: 320px) {
+    padding: 16px;
+  }
+
+  @media (min-width: 375px) {
+    padding: 28px;
+  }
+
+  @media (min-width: 768px) {
+    padding: 48px;
+  }
+
+  @media (min-width: 1024px) {
+    padding: 56px;
+  }
+
+  @media (min-width: 1440px) {
+    padding: 64px;
+  }
+}
+
+.dealer--popup--close {
+  position: absolute;
+  top: 24px;
+  right: 24px;
+  width: 40px;
+  height: 40px;
+  background: transparent;
+  border: none;
+  cursor: pointer;
+  z-index: 1;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+  transition: all 0.3s;
+
+  &:hover {
+    background-color: rgba(0, 0, 0, 0.05);
+  }
+
+  .close--icon {
+    width: 24px;
+    height: 24px;
+    position: relative;
+
+    &::before,
+    &::after {
+      content: '';
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      width: 20px;
+      height: 2px;
+      background-color: #333333;
+    }
+
+    &::before {
+      transform: translate(-50%, -50%) rotate(45deg);
+    }
+
+    &::after {
+      transform: translate(-50%, -50%) rotate(-45deg);
+    }
+  }
+}
+
+.dealer--popup--content {
+  margin: auto;
+  max-width: var(--max-content-width);
+  display: grid;
+  grid-template-columns: 1fr;
+  grid-template-rows: 1fr;
+  grid-template-areas: "media" "textarea";
+  padding: var(--spacing-relative-xl) var(--spacing-relative-md);
+
+
+  @media (min-width: 1024px) {
+    grid-template-columns: 1fr 1fr;
+    grid-template-areas: "media textarea";
+  }
+
+  @media (min-width: 1440px) {
+    grid-template-columns: 1fr 1fr;
+    grid-template-areas: "media textarea";
+  }
+
+
+
+  .dealer--thumb--wrap {
+    border-radius: 20px;
+    overflow: hidden;
+  }
+
+  .dealer--infos--wrap {
+    grid-area: textarea;
+    align-self: center;
+
+    @media screen and (min-width: 320px) {
+      padding-block-start: var(--spacing-relative-lg);
+    }
+
+    @media screen and (min-width: 1024px) {
+      display: flex;
+      flex-direction: column;
+      -webkit-box-pack: center;
+      justify-content: center;
+      align-items: flex-start;
+      padding-inline-start: var(--spacing-relative-2xl);
+      padding-inline-end: var(--spacing-relative-2xl);
+      padding-block-start: 0px;
+    }
+
+
+  }
+}
+
+.dealer--popup--header {
+  .dealer--name {
+    margin: 0px;
+    color: #1a1a1a;
+    font-family: FORDKOREAType, sans-serif;
+    letter-spacing: 0px;
+    font-weight: 400;
+    text-decoration: none;
+    font-size: 24px;
+    line-height: 36px;
+    font-stretch: 130%;
+
+    @media (min-width: 1024px) {
+      font-size: 28px;
+      line-height: 40px;
+    }
+
+    @media (min-width: 1440px) {
+      font-size: 32px;
+      line-height: 44px;
+    }
+
+    @media (min-width: 1920px) {
+      font-size: 36px;
+      line-height: 52px;
+    }
+
+
+  }
+
+  .dealer--type {
+    display: inline-block;
+    font-size: 14px;
+    color: #666666;
+    padding: 4px 12px;
+    background-color: rgba(0, 0, 0, 0.05);
+    border-radius: 100px;
+  }
+}
+
+.dealer--popup--body {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  padding-top: 8px;
+}
+
+.dealer--info--section {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+
+  .info--label {
+    margin: 0px;
+    color: #666666;
+    font-family: FORDKOREAType, sans-serif;
+    letter-spacing: 0px;
+    font-weight: 400;
+    text-decoration: none;
+    font-size: 16px;
+    line-height: 24px;
+    font-stretch: 105%;
+
+    a {
+      word-break: break-all;
+    }
+  }
+
+  .phone--link,
+  .email--link,
+  .website--link {
+    color: #2563eb;
+    text-decoration: underline;
+    text-underline-offset: 4px;
+    transition: all 0.3s;
+
+    &:hover {
+      color: #1d4ed8;
+    }
+  }
+
+  .business--hours--item {
+    line-height: 1.8;
+  }
+}
+
+.dealer--popup--footer {
+  margin-top: 40px;
+  padding-top: 24px;
+  border-top: 1px solid #e0e0e0;
+  display: flex;
+  justify-content: center;
+}
+
+.dealer--popup--empty {
+  padding: 80px 48px;
+  text-align: center;
+
+  >p {
+    font-size: 16px;
+    color: #666666;
+  }
+}
+
+// 팝업 페이드 애니메이션
+.dealer--popup--fade-enter-active,
+.dealer--popup--fade-leave-active {
+  transition: opacity 0.3s ease;
+
+  .dealer--popup--container {
+    transition: transform 0.3s ease, opacity 0.3s ease;
+  }
+}
+
+.dealer--popup--fade-enter-from,
+.dealer--popup--fade-leave-to {
+  opacity: 0;
+
+  .dealer--popup--container {
+    transform: scale(0.95);
+    opacity: 0;
+  }
+}
+
+.dealer--popup--fade-enter-to,
+.dealer--popup--fade-leave-from {
+  opacity: 1;
+
+  .dealer--popup--container {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+
+
+
+
+
+/*=================================================
+|버튼 리스트 css
+|end
+=================================================*/
+
+
+header {
+  width: 100%;
+  position: sticky;
+  top: 0px;
+  z-index: 1000;
+  background-color: #ffffff;
+  border-bottom: 1px solid #E9E9E9;
+
+  .header--wrap {
+    max-width: 1440px;
+    // padding:25px 0px;
+    background-color: #fff;
+    align-items: center;
+    margin: 0 auto;
+    display: flex;
+    justify-content: space-between;
+    position: relative;
+    z-index: 1001;
+
+    nav {
+      display: flex;
+      align-items: center;
+
+      >ul {
+        display: flex;
+        align-items: center;
+
+        >li {
+          width: 200px;
+          white-space: nowrap;
+          height: 90px;
+          display: flex;
+          position: relative;
+          align-items: center;
+          justify-content: center;
+          cursor: pointer;
+
+          >a {
+            width: 100%;
+            color: #222;
+            text-align: center;
+            // padding: 0 60px;
+            display: inline-block;
+            font-size: 16px;
+            font-style: normal;
+            font-weight: 700 !important;
+            text-transform: uppercase;
+            transition: color 0.3s;
+
+            &:hover {
+              color: #076FED;
+            }
+          }
+
+
+          &:hover {
+            .sub--menu--wrap {
+              max-height: 550px;
+            }
+          }
+
+          .sub--menu--wrap {
+            max-height: 0px;
+            overflow: hidden;
+            transition: max-height 0.3s;
+            background-color: #fff;
+            top: 90.5px;
+
+            &.sub1 {
+              position: fixed;
+              border-bottom-left-radius: 30px;
+              border-bottom-right-radius: 30px;
+              max-width: 1440px;
+              left: 50%;
+              transform: translateX(-50%);
+              width: 100%;
+
+              >div {
+                padding: 60px 0;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                gap: 70px;
+
+                &.active {
+                  max-height: 300px;
+                }
+
+                li {
+                  a {
+                    display: flex;
+                    gap: 25px;
+                    flex-direction: column;
+                    max-width: 180px;
+                    text-align: center;
+                    color: #000;
+                    font-size: 16px;
+                    font-weight: 500;
+                    text-transform: uppercase;
+
+                    img {
+                      transition: all 0.3s;
+                    }
+
+                    &:hover {
+                      color: #076fed;
+
+                      img {
+                        transform: scale(1.1);
+                      }
+                    }
+                  }
+                }
+              }
+            }
+
+            &.sub2 {
+              position: absolute;
+              border-bottom-left-radius: 20px;
+              border-bottom-right-radius: 20px;
+              width: 280px;
+              left: calc(50% - 140px);
+
+              >div {
+                padding: 20px 40px;
+                display: flex;
+                flex-direction: column;
+                //height: 550px;
+                line-height: 1;
+
+                li {
+                  a {
+                    cursor: pointer;
+                    color: #000;
+                    padding: 20px 0;
+                    font-size: 16px;
+                    font-weight: 500;
+                    display: flex;
+                    justify-content: space-between;
+                    align-items: center;
+                    transition: color 0.3s;
+
+                    &:hover {
+                      color: #076fed;
+                    }
+                  }
+                }
+              }
+            }
+
+            &.sub3 {
+              position: absolute;
+              border-bottom-left-radius: 20px;
+              border-bottom-right-radius: 20px;
+              width: 280px;
+              left: calc(50% - 140px);
+              //left: -32px;
+              transition: all 0.3s;
+
+              &:has(.active) {
+                width: 630px;
+              }
+
+              &.h--200 {
+                >div {
+                  height: 320px;
+                }
+              }
+
+              &.h--400 {
+                >div {
+                  height: 432px;
+                }
+              }
+
+              >div {
+                height: 375px;
+                // max-height: 550px;
+                background-color: #fff;
+                position: relative;
+
+                .sub--menu--left {
+                  width: 280px;
+                  padding: 20px 40px;
+                  line-height: 1;
+
+                  li {
+                    padding: 20px 0;
+                    cursor: pointer;
+                    color: #000;
+                    font-size: 16px;
+                    font-weight: 500;
+                    display: flex;
+                    justify-content: space-between;
+                    align-items: center;
+                    transition: color 0.3s;
+
+                    &:hover,
+                    &.active {
+                      color: #076fed;
+
+                      .ico {
+                        background-image: url(/img/ico--header--arrow--blue.svg);
+                      }
+                    }
+
+                    .ico {
+                      width: 18px;
+                      background-image: url(/img/ico--header--arrow.svg);
+                      height: 18px;
+                    }
+                  }
+                }
+
+                .sub--menu--right {
+                  position: absolute;
+                  left: 280px;
+                  border-left: 1px solid #E5E5E5;
+                  width: 350px;
+                  padding: 40px 35px;
+                  height: 100%;
+                  top: 0;
+
+                  >div {
+                    opacity: 0;
+                    transition: opacity 0.3s;
+                    display: none;
+                    pointer-events: none;
+                    flex-direction: column;
+                    gap: 40px;
+
+                    li {
+                      line-height: 1;
+
+                      a {
+                        display: inline-block;
+                        color: #333;
+                        font-size: 16px;
+                        font-weight: 400;
+                        width: 100%;
+                        transition: all 0.3s;
+
+                        &:hover {
+                          color: #076fed;
+                        }
+                      }
+                    }
+
+                    &.active {
+                      display: flex;
+                      pointer-events: all;
+                      opacity: 1;
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+}
+
+footer {
+  width: 100%;
+  //--one-footer-color-black: hsla(216, 26%, 1%, 1);
+  --one-footer-color-white: hsla(216, 33%, 99%, 1);
+  --one-footer-neutral-5: hsla(216, 26%, 1%, 0.6);
+  --one-footer-neutral-10: hsla(216, 17%, 26%, 1);
+  --one-footer-neutral-20: hsla(216, 14%, 35%, 1);
+  --one-footer-neutral-70: hsla(216, 33%, 99%, 0.6);
+  --one-footer-side-spacing: 16px;
+  --one-footer-space-xs: var(--spacing-relative-xs);
+  --one-footer-space-s: var(--spacing-relative-sm);
+  --one-footer-space-m: var(--spacing-relative-md);
+  --one-footer-space-l: var(--spacing-relative-lg);
+  --one-footer-space-xl: var(--spacing-relative-xl);
+  --one-footer-space-xxl: var(--spacing-relative-2xl);
+  --one-footer-space-xxxl: var(--spacing-relative-3xl);
+  background: var(--one-footer-color-black);
+  box-sizing: border-box;
+  color: var(--one-footer-color-white);
+
+
+  @media (min-width: 375px) {
+    --one-footer-side-spacing: 28px;
+  }
+
+  @media (min-width: 768px) {
+    --one-footer-side-spacing: 40px;
+  }
+
+  @media (min-width: 1024px) {
+    --one-footer-side-spacing: 60px;
+  }
+
+  .footer--wrap {
+    max-width: 1440px;
+    width: 100%;
+    margin: 0 auto;
+    display: flex;
+    flex-direction: column;
+
+    .footer--site--map {
+      width: 100%;
+      max-width: 1440px;
+      margin: 0 auto;
+      padding: 70px 0px 40px;
+
+      >ul {
+        display: block;
+        list-style: none;
+        row-gap: 40px;
+        padding: 0px;
+        width: 100%;
+
+        @media (min-width: 768px) {
+          display: flex;
+          flex-flow: wrap;
+          width: 100%;
+        }
+
+        &.lincoln {
+          >li {
+            @media (min-width: 1440px) {
+              //width: calc(100% / 5);
+            }
+          }
+        }
+
+        >li {
+          margin-bottom: -1px;
+
+
+          @media (min-width: 768px) {
+            //margin: 0px 24px 40px 0px;            
+            width: calc(33.333%);
+
+
+            &:first-of-type {
+              margin-top: 0px;
+            }
+
+            >h2 {
+              border: none !important;
+            }
+
+            >ul {
+              >h3 {
+                color: #E9E9E9;
+                font-size: 14px;
+                font-style: normal;
+                font-weight: 500;
+                line-height: 100%;
+                /* 14px */
+                text-transform: uppercase;
+                display: flex;
+              }
+
+              >li {
+                padding-left: 0;
+                padding-right: 0;
+                margin-bottom: 25px;
+
+                a {
+                  color: #E9E9E9;
+                  font-size: 14px;
+                  font-style: normal;
+                  font-weight: 500;
+                  line-height: 100%;
+                  /* 14px */
+                  text-transform: uppercase;
+                  display: flex;
+                }
+              }
+            }
+          }
+
+
+          @media (min-width: 1440px) {
+            width: calc(100% / 6);
+          }
+
+          >h2 {
+            color: var(--one-footer-color-white);
+            display: flex;
+            flex-flow: row;
+            -webkit-box-pack: justify;
+            justify-content: space-between;
+            padding: var(--one-footer-space-m) 20px;
+            width: 100%;
+            margin: 0px;
+            color: #FFF;
+            font-size: 18px;
+            font-style: normal;
+            font-weight: 700;
+            line-height: 100%;
+            /* 18px */
+            text-transform: uppercase;
+            text-decoration: none;
+            border-top: 1px solid #4A4A4A;
+            border-bottom: 1px solid #4A4A4A;
+
+
+            @media (min-width: 768px) {
+              padding: 0px;
+              width: auto;
+              margin-bottom: 30px;
+            }
+
+
+            @media (min-width: 1440px) {
+              font-size: 18px;
+              line-height: 28px;
+            }
+
+            @media (min-width: 1920px) {
+              font-size: 20px;
+              line-height: 32px;
+            }
+          }
+
+        }
+      }
+    }
+  }
+}
+
+// ============================================
+// Admin Panel Dark Theme Styles
+// ============================================
+
+// Light Theme Color Variables
+:root {
+  --admin-yellow: #e8b546;
+  --admin-bg-primary: #f5f5f5;
+  --admin-bg-secondary: #ffffff;
+  --admin-bg-tertiary: #f6f7f8;
+  --admin-text-primary: #181d29;
+  --admin-text-secondary: #666b75;
+  --admin-text-muted: #999999;
+  --admin-border-color: #e0e0e0;
+  --admin-accent-primary: #1A2332;
+  --admin-accent-hover: #1A2332;
+  --admin-success: #10b981;
+  --admin-warning: #f59e0b;
+  --admin-error: #ef4444;
+  --admin-shadow: rgba(0, 0, 0, 0.1);
+}
+
+// Admin Login Page
+.admin--login {
+  min-height: 100vh;
+  width: 100%;
+  min-width: 1680px;
+  background: var(--admin-bg-primary);
+
+  .login--container {
+    display: flex;
+    width: 100%;
+    min-width: 1680px;
+    min-height: 100vh;
+  }
+
+  // 왼쪽 영역 (50%) - 로고 + 설명
+  .login--layout {
+    flex: 0 0 50%;
+    max-width: 50%;
+    min-height: 100vh;
+    background: #1A2332;
+    color: #fcfcfd;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 80px 96px;
+    position: relative;
+    overflow: hidden;
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: -200px;
+      right: -200px;
+      width: 600px;
+      height: 600px;
+      border-radius: 50%;
+      opacity: 0.08;
+      background: var(--admin-yellow);
+      pointer-events: none;
+    }
+
+    &::after {
+      content: '';
+      position: absolute;
+      bottom: 100px;
+      left: 80px;
+      width: 300px;
+      height: 300px;
+      border-radius: 50%;
+      background: #e85c3f;
+      opacity: 0.08;
+      pointer-events: none;
+    }
+
+    .login--layout-inner {
+      position: relative;
+      z-index: 1;
+      width: 100%;
+      max-width: 560px;
+      display: flex;
+      flex-direction: column;
+      gap: 48px;
+    }
+
+    .login--brand {
+      display: flex;
+      gap: 16px;
+      align-items: center;
+      .login--brand--logo{
+        width: 56px;
+        height: 56px;
+        border-radius: 12px;
+        background-color: var(--admin-yellow);
+        font-size: 28px;
+        text-align: center;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+      .login--brand-title {
+        font-size: 22px;
+        font-weight: 600;
+        color: #fcfcfd;
+        letter-spacing: -0.11px;
+      }
+
+      .login--brand-tagline {
+        font-size: 10px;
+        letter-spacing: 1px;
+        text-transform: uppercase;
+        color: var(--admin-yellow);
+        margin: 0;
+        font-weight: 500;
+      }
+    }
+
+    .login--deco--line{
+      position: relative;
+      width: 240px;
+      height: 240px;
+      margin: 0 auto;
+      .circle1{
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        border-radius: 50%;
+        border: 1.5px solid var(--admin-yellow);
+        opacity: 0.3;
+      }
+      .circle2{
+        position: absolute;
+        width: 160px;
+        height: 160px;
+        top: calc(50% - 80px);
+        left: calc(50% - 80px);
+        border-radius: 50%;
+        border: 1px solid var(--admin-yellow);
+        opacity: 0.3;
+      }
+      .circle3{
+        font-size: 100px;
+        width: 160px;
+        height: 160px;
+        position: absolute;
+        top: calc(50% - 80px);
+        left: calc(50% - 80px);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+    }
+
+    .login--intro {
+      .login--intro-title {
+        font-size: 36px;
+        font-weight: 600;
+        color: #fff;
+      }
+
+      .login--intro-desc {
+        font-size: 14px;
+        letter-spacing: 2.1px;
+        line-height: 1.7;
+        margin-bottom: 15px;
+        color: var(--admin-yellow);
+      }
+
+      .login--intro-list {
+        color: #b2bdcc;
+        font-size: 13px;
+        font-weight: 400;
+      }
+    }
+
+    .login--copyright {
+      font-size: 13px;
+      color: rgba(252, 252, 253, 0.4);
+      letter-spacing: 0.5px;
+    }
+  }
+
+  // 오른쪽 영역 (50%) - 로그인 폼
+  .login--panel {
+    flex: 0 0 50%;
+    max-width: 50%;
+    min-height: 100vh;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 80px 96px;
+    background: var(--admin-bg-secondary);
+  }
+
+  .login--box {
+    width: 100%;
+    max-width: 480px;
+    background: var(--admin-bg-secondary);
+    padding: 0;
+    border: none;
+    box-shadow: none;
+    border-radius: 0;
+  }
+
+  .login--logo {
+    margin-bottom: 48px;
+
+    > span{
+      color: var(--admin-yellow);
+      font-size: 12px;
+      font-weight: 700;
+      letter-spacing: 0.96px;
+    }
+
+    h1 {
+      font-size: 40px;
+      font-weight: 700;
+      color: var(--admin-text-primary);
+      margin: 0 0 8px 0;
+    }
+
+    .subtitle {
+      font-size: 14px;
+      color: var(--admin-text-secondary);
+      margin: 0;
+    }
+  }
+
+  .login--error {
+    background: rgba(239, 68, 68, 0.1);
+    border: 1px solid var(--admin-error);
+    color: var(--admin-error);
+    padding: 12px 16px;
+    border-radius: 6px;
+    margin-bottom: 20px;
+    font-size: 14px;
+  }
+
+  .login--form {
+    .form--group {
+      margin-bottom: 20px;
+    }
+
+    .form--input {
+      width: 100%;
+      padding: 14px 16px;
+      background: var(--admin-bg-tertiary);
+      border: 1px solid var(--admin-border-color);
+      border-radius: 6px;
+      color: var(--admin-text-primary);
+      font-size: 14px;
+      transition: all 0.3s ease;
+      font-family: 'FORDKOREAType', sans-serif;
+
+      &:focus {
+        outline: none;
+        border-color: var(--admin-accent-primary);
+        box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
+      }
+
+      &::placeholder {
+        color: var(--admin-text-muted);
+      }
+
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+      }
+    }
+
+    .form--options {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 24px;
+
+      .checkbox--label {
+        display: flex;
+        align-items: center;
+        cursor: pointer;
+        font-size: 14px;
+        color: var(--admin-text-secondary);
+
+        input[type="checkbox"] {
+          margin-right: 8px;
+          cursor: pointer;
+        }
+      }
+
+      .forgot--password {
+        font-size: 14px;
+        color: var(--admin-accent-primary);
+        text-decoration: none;
+        transition: color 0.3s ease;
+
+        &:hover {
+          color: var(--admin-accent-hover);
+        }
+      }
+    }
+
+    .login--button {
+      width: 100%;
+      height: 56px;
+      line-height: 56px;
+      background: var(--admin-accent-primary);
+      color: var(--admin-yellow);
+      border: none;
+      border-radius: 10px;
+      font-size: 15px;
+      font-weight: 600;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      font-family: 'FORDKOREAType', sans-serif;
+
+      &:hover:not(:disabled) {
+        background: var(--admin-accent-hover);
+        transform: translateY(-2px);
+        box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
+      }
+
+      &:active:not(:disabled) {
+        transform: translateY(0);
+      }
+
+      &:disabled {
+        opacity: 0.6;
+        cursor: not-allowed;
+      }
+    }
+  }
+
+  .login--footer {
+    margin-top: 32px;
+    text-align: center;
+
+    p {
+      font-size: 12px;
+      color: var(--admin-text-muted);
+      margin: 0;
+    }
+  }
+}
+
+// Admin Layout
+.admin--layout {
+  min-height: 100vh;
+  background: var(--admin-bg-primary);
+  font-family: 'FORDKOREAType', sans-serif;
+}
+
+// Admin Header
+.admin--header {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 64px;
+  background: var(--admin-bg-secondary);
+  border-bottom: 1px solid var(--admin-border-color);
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 32px;
+  z-index: 1000;
+
+  .admin--header-left {
+    display: flex;
+    align-items: center;
+  }
+
+  .admin--logo {
+    display: flex;
+    align-items: baseline;
+    gap: 12px;
+
+    h1 {
+      font-size: 28px;
+      font-weight: 700;
+      color: var(--admin-text-primary);
+      margin: 0;
+      letter-spacing: 2px;
+    }
+
+    .admin--logo-sub {
+      font-size: 16px;
+      color: var(--admin-text-secondary);
+      font-weight: 400;
+    }
+  }
+
+  .admin--header-right {
+    display: flex;
+    gap: 12px;
+  }
+
+  .admin--header-btn {
+    padding: 8px 20px;
+    background: var(--admin-bg-tertiary);
+    color: var(--admin-text-primary);
+    border: 1px solid var(--admin-border-color);
+    border-radius: 6px;
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    font-family: 'FORDKOREAType', sans-serif;
+
+    &:hover {
+      background: var(--admin-bg-primary);
+      border-color: var(--admin-accent-primary);
+    }
+
+    &.admin--header-btn-logout {
+      background: var(--admin-accent-primary);
+      border-color: var(--admin-accent-primary);
+      color: #ffffff !important;
+
+      &:hover {
+        background: var(--admin-accent-hover);
+        border-color: var(--admin-accent-hover);
+        color: #ffffff !important;
+      }
+    }
+  }
+}
+
+// Admin Content Wrapper
+.admin--content-wrapper {
+  display: flex;
+  margin-top: 64px;
+  min-height: calc(100vh - 64px);
+}
+
+// Admin Sidebar
+.admin--sidebar {
+  width: 260px;
+  background: var(--admin-bg-secondary);
+  border-right: 1px solid var(--admin-border-color);
+  padding: 24px 0;
+  position: fixed;
+  left: 0;
+  top: 64px;
+  bottom: 0;
+  overflow-y: auto;
+
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: var(--admin-bg-primary);
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: var(--admin-border-color);
+    border-radius: 3px;
+
+    &:hover {
+      background: var(--admin-text-muted);
+    }
+  }
+}
+
+// Admin GNB
+.admin--gnb {
+  .admin--gnb-group {
+    margin-bottom: 8px;
+  }
+
+  .admin--gnb-title {
+    padding: 12px 24px;
+    font-size: 14px;
+    font-weight: 600;
+    color: var(--admin-text-secondary);
+    cursor: pointer;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    transition: all 0.3s ease;
+    user-select: none;
+
+    &:hover {
+      color: var(--admin-text-primary);
+      background: var(--admin-bg-primary);
+    }
+
+    .admin--gnb-arrow {
+      font-size: 10px;
+      transition: transform 0.3s ease;
+
+      &.is-open {
+        transform: rotate(-180deg);
+      }
+    }
+  }
+
+  .admin--gnb-submenu {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+  }
+
+  .admin--gnb-item {
+    &.is-active {
+      .admin--gnb-link {
+        color: var(--admin-accent-primary);
+        background: rgba(37, 99, 235, 0.1);
+        border-left: 3px solid var(--admin-accent-primary);
+      }
+    }
+  }
+
+  .admin--gnb-link {
+    display: block;
+    padding: 10px 24px 10px 40px;
+    font-size: 14px;
+    color: var(--admin-text-secondary);
+    text-decoration: none;
+    transition: all 0.3s ease;
+    border-left: 3px solid transparent;
+
+    &:hover {
+      color: var(--admin-text-primary);
+      background: var(--admin-bg-primary);
+    }
+  }
+}
+
+// Submenu Animation
+.admin--submenu-enter-active,
+.admin--submenu-leave-active {
+  transition: all 0.3s ease;
+  overflow: hidden;
+}
+
+.admin--submenu-enter-from,
+.admin--submenu-leave-to {
+  opacity: 0;
+  max-height: 0;
+}
+
+.admin--submenu-enter-to,
+.admin--submenu-leave-from {
+  opacity: 1;
+  max-height: 500px;
+}
+
+// Admin Main Content
+.admin--main {
+  flex: 1;
+  margin-left: 260px;
+  padding: 32px;
+  background: var(--admin-bg-primary);
+}
+
+// Admin Page Header
+.admin--page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 32px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid var(--admin-border-color);
+}
+
+.admin--page-title {
+  font-size: 28px;
+  font-weight: 700;
+  color: var(--admin-text-primary);
+  margin: 0;
+}
+
+// Admin Breadcrumb
+.admin--breadcrumb {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+
+  .admin--breadcrumb-link {
+    color: var(--admin-text-secondary);
+    text-decoration: none;
+    transition: color 0.3s ease;
+
+    &:hover {
+      color: var(--admin-accent-primary);
+    }
+  }
+
+  .admin--breadcrumb-current {
+    color: var(--admin-text-primary);
+    font-weight: 500;
+  }
+
+  .admin--breadcrumb-separator {
+    color: var(--admin-text-muted);
+  }
+}
+
+// Admin Page Content
+.admin--page-content {
+  background: var(--admin-bg-secondary);
+  border-radius: 8px;
+  padding: 24px;
+  border: 1px solid var(--admin-border-color);
+}
+
+// Admin Dashboard
+.admin--dashboard {
+  .admin--dashboard-welcome {
+    margin-bottom: 32px;
+
+    h3 {
+      font-size: 24px;
+      color: var(--admin-text-primary);
+      margin: 0 0 8px 0;
+    }
+
+    p {
+      font-size: 14px;
+      color: var(--admin-text-secondary);
+      margin: 0;
+    }
+  }
+
+  .admin--dashboard-stats {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+    gap: 20px;
+    margin-bottom: 32px;
+  }
+
+  .admin--stat-card {
+    background: var(--admin-bg-tertiary);
+    border: 1px solid var(--admin-border-color);
+    border-radius: 8px;
+    padding: 24px;
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    transition: all 0.3s ease;
+
+    &:hover {
+      border-color: var(--admin-accent-primary);
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px var(--admin-shadow);
+    }
+
+    .admin--stat-icon {
+      font-size: 36px;
+    }
+
+    .admin--stat-content {
+      flex: 1;
+
+      h4 {
+        font-size: 14px;
+        color: var(--admin-text-secondary);
+        margin: 0 0 8px 0;
+        font-weight: 500;
+      }
+
+      .admin--stat-number {
+        font-size: 28px;
+        color: var(--admin-text-primary);
+        margin: 0;
+        font-weight: 700;
+      }
+    }
+  }
+
+  .admin--dashboard-recent {
+    h4 {
+      font-size: 18px;
+      color: var(--admin-text-primary);
+      margin: 0 0 16px 0;
+    }
+
+    .admin--recent-list {
+      background: var(--admin-bg-tertiary);
+      border: 1px solid var(--admin-border-color);
+      border-radius: 8px;
+      padding: 24px;
+
+      .admin--no-data {
+        text-align: center;
+        color: var(--admin-text-muted);
+        margin: 0;
+      }
+    }
+  }
+}
+
+// Admin Form Styles
+.admin--form {
+  max-width: 800px;
+
+  .admin--form-group {
+    margin-bottom: 24px;
+  }
+
+  .admin--form-label {
+    display: block;
+    font-size: 14px;
+    font-weight: 500;
+    color: var(--admin-text-primary);
+    margin-bottom: 8px;
+
+    .admin--required {
+      color: var(--admin-error);
+      margin-left: 4px;
+    }
+  }
+
+  .admin--form-input,
+  .admin--form-textarea,
+  .admin--form-select {
+    width: 100%;
+    padding: 12px 16px;
+    background: var(--admin-bg-tertiary);
+    border: 1px solid var(--admin-border-color);
+    border-radius: 6px;
+    color: var(--admin-text-primary);
+    font-size: 14px;
+    font-family: 'FORDKOREAType', sans-serif;
+    transition: all 0.3s ease;
+
+    &:focus {
+      outline: none;
+      border-color: var(--admin-accent-primary);
+      box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
+    }
+
+    &::placeholder {
+      color: var(--admin-text-muted);
+    }
+
+    &:disabled {
+      opacity: 0.5;
+      cursor: not-allowed;
+    }
+  }
+
+  .admin--form-textarea {
+    min-height: 120px;
+    resize: vertical;
+  }
+
+  .admin--form-select {
+    cursor: pointer;
+  }
+
+  // 다중 입력 필드
+  .admin--multi-input-wrapper {
+    .admin--multi-input-item {
+      display: flex;
+      gap: 12px;
+      margin-bottom: 12px;
+      align-items: flex-start;
+
+      .admin--sender-row {
+        display: flex;
+        gap: 10px;
+        flex: 1;
+      }
+
+      .admin--form-input {
+        flex: 1;
+      }
+
+      .admin--btn-remove {
+        padding: 12px 20px;
+        background: var(--admin-error);
+        color: #fff;
+        border: none;
+        border-radius: 6px;
+        font-size: 14px;
+        font-weight: 500;
+        cursor: pointer;
+        transition: all 0.3s ease;
+        white-space: nowrap;
+        font-family: 'FORDKOREAType', sans-serif;
+
+        &:hover {
+          background: #dc2626;
+        }
+      }
+    }
+
+    .admin--btn-add {
+      padding: 10px 20px;
+      background: var(--admin-bg-tertiary);
+      color: var(--admin-text-secondary);
+      border: 1px dashed var(--admin-border-color);
+      border-radius: 6px;
+      font-size: 14px;
+      font-weight: 500;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      font-family: 'FORDKOREAType', sans-serif;
+
+      &:hover {
+        background: var(--admin-bg-primary);
+        border-color: var(--admin-accent-primary);
+        color: var(--admin-text-primary);
+      }
+    }
+  }
+
+  // 폼 액션 버튼
+  .admin--form-actions {
+    display: flex;
+    gap: 12px;
+    margin-top: 32px;
+    padding-top: 24px;
+    border-top: 1px solid var(--admin-border-color);
+  }
+
+  .admin--btn {
+    padding: 12px 32px;
+    border: none;
+    border-radius: 6px;
+    font-size: 14px;
+    font-weight: 600;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    font-family: 'FORDKOREAType', sans-serif;
+
+    &:disabled {
+      opacity: 0.5;
+      cursor: not-allowed;
+    }
+  }
+
+  .admin--btn-primary {
+    background: var(--admin-accent-primary);
+    color: #ffffff;
+
+    &:hover:not(:disabled) {
+      background: var(--admin-accent-hover);
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
+    }
+  }
+
+  .admin--btn-secondary {
+    background: var(--admin-bg-tertiary);
+    color: var(--admin-text-primary);
+    border: 1px solid var(--admin-border-color);
+
+    &:hover:not(:disabled) {
+      background: var(--admin-bg-primary);
+      border-color: var(--admin-accent-primary);
+    }
+  }
+
+  // 알림 메시지
+  .admin--alert {
+    padding: 12px 16px;
+    border-radius: 6px;
+    margin-top: 20px;
+    font-size: 14px;
+  }
+
+  .admin--alert-success {
+    background: rgba(16, 185, 129, 0.1);
+    border: 1px solid var(--admin-success);
+    color: var(--admin-success);
+  }
+
+  .admin--alert-error {
+    background: rgba(239, 68, 68, 0.1);
+    border: 1px solid var(--admin-error);
+    color: var(--admin-error);
+  }
+
+  .admin--alert-warning {
+    background: rgba(245, 158, 11, 0.1);
+    border: 1px solid var(--admin-warning);
+    color: var(--admin-warning);
+  }
+}
+
+// 로딩
+.admin--loading {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 60px 20px;
+  color: var(--admin-text-secondary);
+  font-size: 16px;
+}
+
+// 전역 로딩 오버레이
+.admin--loading-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 99999;
+}
+
+// 스피너
+.admin--spinner {
+  width: 50px;
+  height: 50px;
+  border: 4px solid rgba(255, 255, 255, 0.3);
+  border-top-color: #fff;
+  border-radius: 50%;
+  animation: admin-spin 0.8s linear infinite;
+}
+
+@keyframes admin-spin {
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+// 검색 박스
+.admin--search-box {
+  background: var(--admin-bg-secondary);
+  border: 1px solid var(--admin-border-color);
+  border-radius: 8px;
+  padding: 20px;
+  margin-bottom: 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 20px;
+
+  .admin--search-form {
+    display: flex;
+    gap: 12px;
+    flex: 1;
+  }
+
+  // 검색 박스 내 모든 select, input 통일된 스타일
+  .admin--search-select,
+  .admin--form-select,
+  .admin--form-input,
+  .admin--search-input {
+    border: 1px solid var(--admin-border-color) !important;
+    border-radius: 4px;
+    height: 33px !important;
+    padding: 6px 14px !important;
+    font-size: 13px !important;
+    background: var(--admin-bg-tertiary) !important;
+    line-height: normal;
+  }
+
+  .admin--search-select,
+  .admin--form-select {
+    width: 140px;
+  }
+
+  .admin--form-input,
+  .admin--search-input {
+    flex: 1;
+    max-width: 400px;
+  }
+}
+
+// 날짜 입력 필드
+.admin--date-input {
+  cursor: pointer;
+
+  &::-webkit-calendar-picker-indicator {
+    cursor: pointer;
+    opacity: 0.7;
+    transition: opacity 0.2s;
+
+    &:hover {
+      opacity: 1;
+    }
+  }
+}
+
+// 날짜 범위
+.admin--date-range {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+
+  .admin--date-separator {
+    color: var(--admin-text-secondary);
+    font-size: 14px;
+  }
+
+  .admin--form-input {
+    flex: 1;
+    max-width: 200px;
+  }
+}
+
+// 검색 input 강제 스타일 (두 클래스 함께 사용될 때)
+.admin--form-input.admin--search-input {
+  border: 1px solid var(--admin-border-color) !important;
+  background: var(--admin-bg-tertiary) !important;
+}
+
+// 테이블
+.admin--table-wrapper {
+  background: var(--admin-bg-secondary);
+  border: 1px solid var(--admin-border-color);
+  border-radius: 8px;
+  overflow-x: auto;
+}
+
+.admin--table {
+  width: 100%;
+  border-collapse: collapse;
+
+  thead {
+    background: var(--admin-bg-tertiary);
+    border-bottom: 1px solid var(--admin-border-color);
+
+    th {
+      padding: 14px 16px;
+      text-align: left;
+      font-size: 14px;
+      font-weight: 600;
+      color: var(--admin-text-primary);
+      white-space: nowrap;
+    }
+  }
+
+  tbody {
+    tr {
+      border-bottom: 1px solid var(--admin-border-color);
+      transition: background 0.3s ease;
+
+      &:hover {
+        background: var(--admin-bg-tertiary);
+      }
+
+      &:last-child {
+        border-bottom: none;
+      }
+    }
+
+    td {
+      padding: 14px 16px;
+      font-size: 14px;
+      color: var(--admin-text-secondary);
+      vertical-align: middle;
+    }
+
+    .admin--table-title {
+      color: var(--admin-text-primary);
+      font-weight: 500;
+      max-width: 300px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    .admin--table-loading,
+    .admin--table-empty {
+      text-align: center;
+      padding: 60px 20px;
+      color: var(--admin-text-muted);
+    }
+  }
+
+  .admin--table-actions {
+    display: flex;
+    gap: 8px;
+  }
+}
+
+// 배지
+.admin--badge {
+  display: inline-block;
+  padding: 4px 12px;
+  border-radius: 12px;
+  font-size: 12px;
+  font-weight: 600;
+  white-space: nowrap;
+
+  &.admin--badge-html {
+    background: rgba(59, 130, 246, 0.1);
+    color: #3b82f6;
+  }
+
+  &.admin--badge-image {
+    background: rgba(16, 185, 129, 0.1);
+    color: #10b981;
+  }
+
+  &.admin--badge-active {
+    background: rgba(16, 185, 129, 0.1);
+    color: var(--admin-success);
+  }
+
+  &.admin--badge-scheduled {
+    background: rgba(245, 158, 11, 0.1);
+    color: var(--admin-warning);
+  }
+
+  &.admin--badge-ended {
+    background: rgba(107, 114, 128, 0.1);
+    color: #6b7280;
+  }
+}
+
+// 작은 버튼
+.admin--btn-small {
+  padding: 6px 14px;
+  border: none;
+  border-radius: 4px;
+  font-size: 13px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  font-family: 'FORDKOREAType', sans-serif;
+  white-space: nowrap;
+
+  &.admin--btn-small-primary {
+    background: var(--admin-accent-primary);
+    color: #fff;
+
+    &:hover {
+      background: var(--admin-accent-hover);
+    }
+  }
+
+  &.admin--btn-small-danger {
+    background: var(--admin-error);
+    color: #fff;
+
+    &:hover {
+      background: #dc2626;
+    }
+  }
+}
+
+// 페이지네이션
+.admin--pagination {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 8px;
+  margin-top: 24px;
+
+  .admin--pagination-btn {
+    min-width: 36px;
+    height: 36px;
+    padding: 0 12px;
+    background: var(--admin-bg-secondary);
+    border: 1px solid var(--admin-border-color);
+    border-radius: 6px;
+    color: var(--admin-text-secondary);
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    font-family: 'FORDKOREAType', sans-serif;
+
+    &:hover:not(:disabled) {
+      background: var(--admin-bg-tertiary);
+      border-color: var(--admin-accent-primary);
+      color: var(--admin-text-primary);
+    }
+
+    &.is-active {
+      background: var(--admin-accent-primary);
+      border-color: var(--admin-accent-primary);
+      color: var(--admin-text-primary);
+    }
+
+    &:disabled {
+      opacity: 0.5;
+      cursor: not-allowed;
+    }
+  }
+}
+
+// 라디오 그룹
+.admin--radio-group {
+  display: flex;
+  gap: 20px;
+  flex-wrap: wrap;
+}
+
+.admin--radio-label {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  cursor: pointer;
+  font-size: 14px;
+  color: var(--admin-text-secondary);
+  transition: color 0.3s ease;
+
+  input[type="radio"] {
+    cursor: pointer;
+    margin: 0;
+  }
+
+  &:hover {
+    color: var(--admin-text-primary);
+  }
+}
+
+// 날짜 범위
+.admin--date-range {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+
+  .admin--date-separator {
+    color: var(--admin-text-secondary);
+    font-weight: 500;
+  }
+}
+
+// 시간 입력 그룹 (AM/PM + 시 + 분)
+.admin--time-group {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  flex-wrap: wrap;
+
+  .admin--time-input {
+    width: 80px;
+    text-align: center;
+  }
+}
+
+.admin--hint {
+  margin-left: 6px;
+  color: var(--admin-text-secondary);
+  font-size: 12px;
+  font-weight: 400;
+}
+
+// 사이즈 그룹
+.admin--size-group {
+  display: flex;
+  gap: 20px;
+
+  .admin--size-item {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+
+    label {
+      font-size: 14px;
+      color: var(--admin-text-secondary);
+      min-width: 40px;
+    }
+
+    .admin--form-input {
+      max-width: 120px;
+    }
+
+    span {
+      font-size: 14px;
+      color: var(--admin-text-secondary);
+    }
+  }
+}
+
+// 파일 입력
+.admin--form-file {
+  width: 100%;
+  padding: 12px 16px;
+  background: var(--admin-bg-tertiary);
+  border: 1px solid var(--admin-border-color);
+  border-radius: 6px;
+  color: #fff;
+  font-size: 14px;
+  font-family: 'FORDKOREAType', sans-serif;
+  cursor: pointer;
+
+  &::-webkit-file-upload-button {
+    padding: 8px 16px;
+    background: var(--admin-accent-primary);
+    color: #fff;
+    border: none;
+    border-radius: 4px;
+    font-size: 13px;
+    font-weight: 500;
+    cursor: pointer;
+    margin-right: 12px;
+    font-family: 'FORDKOREAType', sans-serif;
+  }
+}
+
+// 이미지 미리보기
+.admin--image-preview {
+  margin-top: 16px;
+  position: relative;
+  display: inline-block;
+  max-width: 400px;
+
+  img {
+    max-width: 100%;
+    border-radius: 8px;
+    border: 1px solid var(--admin-border-color);
+    display: block;
+  }
+
+  .admin--btn-remove-image {
+    position: absolute;
+    top: 12px;
+    right: 12px;
+    padding: 8px 16px;
+    background: var(--admin-error);
+    color: #fff;
+    border: none;
+    border-radius: 4px;
+    font-size: 13px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    font-family: 'FORDKOREAType', sans-serif;
+
+    &:hover {
+      background: #dc2626;
+    }
+  }
+}
+
+// 비밀번호 입력 래퍼
+.admin--password-input-wrapper {
+  position: relative;
+
+  .admin--form-input {
+    padding-right: 50px;
+  }
+
+  .admin--password-toggle {
+    position: absolute;
+    right: 12px;
+    top: 50%;
+    transform: translateY(-50%);
+    background: none;
+    border: none;
+    font-size: 20px;
+    cursor: pointer;
+    padding: 4px;
+    line-height: 1;
+    transition: opacity 0.3s ease;
+
+    &:hover {
+      opacity: 0.7;
+    }
+  }
+}
+
+// 폼 도움말
+.admin--form-help {
+  margin: 8px 0 0 0;
+  font-size: 13px;
+  color: var(--admin-text-muted);
+  font-style: italic;
+}
+
+// 검색 박스 (큰 버전 - 여러 필터)
+.admin--search-box-large {
+  .admin--search-filters {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+
+    .admin--filter-row {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      flex-wrap: wrap;
+
+      .admin--filter-label {
+        font-size: 14px;
+        color: var(--admin-text-secondary);
+        min-width: 60px;
+      }
+
+      .admin--form-select,
+      .admin--form-input {
+        flex: 1;
+        min-width: 150px;
+      }
+    }
+  }
+}
+
+// 테이블 사진
+.admin--table-photo {
+  width: 80px;
+  height: 120px;
+  border-radius: 4px;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: var(--admin-bg-tertiary);
+
+  img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+
+  .admin--table-photo-empty {
+    font-size: 11px;
+    color: var(--admin-text-muted);
+    text-align: center;
+  }
+}
+
+// 테이블 액션 (세로 배치)
+.admin--table-actions-col {
+  flex-direction: column;
+  gap: 6px;
+
+  .admin--btn-small {
+    width: 100%;
+  }
+}
+
+// 작은 버튼 (secondary 추가)
+.admin--btn-small-secondary {
+  background: var(--admin-bg-tertiary);
+  color: var(--admin-text-secondary);
+  border: 1px solid var(--admin-border-color);
+
+  &:hover {
+    background: var(--admin-bg-primary);
+    border-color: var(--admin-accent-primary);
+    color: var(--admin-text-primary);
+  }
+}
+
+// 엑셀 버튼 (녹색 배경)
+.admin--btn-small-excel {
+  background: #217346;
+  color: #ffffff;
+  border: 1px solid #1a5c37;
+
+  &:hover {
+    background: #185c37;
+    border-color: #144d2d;
+  }
+}
+
+// Responsive
+@media (max-width: 1024px) {
+  .admin--sidebar {
+    width: 220px;
+  }
+
+  .admin--main {
+    margin-left: 220px;
+  }
+}
+
+@media (max-width: 768px) {
+  .admin--header {
+    padding: 0 16px;
+  }
+
+  .admin--sidebar {
+    transform: translateX(-100%);
+    transition: transform 0.3s ease;
+
+    &.is-open {
+      transform: translateX(0);
+    }
+  }
+
+  .admin--main {
+    margin-left: 0;
+    padding: 16px;
+  }
+
+  .admin--page-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 12px;
+  }
+
+  .admin--form {
+    .admin--form-actions {
+      flex-direction: column;
+
+      .admin--btn {
+        width: 100%;
+      }
+    }
+
+    .admin--multi-input-wrapper {
+      .admin--multi-input-item {
+        flex-direction: column;
+
+        .admin--btn-remove {
+          width: 100%;
+        }
+      }
+    }
+  }
+}
+
+// File Attachment Styles
+.admin--form-file-hidden {
+  display: none;
+}
+
+.admin--file-list {
+  margin-bottom: 12px;
+  border: 1px solid var(--admin-border-color);
+  border-radius: 4px;
+  background-color: var(--admin-bg-secondary);
+}
+
+.admin--file-item {
+  display: flex;
+  align-items: center;
+  padding: 12px 16px;
+  border-bottom: 1px solid var(--admin-border-color);
+
+  &:last-child {
+    border-bottom: none;
+  }
+
+  &:hover {
+    background-color: rgba(255, 255, 255, 0.03);
+  }
+}
+
+.admin--file-name {
+  flex: 1;
+  color: var(--admin-text-primary);
+  font-size: 14px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  margin-right: 8px;
+
+  &:hover {
+    text-decoration: underline;
+  }
+}
+
+.admin--file-size {
+  color: var(--admin-text-secondary);
+  font-size: 13px;
+  margin-right: 12px;
+  white-space: nowrap;
+}
+
+.admin--btn-remove-file {
+  padding: 4px 12px;
+  background-color: transparent;
+  color: var(--admin-danger-color);
+  border: 1px solid var(--admin-danger-color);
+  border-radius: 4px;
+  font-size: 13px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  white-space: nowrap;
+
+  &:hover {
+    background-color: var(--admin-danger-color);
+    color: white;
+  }
+}
+
+// Checkbox Group Styles
+.admin--checkbox-group {
+  display: flex;
+  gap: 20px;
+}
+
+.admin--checkbox-label {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  color: var(--admin-text-primary);
+  font-size: 14px;
+  cursor: pointer;
+
+  input[type="checkbox"] {
+    width: 18px;
+    height: 18px;
+    cursor: pointer;
+    accent-color: var(--admin-accent-primary);
+  }
+
+  span {
+    user-select: none;
+  }
+
+  &:hover {
+    opacity: 0.8;
+  }
+}
+
+// Admin Footer
+.admin--footer {
+  margin-top: 64px;
+  padding: 24px 0;
+  border-top: 1px solid var(--admin-border-color);
+  text-align: center;
+
+  p {
+    color: var(--admin-text-muted);
+    font-size: 14px;
+    margin: 0;
+  }
+}
+
+// IMPORTANT: Force display admin header and footer
+.admin--header {
+  display: flex !important;
+  min-height: 64px !important;
+  background: #ffffff !important;
+  visibility: visible !important;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.admin--footer {
+  display: block !important;
+  min-height: 60px !important;
+  background: #ffffff !important;
+  visibility: visible !important;
+}
+
+// Alert Modal Styles
+.admin--modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.7);
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.admin--alert-modal {
+  background: #ffffff;
+  padding: 0;
+  border-radius: 8px;
+  min-width: 400px;
+  max-width: 500px;
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+}
+
+.admin--alert-modal .admin--modal-header {
+  padding: 20px;
+  border-bottom: 1px solid #e0e0e0;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+
+  h4 {
+    margin: 0;
+    font-size: 18px;
+    font-weight: 600;
+    color: #1a1a1a;
+  }
+}
+
+.admin--alert-modal .admin--modal-close {
+  background: none;
+  border: none;
+  font-size: 24px;
+  cursor: pointer;
+  color: #999;
+  line-height: 1;
+  transition: color 0.2s;
+
+  &:hover {
+    color: #333;
+  }
+}
+
+.admin--alert-modal .admin--modal-body {
+  padding: 30px 20px;
+
+  .admin--alert-content p {
+    margin: 0;
+    font-size: 15px;
+    line-height: 1.6;
+    color: #333333;
+  }
+}
+
+.admin--alert-modal .admin--modal-footer {
+  padding: 15px 20px;
+  border-top: 1px solid #e0e0e0;
+  display: flex;
+  gap: 10px;
+  justify-content: flex-end;
+}
+
+.admin--alert-modal .admin--btn-secondary {
+  padding: 8px 20px;
+  border: 1px solid #e0e0e0;
+  background: #f5f5f5;
+  color: #333333;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.2s;
+
+  &:hover {
+    background: #eeeeee;
+  }
+}
+
+.admin--alert-modal .admin--btn-primary {
+  padding: 8px 20px;
+  border: none;
+  background: var(--admin-accent-primary);
+  color: white;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.2s;
+
+  &:hover {
+    background: var(--admin-accent-hover);
+  }
+}
+
+// Modal Animation
+@keyframes slideUp {
+  from {
+    opacity: 0;
+    transform: translateY(30px);
+  }
+
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.modal-fade-enter-active {
+  transition: opacity 0.3s ease;
+
+  .admin--alert-modal {
+    animation: slideUp 0.3s ease-out;
+  }
+}
+
+.modal-fade-leave-active {
+  transition: opacity 0.2s ease;
+}
+
+.modal-fade-enter-from,
+.modal-fade-leave-to {
+  opacity: 0;
+}
+
+
+
+// ScrollCanvasAnimation Component Styles
+.scroll--canvas--animation {
+  position: relative;
+  width: 100%;
+
+  // 가상 스크롤 공간 (보이지 않음, 스크롤 영역만 확보)
+  .scroll--spacer {
+    width: 100%;
+    pointer-events: none;
+  }
+
+  // 고정될 캔버스 영역 (100vh)
+  .scroll--canvas--sticky {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100vh;
+    overflow: hidden;
+    transition: background-color 0.3s ease;
+
+    // 고정 상태일 때
+    &.is--fixed {
+      position: fixed;
+      top: 0;
+      left: 0;
+      right: 0;
+      z-index: 100;
+    }
+
+    // 완료 상태일 때
+    &.is--completed {
+      position: absolute;
+      top: auto;
+      bottom: 0;
+    }
+  }
+
+  // 헤드라인 컨테이너
+  .headline--container {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 10;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 40px 20px;
+    pointer-events: none;
+    transition: opacity 0.3s ease;
+
+    .content--container {
+      text-align: left;
+      padding: 0 var(--spacing-relative-3xl);
+      width: 100%;
+    }
+
+    .headline--text {
+      font-size: clamp(32px, 5vw, 64px);
+      font-weight: 700;
+      line-height: 1.2;
+      margin: 0 0 20px 0;
+      letter-spacing: -0.02em;
+      animation: fadeInUp 0.8s cubic-bezier(0.15, 0, 0.25, 1) forwards;
+      opacity: 0;
+
+
+      color: rgb(252, 252, 253);
+      letter-spacing: 0px;
+      font-weight: 400;
+      text-decoration: none;
+      font-size: 36px;
+      line-height: 52px;
+      font-stretch: 130%;
+
+
+      @media (min-width: 768px) {
+        font-size: 44px;
+        line-height: 60px;
+      }
+
+      @media (min-width: 1024px) {
+        font-size: 52px;
+        line-height: 76px;
+      }
+
+      @media (min-width: 1440px) {
+        font-size: 60px;
+        line-height: 84px;
+      }
+
+      @media (min-width: 1920px) {
+        font-size: 72px;
+        line-height: 100px;
+      }
+    }
+
+    .subheadline--text {
+      font-size: clamp(16px, 2.5vw, 24px);
+      font-weight: 400;
+      line-height: 1.5;
+      margin: 0;
+      color: rgba(252, 252, 253, 0.8);
+      animation: fadeInUp 0.8s cubic-bezier(0.15, 0, 0.25, 1) 0.2s forwards;
+      opacity: 0;
+    }
+  }
+
+  // 이미지 컨테이너
+  .scroll--images--container {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  // 캔버스
+  .scroll--canvas {
+    display: block;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    max-width: 100%;
+    max-height: 100%;
+    object-fit: cover;
+  }
+}
+
+// 페이드인 애니메이션
+@keyframes fadeInUp {
+  from {
+    opacity: 0;
+    transform: translateY(40px);
+  }
+
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+// 반응형 스타일
+@media (max-width: 1024px) {
+  .scroll--canvas--animation {
+    .headline--container {
+      padding: 30px 15px;
+
+      .headline--text {
+        margin-bottom: 15px;
+      }
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .scroll--canvas--animation {
+    .headline--container {
+      padding: 20px 15px;
+
+      .headline--text {
+        margin-bottom: 12px;
+      }
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .scroll--canvas--animation {
+    .headline--container {
+      padding: 15px 10px;
+
+      .content--container {
+        max-width: 100%;
+      }
+    }
+  }
+}
+
+
+
+
+
+.iframe--wrapper {
+  position: relative;
+  padding-top: 55.6%;
+
+  iframe {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    top: 0px;
+  }
+}
+
+
+.sign--wrapper {
+  display: flex;
+  align-items: center;
+  gap: 30px;
+  width: 100%;
+  justify-content: flex-end;
+}
+
+// 중복 체크 버튼 스타일
+.admin--input-with-button {
+  display: flex;
+  gap: 8px;
+  align-items: stretch;
+
+  .admin--form-input {
+    flex: 1;
+  }
+
+  .admin--btn {
+    flex-shrink: 0;
+    white-space: nowrap;
+    height: auto;
+  }
+}
+
+.admin--btn-check {
+  background: #2563eb !important;
+  color: #ffffff !important;
+  border: none;
+  padding: 10px 16px;
+  border-radius: 4px;
+  font-size: 13px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  font-family: 'FORDKOREAType', sans-serif;
+
+  &:hover:not(:disabled) {
+    background: #1d4ed8 !important;
+  }
+
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+}
+
+.admin--form-help {
+  margin-top: 6px;
+  font-size: 12px;
+  line-height: 1.4;
+  color: #999;
+}
+
+.admin--text-success {
+  color: #4caf50 !important;
+}
+
+.admin--text-error {
+  color: #f44336 !important;
+}
+
+
+.file--item {
+  display: flex;
+  align-items: center;
+}
+
+
+
+
+
+footer {
+  position: relative;
+  background: #252525;
+
+  .footer--wrap {
+
+    //position: relative;
+    .footer--btn--wrap {
+      display: flex;
+      flex-direction: column;
+      position: fixed;
+      bottom: 120px;
+      //left: calc(100% + 60px);
+      right: 110px;
+      gap: 12px;
+      opacity: 0;
+      z-index: 10;
+      pointer-events: none;
+      transition: all 0.3s;
+
+      &.active {
+        pointer-events: all;
+        opacity: 1;
+      }
+
+      .quick--wrap {
+        position: fixed;
+        right: 49px;
+        border-radius: 20px;
+        background: #F5FAFF;
+        box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.20);
+        width: 192px;
+        overflow: hidden;
+        transition: all 0.3s;
+        border: 1px solid rgba(7, 111, 237, 0.50);
+        opacity: 0;
+        bottom: 330px;
+        pointer-events: none;
+
+        &.lincoln {
+          border: 1px solid rgba(219, 120, 77, 0.50);
+          background: #FFF8F5;
+
+          .inner--wrap {
+            li {
+              a {
+                &:hover {
+                  color: #DB784D;
+                }
+              }
+            }
+          }
+        }
+
+        &.active {
+          pointer-events: all;
+          opacity: 1;
+          bottom: 317px;
+        }
+
+        .inner--wrap {
+          padding: 5px 30px;
+          display: flex;
+          flex-direction: column;
+
+          li {
+            text-align: center;
+            display: flex;
+
+            a {
+              width: 100%;
+              color: #333;
+              white-space: nowrap;
+              padding: 12.5px 0;
+              font-size: 13px;
+              text-transform: uppercase;
+              font-weight: 500;
+              transition: all 0.3s;
+              line-height: 1;
+
+              &:hover {
+                color: #076FED;
+                text-decoration-line: underline;
+                text-decoration-style: solid;
+                text-decoration-skip-ink: auto;
+                text-decoration-thickness: auto;
+                text-underline-offset: auto;
+                text-underline-position: from-font;
+                font-weight: 700;
+              }
+            }
+          }
+        }
+      }
+
+      .quick--menu {
+        display: flex;
+        flex-direction: column;
+        background-color: #076fed;
+        border-radius: 100px;
+        box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.20);
+        align-items: center;
+        justify-content: center;
+        width: 70px;
+        height: 100px;
+        gap: 10px;
+        font-size: 12px;
+        font-weight: 700;
+        color: #fff;
+
+        &.active {
+          .ico {
+            background-image: url(/img/ico--quick--close.svg);
+          }
+        }
+
+        &.lincoln {
+          background-color: #DB784D;
+        }
+
+        .ico {
+          width: 24px;
+          height: 24px;
+          background-image: url(/img/ico--quick.svg);
+        }
+      }
+
+      .scroll--to--top {
+        display: flex;
+        flex-direction: column;
+        box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.20);
+        width: 70px;
+        height: 70px;
+        border-radius: 50%;
+        background-color: #282828;
+        gap: 2px;
+        justify-content: center;
+        font-size: 13px;
+        font-weight: 700;
+        align-items: center;
+
+        .ico {
+          width: 24px;
+          height: 24px;
+          background-image: url(/img/ico--top--arrow.svg);
+        }
+      }
+    }
+
+    .footer--site--map {
+
+      >ul {
+        >li {
+          >ul {
+
+            >h3,
+            >a {
+              position: relative;
+              padding-left: 13px;
+              margin-bottom: 15px;
+              text-transform: capitalize;
+              display: block;
+
+              &:before {
+                content: '';
+                width: 3px;
+                height: 3px;
+                position: absolute;
+                display: block;
+                background: #E9E9E9;
+                top: 50%;
+                left: 0;
+                transform: translateY(-50%);
+              }
+            }
+
+            >li {
+              position: relative;
+              padding-left: 13px;
+
+              &:before {
+                content: '';
+                width: 3px;
+                height: 3px;
+                position: absolute;
+                display: block;
+                background: #E9E9E9;
+                top: 50%;
+                left: 0;
+                transform: translateY(-50%);
+              }
+
+              >a {
+                color: #E9E9E9;
+                font-size: 14px;
+              }
+
+              &.inner--style {
+                margin-bottom: 15px;
+
+                &:last-child {
+                  margin-bottom: 30px;
+                }
+
+                &:before {
+                  display: none;
+                }
+
+                a {
+                  color: #B7B7B7;
+                  font-size: 14px;
+                  font-style: normal;
+                  font-weight: 400;
+                  line-height: 100%;
+                  /* 14px */
+                  text-transform: capitalize;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .footer--info--wrapper {
+    background: #161616;
+
+    .footer--info--wrap {
+      position: relative;
+
+      .sns--wrap {
+        display: flex;
+        justify-content: flex-end;
+        margin-bottom: 12px;
+        gap: 15px;
+        position: absolute;
+        top: 50%;
+        transform: translateY(-50%);
+        right: 0px;
+
+        >a {
+          width: 40px;
+          height: 40px;
+          display: inline-block;
+          border-radius: 50%;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+
+          cursor: pointer;
+          box-sizing: border-box;
+          background: #fff;
+        }
+      }
+
+      .copy--wrap {
+        font-size: 14px;
+        color: #fcfcfdb2;
+        max-width: 1440px;
+        margin: 0 auto;
+        padding: 30px 0px;
+        position: relative;
+
+        .link--list {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 37px;
+
+          >li {
+            position: relative;
+
+            &::after {
+              content: '';
+              height: 15px;
+              width: 1px;
+              background: rgba(255, 255, 255, 0.50);
+              display: block;
+              position: absolute;
+              right: -18px;
+              top: 50%;
+              transform: translateY(-50%);
+            }
+
+            &:last-child {
+              &::after {
+                display: none;
+              }
+            }
+
+            >a {
+              display: inline-block;
+              color: rgb(252, 252, 253);
+              transition: all 0.3s;
+              color: #FFF;
+              font-size: 15px;
+              font-style: normal;
+              font-weight: 700;
+              line-height: 100%;
+              /* 15px */
+              letter-spacing: -0.3px;
+              text-transform: capitalize;
+
+              &:hover {
+                opacity: 0.7;
+              }
+            }
+          }
+        }
+
+        >p {
+          //margin-top: 25px;
+        }
+      }
+    }
+  }
+
+  @media (max-width: 768px) {
+    .footer--wrap {
+      .footer--site--map {
+        >ul {
+          >li {
+            >h2 {
+              cursor: pointer;
+              user-select: none;
+              position: relative;
+              padding-right: 20px;
+
+              &::after {
+                content: '';
+                position: absolute;
+                right: 20px;
+                background-image: url(/img/ico--footer.svg);
+                width: 18px;
+                min-width: 18px;
+                margin-left: 20px;
+                height: 18px;
+                transition: transform 0.3s ease;
+              }
+            }
+
+            >ul {
+              max-height: 0;
+              overflow: hidden;
+              transition: max-height 0.3s ease;
+
+              &.active {
+                max-height: 1000px;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+
+
+.swiper--banner--wrapper {
+  width: 100%;
+  overflow: hidden;
+
+  .swiper--banner--container {
+    position: relative;
+
+    .swiper-button-next,
+    .swiper-button-prev {
+      display: flex;
+      width: 65px;
+      height: 65px;
+      padding: 21px 20px 20px 21px;
+      justify-content: center;
+      align-items: center;
+      flex-shrink: 0;
+      position: absolute;
+      top: 50%;
+      background: none;
+      border-radius: 1000px;
+      border: 1px solid #FFF;
+      z-index: 9;
+      cursor: pointer;
+      transition: all 0.3s ease;
+
+      &:hover {
+        background: rgba(0, 0, 0, 0.5);
+      }
+
+      &::after {
+        display: none; // Swiper 기본 화살표 숨김
+      }
+
+      svg {
+        width: 24px;
+        height: 24px;
+        fill: none;
+        pointer-events: none; // SVG가 클릭을 방해하지 않도록
+      }
+    }
+
+    .swiper-button-next {
+      right: 50%;
+      transform: translate(calc(1440px / 2), -50%);
+    }
+
+    .swiper-button-prev {
+      left: 50%;
+      transform: translate(calc(-1440px / 2), -50%);
+    }
+
+    // Pagination wrapper with progress and fractions
+    .swiper--pagination--wrapper {
+      position: absolute;
+      bottom: 20px;
+      left: 50%;
+      transform: translateX(-50%);
+      z-index: 9;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      gap: 12px;
+
+      .swiper-pagination {
+        position: relative;
+        bottom: auto;
+        left: auto;
+        transform: none;
+
+        .swiper-pagination-bullet {
+          opacity: 1;
+          width: 12px;
+          height: 12px;
+          background: rgba(255, 255, 255, 0.5);
+          transition: all 0.3s ease;
+
+          &.swiper-pagination-bullet-active {
+            background: #fff;
+          }
+        }
+      }
+
+      .swiper--fraction {
+        display: flex;
+        align-items: center;
+        gap: 4px;
+        color: #fff;
+        font-size: 16px;
+        font-weight: 600;
+
+        .separator {
+          opacity: 0.7;
+        }
+
+        .total {
+          opacity: 0.7;
+        }
+      }
+
+      .swiper--progressbar {
+        width: 200px;
+        height: 3px;
+        background: rgba(255, 255, 255, 0.3);
+        border-radius: 10px;
+        overflow: hidden;
+
+        .swiper--progressbar-fill {
+          height: 100%;
+          background: #fff;
+          transition: width 0.3s ease;
+          border-radius: 10px;
+        }
+      }
+    }
+
+    // 기존 pagination (호환성 유지)
+    .swiper-pagination {
+      position: absolute;
+      bottom: 20px;
+      z-index: 8;
+
+      .swiper-pagination-bullet {
+        opacity: 1;
+
+        &.swiper-pagination-bullet-active {
+          background: #FFF;
+        }
+
+        width:12px;
+        height:12px;
+        background: rgba(255, 255, 255, 0.50);
+      }
+    }
+
+    .swiper--banner--section {
+      .swiper--container {
+        .swiper-wrapper {
+          .slide--image {
+            height: 550px;
+            position: relative;
+
+            img {
+              margin: 0 auto;
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+
+            // 확대 버튼
+            .zoom--btn {
+              position: absolute;
+              top: 20px;
+              right: 20px;
+              width: 50px;
+              height: 50px;
+              background: rgba(0, 0, 0, 0.5);
+              border: 1px solid rgba(255, 255, 255, 0.3);
+              border-radius: 50%;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              cursor: pointer;
+              z-index: 10;
+              transition: all 0.3s ease;
+
+              &:hover {
+                background: rgba(0, 0, 0, 0.7);
+                border-color: rgba(255, 255, 255, 0.6);
+                transform: scale(1.1);
+              }
+
+              svg {
+                width: 24px;
+                height: 24px;
+              }
+            }
+
+            .desc--wrapper {
+              position: absolute;
+              top: 100px;
+              left: 50%;
+              transform: translateX(-50%);
+              width: 100%;
+              display: flex;
+              align-items: center;
+              flex-direction: column;
+
+              h2 {
+                color: #FFF;
+                text-align: center;
+                font-size: 45px;
+                font-style: normal;
+                font-weight: 700;
+                line-height: 1.2;
+                text-transform: uppercase;
+              }
+
+              h3 {
+                color: #FFF;
+                text-align: center;
+                font-size: 20px;
+                font-style: normal;
+                font-weight: 400;
+                //line-height: 100%; /* 20px */
+                text-transform: uppercase;
+                margin-top: 25px;
+              }
+
+              .more--detail--href {
+                border-radius: 100px;
+                border: 1px solid #FFF;
+
+                color: #FFF;
+                text-align: center;
+                font-size: 15px;
+                font-style: normal;
+                font-weight: 700;
+                line-height: 100%;
+                /* 15px */
+                letter-spacing: -0.3px;
+                text-transform: uppercase;
+                padding: 18px 20px;
+                min-width: 150px;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+
+.swiper--banner--wrapper2 {
+  width: 100%;
+  overflow: hidden;
+  border-radius: 30px;
+  max-height: 600px;
+
+  .swiper--banner--container {
+    position: relative;
+
+    .type--connection {
+      position: absolute;
+      top: 45px;
+      right: 45px;
+      z-index: 10;
+
+      &.exterior {
+        color: #FFF;
+        font-size: 14px;
+        font-style: normal;
+        font-weight: 700;
+        line-height: 100%;
+        /* 14px */
+        text-transform: uppercase;
+        border-radius: 1000px;
+        background: #076FED;
+        padding: 15px 20px;
+        gap: 10px;
+        display: flex;
+        align-items: center;
+
+        &.lincoln {
+          background: #DB784D;
+        }
+      }
+
+      &.interior {
+        color: #FFF;
+        font-size: 14px;
+        font-style: normal;
+        font-weight: 700;
+        line-height: 100%;
+        /* 14px */
+        text-transform: uppercase;
+        border-radius: 1000px;
+        background: #0AF;
+        padding: 15px 20px;
+        gap: 10px;
+        display: flex;
+        align-items: center;
+
+        &.lincoln {
+          background: #22292B;
+        }
+      }
+    }
+
+    // Pagination wrapper with progress and fractions
+    .swiper--pagination--wrapper {
+      position: absolute;
+      bottom: 65px;
+      right: 45px;
+      z-index: 9;
+      display: flex;
+      align-items: center;
+      gap: 12px;
+
+      .swiper-pagination {
+        display: none;
+        position: relative;
+        bottom: auto;
+        left: auto;
+        transform: none;
+
+        .swiper-pagination-bullet {
+          opacity: 1;
+          width: 12px;
+          height: 12px;
+          background: rgba(255, 255, 255, 0.5);
+          transition: all 0.3s ease;
+
+          &.swiper-pagination-bullet-active {
+            background: #fff;
+          }
+        }
+      }
+
+      .swiper--fraction {
+        display: flex;
+        align-items: center;
+        gap: 4px;
+        color: #fff;
+        font-size: 14px;
+        font-weight: 700;
+
+        .separator {
+          color: rgba(255, 255, 255, 0.60);
+          font-weight: 400;
+        }
+
+        .total {
+          color: rgba(255, 255, 255, 0.60);
+          font-weight: 400;
+        }
+      }
+
+      .swiper--progressbar {
+        width: 200px;
+        height: 3px;
+        background: rgba(255, 255, 255, 0.3);
+        border-radius: 10px;
+        overflow: hidden;
+
+        .swiper--progressbar-fill {
+          height: 100%;
+          background: #fff;
+          transition: width 0.3s ease;
+          border-radius: 10px;
+        }
+      }
+    }
+
+    .swiper--banner--section {
+      .swiper--container {
+        .swiper-wrapper {
+          .slide--image {
+            position: relative;
+            z-index: 1;
+            padding-top: 56.5%;
+            min-height: 600px;
+
+            >img {
+              position: absolute;
+              top: 0px;
+              width: 100%;
+              height: 100%;
+              z-index: 1;
+              object-fit: cover;
+            }
+
+            // 확대 버튼
+            .zoom--btn {
+              position: absolute;
+              bottom: 45px;
+              left: 45px;
+
+              display: flex;
+              height: 60px;
+              padding: 20px 25px;
+              justify-content: center;
+              align-items: center;
+              gap: 10px;
+
+              cursor: pointer;
+              z-index: 10;
+              transition: all 0.3s ease;
+              border-radius: 1000px;
+              border: 1px solid #FFF;
+
+              color: #FFF;
+              font-size: 14px;
+              font-style: normal;
+              font-weight: 400;
+              line-height: 100%;
+              /* 14px */
+              text-transform: capitalize;
+
+
+
+              svg {
+                width: 12px;
+                height: 12px;
+              }
+            }
+
+            .desc--wrapper {
+              position: absolute;
+              top: 0px;
+              left: 0px;
+              width: 100%;
+              height: 100%;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              flex-direction: column;
+
+              h2 {
+                color: #FFF;
+                text-align: center;
+                font-size: 45px;
+                font-style: normal;
+                font-weight: 700;
+                line-height: 100%;
+                /* 45px */
+                text-transform: uppercase;
+              }
+
+              h3 {
+                color: #FFF;
+                text-align: center;
+                font-size: 20px;
+                font-style: normal;
+                font-weight: 400;
+                line-height: 100%;
+                /* 20px */
+                text-transform: uppercase;
+                margin-top: 25px;
+              }
+
+              .more--detail--href {
+                border-radius: 100px;
+                border: 1px solid #FFF;
+
+                color: #FFF;
+                text-align: center;
+                font-size: 15px;
+                font-style: normal;
+                font-weight: 700;
+                line-height: 100%;
+                /* 15px */
+                letter-spacing: -0.3px;
+                text-transform: uppercase;
+                padding: 18px 20px;
+                min-width: 150px;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+
+.swiper--banner--wrapper3 {
+  width: 100%;
+  overflow: hidden;
+  background: #111;
+
+  .top--text--wrap {
+    padding: 80px 0px;
+
+    h2 {
+      color: #FFF;
+      text-align: center;
+      font-size: 40px;
+      font-style: normal;
+      font-weight: 500;
+      line-height: 100%;
+      /* 40px */
+      text-transform: capitalize;
+    }
+
+    >div {
+      color: #FFF;
+      text-align: center;
+      font-size: 18px;
+      font-style: normal;
+      font-weight: 300;
+      line-height: 1.7;
+      text-transform: uppercase;
+    }
+  }
+
+  .swiper--banner--container {
+    position: relative;
+
+
+    .btn--actions {
+      display: flex;
+      position: absolute;
+      top: -27.5px;
+      right: 25px;
+      height: 55px;
+      justify-content: flex-end;
+      gap: 15px;
+      z-index: 10;
+
+      .swiper-button-next,
+      .swiper-button-prev {
+        margin-top: 0;
+        display: flex;
+        width: 55px;
+        height: 55px;
+        justify-content: center;
+        align-items: center;
+        flex-shrink: 0;
+        background: none;
+        border-radius: 1000px;
+        border: 0px;
+        background: #fff;
+        cursor: pointer;
+        transition: all 0.3s ease;
+        position: static;
+
+        &:hover {
+          background: rgba(0, 0, 0, 0.5);
+        }
+
+        &::after {
+          display: none; // Swiper 기본 화살표 숨김
+        }
+
+        svg {
+          width: 24px;
+          height: 24px;
+          fill: none;
+          pointer-events: none; // SVG가 클릭을 방해하지 않도록
+        }
+      }
+
+    }
+
+    // Pagination wrapper with progress and fractions
+    .swiper--pagination--wrapper {
+      position: absolute;
+      bottom: 20px;
+      left: 50%;
+      transform: translateX(-50%);
+      z-index: 9;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      gap: 12px;
+
+      .swiper-pagination {
+        position: relative;
+        bottom: auto;
+        left: auto;
+        transform: none;
+
+        .swiper-pagination-bullet {
+          opacity: 1;
+          width: 12px;
+          height: 12px;
+          background: rgba(255, 255, 255, 0.5);
+          transition: all 0.3s ease;
+
+          &.swiper-pagination-bullet-active {
+            background: #fff;
+          }
+        }
+      }
+
+
+
+    }
+
+    // 기존 pagination (호환성 유지)
+    .swiper-pagination {
+      position: absolute;
+      bottom: 20px;
+      z-index: 9;
+      display: none;
+
+      .swiper-pagination-bullet {
+        opacity: 1;
+
+        &.swiper-pagination-bullet-active {
+          background: #FFF;
+        }
+
+        width:12px;
+        height:12px;
+        background: rgba(255, 255, 255, 0.50);
+      }
+    }
+
+    .swiper--banner--section {
+      .swiper--container {
+        .swiper-wrapper {
+          .slide--image {
+            position: relative;
+
+            img {
+              width: 100%;
+              margin: 0 auto;
+              object-fit: cover;
+            }
+
+
+            .desc--wrapper {
+              position: absolute;
+              bottom: 0;
+              width: 100%;
+              max-width: 1440px;
+              margin: 0 auto;
+              left: 50%;
+              transform: translateX(-50%);
+              background: rgba(0, 0, 0, 0.50);
+
+              .desc--wrap {
+                padding: 50px 60px;
+                align-self: stretch;
+                gap: 25px;
+                flex-direction: column;
+                display: flex;
+                position: relative;
+                height: 100%;
+                width: 100%;
+              }
+
+              h2 {
+                color: #FFF;
+                font-size: 22px;
+                font-style: normal;
+                font-weight: 500;
+                line-height: 100%;
+                /* 22px */
+                text-transform: uppercase;
+              }
+
+              h4 {
+                color: #FFF;
+                font-size: 17px;
+                font-style: normal;
+                font-weight: 400;
+                line-height: 1.9;
+              }
+
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+/* 차량 상세 정보 스팩 */
+.trim--spec--wrap {
+  width: 100%;
+  max-width: 1440px;
+  margin: 0 auto;
+
+  ul {
+    display: flex;
+    width: 100%;
+    align-items: flex-start;
+    justify-content: center;
+    padding: 40px 0px;
+    border-radius: 30px;
+    background: #F9F9F9;
+
+    li {
+      width: calc(100% / 3);
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      border-right: 1px solid #DEDEDE;
+      padding: 15px 20px;
+
+      &:last-child {
+        border-right: 0px;
+      }
+
+      >span {
+        color: #000;
+        text-align: center;
+        font-size: 16px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: 100%;
+        /* 16px */
+        text-transform: uppercase;
+      }
+
+      >div {
+        color: #000;
+        font-size: 22px;
+        font-style: normal;
+        font-weight: 700;
+        text-align: center;
+        //line-height: 100%; /* 22px */
+        flex-wrap: wrap;
+        //text-transform: uppercase;
+        justify-content: center;
+        display: flex;
+        align-items: center;
+        margin-top: 25px;
+
+        >u {
+          text-decoration: none;
+          color: #000;
+          font-size: 16px;
+          font-style: normal;
+          font-weight: 500;
+          line-height: 1.3;
+          text-align: left;
+          width: 90px;
+          margin-right: 20px;
+          display: flex;
+          align-items: center;
+        }
+
+        >i {
+          margin: 0 5px;
+          text-transform: lowercase;
+          color: #000;
+          text-align: center;
+          font-size: 22px;
+          font-style: normal;
+          font-weight: 700;
+          line-height: 100%;
+          /* 22px */
+        }
+
+        span {
+          display: inline-flex;
+          width: 1px;
+          height: 18px;
+          background: #CACACA;
+          margin: 0 10px;
+        }
+
+        em {
+          font-style: normal;
+          color: #000;
+          font-size: 16px;
+          font-style: normal;
+          font-weight: 400;
+          line-height: 100%;
+          /* 16px */
+          text-transform: uppercase;
+          margin-left: 5px;
+          position: relative;
+
+          i {
+            font-style: normal;
+            color: #076FED;
+            font-size: 12px;
+            font-style: normal;
+            font-weight: 700;
+            line-height: 100%;
+            /* 12px */
+            text-transform: uppercase;
+            position: absolute;
+            top: -1px;
+            right: -10px;
+          }
+        }
+      }
+    }
+  }
+
+  &.lincoln {
+    ul {
+      li {
+        >div {
+          em {
+            i {
+              color: #DB784D;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+.title--visual {
+  margin-top: 120px;
+
+  &.dark--type {
+    background: #FAFAFA;
+    margin-top: 0px;
+    padding: 80px 0px;
+  }
+
+  h2 {
+    color: #000;
+    text-align: center;
+    font-size: 45px;
+    font-style: normal;
+    font-weight: 500;
+    //line-height: 100%; /* 45px */
+    text-transform: capitalize;
+
+    span {
+      font-weight: 400;
+    }
+
+    &.side--title {
+      color: #111;
+      text-align: center;
+      font-size: 35px;
+      font-style: normal;
+      font-weight: 500;
+      //line-height: 100%; /* 30px */
+      text-transform: uppercase;
+    }
+
+    &.side--title2 {
+      color: #111;
+      text-align: center;
+      font-size: 25px;
+      font-style: normal;
+      font-weight: 500;
+      line-height: 100%;
+      /* 25px */
+      letter-spacing: -0.5px;
+      text-transform: uppercase;
+    }
+
+    &.side--title3 {
+      color: #000;
+      font-size: 30px;
+      font-weight: 700;
+    }
+  }
+
+  .middle--title {
+    color: #00095B;
+    text-align: center;
+    font-size: 20px;
+    font-style: normal;
+    font-weight: 500;
+    //line-height: 100%; /* 20px */
+    text-transform: uppercase;
+    margin-top: 40px;
+  }
+
+  .sub--title {
+    margin-top: 45px;
+    color: #222;
+    text-align: center;
+    font-size: 18px;
+    font-style: normal;
+    font-weight: 300;
+    line-height: 1.7;
+    // text-transform: uppercase;
+  }
+
+  .sub--title2 {
+    margin-top: 40px;
+    text-align: center;
+    color: #111;
+    font-size: 25px;
+    font-weight: 700;
+
+    @media(max-width: 1024px) {
+      margin-top: 20px;
+      font-size: 20px;
+    }
+  }
+}
+
+.models--visual--01 {
+  width: 100%;
+  max-width: 1440px;
+  margin: 0 auto;
+  overflow: hidden;
+  border-radius: 20px;
+  position: relative;
+  aspect-ratio: 1440 / 530; // 비율 유지
+
+  &.type--5 {
+    aspect-ratio: 1440 / 650; // 비율 유지
+
+    .thumb--wrap {
+      top: 0%;
+    }
+  }
+
+  &.type--4 {
+    aspect-ratio: 1440 / 530; // 비율 유지
+
+    .thumb--wrap {
+      top: 0%;
+
+      img {
+        width: 100%;
+        height: 100%;
+        max-height: 530px;
+      }
+    }
+  }
+
+  &.type--3 {
+    aspect-ratio: 1440 / 530; // 비율 유지
+
+    .thumb--wrap {
+      top: 0%;
+
+      img {
+        width: 100%;
+        height: auto;
+      }
+    }
+  }
+
+  &.type--2 {
+
+    //aspect-ratio: 1440 / 650; // 비율 유지
+    .thumb--wrap {
+      top: -20%;
+
+      img {
+        width: 100%;
+        height: auto;
+      }
+    }
+  }
+
+  .thumb--wrap {
+    width: 100%;
+    height: 150%; // 높이를 늘려서 여유 공간 확보
+    position: relative;
+    top: -50%; // 위로 이동
+
+    img {
+      object-fit: cover;
+      object-position: center top; // 이미지 상단 기준으로 정렬
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+
+
+.car--price--small--pic--wrap {
+  >div {
+    h2 {
+      color: #000;
+      text-align: center;
+      font-size: 30px;
+      font-style: normal;
+      font-weight: 500;
+      line-height: 1.4;
+      text-transform: capitalize;
+    }
+
+    .car--list--wrap {
+      padding-top: 60px;
+      ;
+
+      ul {
+        display: flex;
+        align-items: flex-start;
+        justify-content: center;
+        gap: 100px;
+
+        li {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          justify-content: flex-start;
+          text-align: center;
+
+          .desc--wrap {
+            padding-top: 30px;
+
+            h2 {
+              color: #00095B;
+              text-align: center;
+              font-size: 22px;
+              font-style: normal;
+              font-weight: 500;
+              //line-height: 100%; /* 22px */
+              text-transform: capitalize;
+
+              .trim--desc {
+                color: #00095B;
+                font-weight: 500;
+                font-size: 16px;
+              }
+
+              &.black {
+                color: #22292B;
+                font-weight: 700;
+              }
+            }
+
+            .price--wrap {
+              color: #000;
+              text-align: center;
+              font-size: 18px;
+              font-style: normal;
+              font-weight: 300;
+              // line-height: 100%; /* 18px */
+              text-transform: uppercase;
+              position: relative;
+              display: flex;
+              flex-wrap: wrap;
+              justify-content: center;
+              align-items: center;
+              margin-top: 25px;
+
+              &.flex--column {
+                flex-direction: column;
+              }
+
+              em {
+                color: #000;
+                font-size: 18px;
+                font-style: normal;
+                font-weight: 500;
+                line-height: 100%;
+                text-transform: uppercase;
+                position: relative;
+                margin-left: 3px;
+
+                i {
+                  position: absolute;
+                  top: -1px;
+                  right: -10px;
+                  color: #1700F4;
+                  text-align: center;
+                  font-size: 12px;
+                  font-style: normal;
+                  font-weight: 500;
+                  line-height: 100%;
+                  /* 12px */
+                  text-transform: uppercase;
+
+                  &.lincoln {
+                    color: #FF6524;
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+// Lightbox Styles
+.lightbox--overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.95);
+  z-index: 10000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+
+  .lightbox--close {
+    position: absolute;
+    top: 20px;
+    right: 20px;
+    width: 50px;
+    height: 50px;
+    background: rgba(255, 255, 255, 0.1);
+    border: 1px solid rgba(255, 255, 255, 0.3);
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    z-index: 10001;
+    transition: all 0.3s ease;
+
+    &:hover {
+      background: rgba(255, 255, 255, 0.2);
+      transform: rotate(90deg);
+    }
+
+    svg {
+      width: 32px;
+      height: 32px;
+    }
+  }
+
+  .lightbox--prev,
+  .lightbox--next {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 60px;
+    height: 60px;
+    background: rgba(255, 255, 255, 0.1);
+    border: 1px solid rgba(255, 255, 255, 0.3);
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    z-index: 10001;
+    transition: all 0.3s ease;
+
+    &:hover {
+      background: rgba(255, 255, 255, 0.2);
+      transform: translateY(-50%) scale(1.1);
+    }
+
+    &:disabled {
+      opacity: 0.3;
+      cursor: not-allowed;
+
+      &:hover {
+        transform: translateY(-50%);
+      }
+    }
+
+    svg {
+      width: 40px;
+      height: 40px;
+    }
+  }
+
+  .lightbox--prev {
+    left: 20px;
+  }
+
+  .lightbox--next {
+    right: 20px;
+  }
+
+  .lightbox--content {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: relative;
+
+    .lightbox--swiper {
+      width: 100%;
+      height: 100%;
+      max-width: 90vw;
+      max-height: 90vh;
+
+      .swiper-wrapper {
+        .swiper-slide {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+
+          img {
+            max-width: 100%;
+            max-height: 90vh;
+            width: auto;
+            height: auto;
+            object-fit: contain;
+            border-radius: 8px;
+          }
+        }
+      }
+    }
+
+    .lightbox--info {
+      position: absolute;
+      bottom: 30px;
+      left: 50%;
+      transform: translateX(-50%);
+      z-index: 10001;
+
+      .lightbox--fraction {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        padding: 12px 24px;
+        background: rgba(0, 0, 0, 0.6);
+        border-radius: 30px;
+        color: #fff;
+        font-size: 18px;
+        font-weight: 600;
+        backdrop-filter: blur(10px);
+      }
+    }
+  }
+}
+
+// Lightbox Animation
+.lightbox-fade-enter-active,
+.lightbox-fade-leave-active {
+  transition: opacity 0.3s ease;
+}
+
+.lightbox-fade-enter-from,
+.lightbox-fade-leave-to {
+  opacity: 0;
+}
+
+
+.caution--text--foot {
+  display: flex;
+  width: 100%;
+  gap: 10px;
+  background: #F7F7F7;
+  padding: 70px 0px;
+
+  .caution--text {
+    max-width: 1440px;
+    margin: 0 auto;
+    color: #000;
+    font-size: 17px;
+    font-style: normal;
+    font-weight: 300;
+    line-height: 1.8;
+    letter-spacing: -0.34px;
+    text-transform: uppercase;
+  }
+}
+
+// 반응형
+@media (max-width: 768px) {
+  .swiper--banner--wrapper {
+    .swiper--banner--container {
+      .swiper--pagination--wrapper {
+        .swiper--progressbar {
+          width: 150px;
+        }
+
+        .swiper--fraction {
+          font-size: 14px;
+        }
+      }
+
+      .zoom--btn {
+        width: 40px;
+        height: 40px;
+        top: 10px;
+        right: 10px;
+
+        svg {
+          width: 20px;
+          height: 20px;
+        }
+      }
+    }
+  }
+
+  .lightbox--overlay {
+
+    .lightbox--prev,
+    .lightbox--next {
+      width: 50px;
+      height: 50px;
+
+      svg {
+        width: 30px;
+        height: 30px;
+      }
+    }
+
+    .lightbox--prev {
+      left: 10px;
+    }
+
+    .lightbox--next {
+      right: 10px;
+    }
+
+    .lightbox--content {
+      .lightbox--info {
+        bottom: 20px;
+
+        .lightbox--fraction {
+          font-size: 16px;
+          padding: 10px 20px;
+        }
+      }
+    }
+  }
+}
+
+.theme--wrap {
+  display: flex;
+  flex-direction: column;
+  margin: 0 auto;
+  max-width: 1440px;
+  align-items: center;
+  justify-content: center;
+  gap: 120px;
+
+  .theme--img {
+    >p {
+      margin-bottom: 35px;
+      color: #000;
+      font-size: 19px;
+      font-weight: 400;
+      text-align: center;
+      text-transform: capitalize;
+
+      @media(max-width: 500px) {
+        margin-bottom: 10px;
+      }
+    }
+
+    .img--wrap {
+      border-radius: 20px;
+      overflow: hidden;
+
+      img {
+        width: 100%;
+      }
+    }
+  }
+
+  .theme--top {
+    display: flex;
+    gap: 40px;
+  }
+
+  .theme--bot {
+    gap: 30px;
+    display: flex;
+
+    .theme--img {
+      border-radius: 20px;
+      overflow: hidden;
+
+      img {
+        width: 100%;
+      }
+    }
+  }
+}
+
+.gallery--wrap {
+  margin: 0 auto;
+  max-width: 1440px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+  .thumb--wrap {
+    // width:calc(50% - 20px);
+    width: 100%;
+
+    .slide--image {
+      padding-top: 0px !important;
+    }
+  }
+}
+
+
+.models--visual--grid {
+  width: 100%;
+
+  ul {
+    max-width: 1440px;
+    margin: 0 auto;
+    display: flex;
+    align-items: flex-start;
+    justify-content: center;
+    gap: 30px;
+
+    li {
+      width: calc((100% - 60px) / 3);
+
+      .desc--wrap {
+        padding-top: 50px;
+
+        h2 {
+          color: #111;
+          text-align: center;
+          font-size: 25px;
+          font-style: normal;
+          font-weight: 500;
+          line-height: 1.3;
+          //text-transform: uppercase;
+        }
+
+        .captions {
+          color: #222;
+          text-align: center;
+          font-size: 17px;
+          font-style: normal;
+          font-weight: 300;
+          line-height: 1.7;
+          //text-transform: uppercase;
+          margin-top: 40px;
+        }
+      }
+
+      .thumb--wrap {
+        position: relative;
+        aspect-ratio: 46/35;
+        overflow: hidden;
+        border-radius: 20px;
+
+        img {
+          width: 100%;
+          height: 100%;
+          position: absolute;
+          top: 50%;
+          transform: translateY(-50%);
+          object-fit: cover;
+        }
+      }
+
+    }
+  }
+}
+
+
+.ovwner--wrapper {
+  width: 100%;
+  max-width: 1440px;
+  margin: 0 auto;
+  padding-bottom: 120px;
+
+  &.type--2 {
+    max-width: 100%;
+
+    .inner--wrap {
+      max-width: 1440px;
+      margin: 0 auto;
+    }
+  }
+
+  .title--visual {
+    text-align: center;
+
+    .desc {
+      color: #333;
+      text-align: center;
+      font-size: 18px;
+      font-style: normal;
+      font-weight: 300;
+      line-height: 1.7;
+      /* 18px */
+      letter-spacing: -0.36px;
+      text-transform: uppercase;
+
+      &.type2 {
+        color: #00095b;
+        margin-top: 50px;
+        font-weight: 500;
+        font-size: 22px;
+        letter-spacing: -0.44px;
+      }
+    }
+  }
+
+  .nav--desc--wrap {
+    margin-top: 80px;
+    margin-bottom: 240px;
+
+    &.lincoln {
+      p {
+        b {
+          color: #DB784D;
+        }
+      }
+    }
+
+    @media(max-width: 1024px) {
+      margin-bottom: 120px;
+    }
+
+    p {
+      color: #333;
+      font-size: 22px;
+      font-weight: 400;
+
+      @media(max-width: 1024px) {
+        font-size: 18px;
+      }
+
+      b {
+        font-weight: 700;
+        color: #00095b;
+      }
+    }
+  }
+
+  .contact--wrap {
+    display: flex;
+    justify-content: space-between;
+    gap: 50px;
+    text-align: center;
+    margin-top: 100px;
+
+    .contact {
+      background-color: #fafafa;
+      padding: 60px 20px 80px;
+      width: 33%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+
+      .quick--more {
+        border-radius: 10px;
+        border: 1px solid rgba(0, 9, 91, 0.50);
+        display: inline-flex;
+        padding: 20px;
+        justify-content: center;
+        align-items: center;
+        gap: 10px;
+        color: #00095B;
+        text-align: center;
+        font-size: 14px;
+        font-style: normal;
+        font-weight: 500;
+        line-height: 100%;
+        /* 14px */
+        letter-spacing: -0.28px;
+        text-transform: uppercase;
+      }
+
+      .mailto--type {
+        font-size: 20px;
+        font-weight: 400;
+        color: #00095B;
+
+        @media(max-width: 768px) {
+          font-size: 18px;
+        }
+      }
+
+      .ico {
+        margin-bottom: 40px;
+        width: 100px;
+        height: 100px;
+        background-color: #f3f3f3;
+        border-radius: 50%;
+        background-image: url(/img/ico--contact1.svg);
+      }
+
+      &:nth-of-type(2) {
+        .ico {
+          background-image: url(/img/ico--contact2.svg);
+        }
+      }
+
+      &:nth-of-type(3) {
+        .ico {
+          background-image: url(/img/ico--contact4.svg);
+        }
+      }
+    }
+
+    h3 {
+      color: #000;
+      font-size: 18px;
+      letter-spacing: -0.36px;
+      margin-bottom: 30px;
+      font-weight: 500;
+    }
+
+    span {
+      margin-bottom: 30px;
+      font-size: 20px;
+      font-weight: 500;
+      letter-spacing: -0.4px;
+      color: #00095b;
+    }
+
+    p {
+      color: #333;
+      font-size: 16px;
+      font-weight: 400;
+      letter-spacing: -0.32px;
+    }
+  }
+
+  .contact--wrap2 {
+    margin-top: 100px;
+    display: flex;
+    gap: 50px;
+
+    .contact {
+      width: 33%;
+      display: flex;
+      padding: 60px 35px;
+      align-items: center;
+      background-color: #fafafa;
+      gap: 30px;
+      border-radius: 30px;
+
+      .ico {
+        min-width: 120px;
+        width: 120px;
+        height: 120px;
+        background-color: #fff;
+        border-radius: 50%;
+        background-image: url(/img/owner/ico--contact1.svg);
+        background-repeat: no-repeat;
+        background-position: center;
+        background-size: 55px 55px;
+      }
+
+      &:nth-child(2) {
+        .ico {
+          background-image: url(/img/owner/ico--contact2.svg);
+        }
+      }
+
+      &:nth-child(3) {
+        .ico {
+          background-image: url(/img/owner/ico--contact4.svg);
+        }
+      }
+
+      .info--wrap {
+        display: flex;
+        flex-direction: column;
+        gap: 20px;
+
+        .quick--more {
+          border-radius: 10px;
+          border: 1px solid rgba(34, 41, 43, 0.50);
+          display: inline-flex;
+          padding: 20px 25px;
+          justify-content: center;
+          align-items: center;
+          gap: 10px;
+          color: #22292B;
+          text-align: center;
+          font-size: 14px;
+          font-style: normal;
+          font-weight: 500;
+          line-height: 100%;
+          /* 14px */
+          letter-spacing: -0.28px;
+          text-transform: uppercase;
+        }
+
+        .mailto--type {
+          font-size: 16px;
+          color: #222;
+        }
+
+        >h3 {
+          color: #111;
+          font-size: 17px;
+          font-weight: 400;
+          line-height: 1;
+        }
+
+        >span {
+          font-size: 28px;
+          font-weight: 700;
+          color: #111;
+          line-height: 1;
+        }
+
+        >p {
+          color: #444;
+          font-size: 17px;
+          font-weight: 400;
+        }
+      }
+    }
+  }
+
+  .text--tab--layout {
+    position: sticky;
+    top: 0px;
+    z-index: 20;
+
+
+    ul {
+      display: flex;
+      align-items: center;
+      overflow-x: auto;
+      justify-content: flex-start;
+      gap: 15px;
+
+      li {
+        cursor: pointer;
+
+        &.actv {
+          a {
+            background-color: #000;
+            border-color: #000;
+            color: #fff;
+          }
+        }
+
+        a {
+          border-radius: 100px;
+          border: 1px solid #E8E8E8;
+          background: #FFF;
+          display: flex;
+          padding: 22px 30px;
+          align-items: center;
+          justify-content: center;
+          color: #444;
+          white-space: nowrap;
+          font-size: 16px;
+          font-style: normal;
+          font-weight: 400;
+          line-height: 100%;
+          /* 16px */
+          letter-spacing: -0.32px;
+          text-transform: uppercase;
+        }
+      }
+    }
+  }
+
+  .owner--inner--content {
+    .thumb {
+      img {
+        width: 100%;
+        object-fit: contain;
+      }
+    }
+
+    .desc--wrapper {
+
+      >h2 {
+        color: #111;
+        font-size: 30px;
+        font-style: normal;
+        font-weight: 500;
+        letter-spacing: -0.6px;
+        text-transform: uppercase;
+      }
+
+      >h3 {
+        color: #111;
+        font-size: 19px;
+        font-weight: 500;
+        letter-spacing: -0.38px;
+      }
+
+      .captions {
+        padding-top: 40px;
+        color: #333;
+        font-size: 18px;
+        font-style: normal;
+        font-weight: 300;
+        line-height: 1.7;
+        /* 18px */
+        letter-spacing: -0.36px;
+        text-transform: uppercase;
+
+        &.fz--17 {
+          font-size: 17px;
+        }
+
+        >h3 {
+          color: #111;
+          font-size: 18px;
+          font-style: normal;
+          font-weight: 400;
+          letter-spacing: -0.36px;
+          text-transform: uppercase;
+        }
+
+        >ul {
+          padding-top: 60px;
+
+          li {
+            position: relative;
+            color: #111;
+            font-size: 17px;
+            font-style: normal;
+            font-weight: 400;
+            //line-height: 100%; /* 17px */
+            line-height: 1.4;
+            letter-spacing: -0.34px;
+            text-transform: uppercase;
+            padding-left: 20px;
+            margin-bottom: 15px;
+
+            &:before {
+              content: '';
+              display: block;
+              width: 3px;
+              height: 3px;
+              background: #111;
+              position: absolute;
+              left: 10px;
+              top: 9px;
+            }
+          }
+        }
+
+        .add--text {
+          color: #333;
+          font-size: 18px;
+          font-style: normal;
+          font-weight: 300;
+          letter-spacing: -0.36px;
+          text-transform: uppercase;
+          padding-top: 60px;
+        }
+      }
+    }
+  }
+
+  .owner--part--wrap {
+    display: flex;
+    flex-direction: column;
+    margin-top: 150px;
+    gap: 50px;
+
+    .owner--part {
+      padding: 120px 100px;
+      background-color: #252525;
+      display: flex;
+      align-items: center;
+      gap: 100px;
+
+      .ico {
+        width: 200px;
+        background-size: 70px 70px;
+        min-width: 200px;
+        border-radius: 100px;
+        background-color: #fff;
+        height: 200px;
+        background-image: url(/img/owner/ico--parts1.svg);
+      }
+
+      &:nth-of-type(2) {
+        .ico {
+          background-image: url(/img/owner/ico--parts2.svg);
+        }
+      }
+
+      .part--content {
+        >h3 {
+          margin-bottom: 40px;
+          color: #fff;
+          font-size: 30px;
+          font-weight: 500;
+          letter-spacing: -0.6px;
+        }
+
+        >p {
+          color: #fff;
+          font-size: 17px;
+          font-weight: 300;
+          letter-spacing: -0.34px;
+          line-height: 1.7;
+        }
+      }
+    }
+  }
+}
+
+.nav--dis--wrap {
+  &.active {
+    .dis--cont {
+      max-height: 1000px;
+    }
+
+    .dis--btn {
+      .ico {
+        transform: rotate(0deg);
+      }
+    }
+  }
+
+  .dis--btn {
+    border-top: 1px solid #D3D3D3;
+    border-bottom: 1px solid #D3D3D3;
+    text-align: center;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 15px;
+    color: #076fed;
+    font-size: 16px;
+    cursor: pointer;
+    font-weight: 500;
+    text-transform: uppercase;
+    padding: 30px;
+    line-height: 1;
+
+    .ico {
+      width: 20px;
+      height: 20px;
+      transition: all 0.3s;
+      transform: rotate(180deg);
+      background-image: url(/img/owner/ico--arrow--blue.svg);
+    }
+  }
+
+  .dis--cont {
+    background-color: #f9f9f9;
+    color: #111;
+    max-height: 0;
+    overflow: hidden;
+    transition: max-height 0.3s;
+    font-size: 16px;
+    font-weight: 300;
+
+    p {
+      padding: 60px 0;
+    }
+  }
+}
+
+.consumable--parts--wrap {
+  .prm--service {
+    >h2 {
+      color: #111;
+      text-align: center;
+      font-size: 30px;
+      font-style: normal;
+      margin-bottom: 40px;
+      font-weight: 500;
+      //line-height: 100%; /* 30px */
+      letter-spacing: -0.6px;
+      text-transform: capitalize;
+    }
+
+    .sub--title {
+      color: #333;
+      text-align: center;
+      font-size: 18px;
+      font-style: normal;
+      font-weight: 300;
+      line-height: 1.7;
+      text-transform: uppercase;
+    }
+
+    .sub--list {
+      gap: 15px;
+      flex-direction: column;
+      align-items: center;
+      max-width: 1024px;
+      margin: 0 auto 70px;
+
+      >li {
+        width: 100%;
+        text-align: left;
+        color: #111;
+        align-items: start;
+        font-size: 17px;
+        font-weight: 300;
+        letter-spacing: -0.34px;
+        position: relative;
+        padding-left: 15px;
+
+        &::before {
+          position: absolute;
+          top: 10px;
+          left: 0;
+          background-color: #111;
+          content: '';
+          width: 3px;
+          height: 3px;
+          display: inline-block;
+
+        }
+      }
+    }
+
+    >ul {
+      display: flex;
+      align-items: flex-start;
+      justify-content: center;
+      padding-top: 70px;
+
+      >li {
+        width: calc(100% / 3);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        flex-direction: column;
+
+        .thumb {
+          width: 150px;
+          height: 150px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          border-radius: 1000px;
+          background: #F6F6F6;
+        }
+
+        .captions {
+          color: #000;
+          text-align: center;
+          font-size: 17px;
+          font-style: normal;
+          font-weight: 300;
+          line-height: 1.7;
+          padding-top: 35px;
+          text-transform: uppercase;
+        }
+      }
+    }
+  }
+
+  .dbl--contents {
+    >ul {
+      display: flex;
+      align-items: flex-start;
+      justify-content: center;
+      gap: 50px;
+
+      li {
+        width: calc(50% - 25px);
+
+        .thumb {
+          overflow: hidden;
+          border-radius: 30px;
+        }
+
+        .desc--wrap {
+
+          h2 {
+            text-align: left;
+            color: #000;
+            font-size: 20px;
+            font-style: normal;
+            font-weight: 500;
+            //line-height: 100%; /* 20px */
+            text-transform: uppercase;
+          }
+
+          >ul {
+            width: 100%;
+            padding-top: 30px;
+
+            li {
+              position: relative;
+              text-align: left;
+              padding-left: 20px;
+              width: 100%;
+              margin-bottom: 10px;
+              color: #222;
+              font-size: 17px;
+              font-style: normal;
+              font-weight: 300;
+              line-height: 1.7;
+              /* 17px */
+              text-transform: uppercase;
+
+              &:before {
+                content: '';
+                display: block;
+                width: 3px;
+                height: 3px;
+                background-color: #000;
+                position: absolute;
+                left: 10px;
+                top: 12px;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .essential--maintenace--wrap {
+    background: #FAFAFA;
+
+    .inner--wrap {
+      .title--visual {
+        >h2 {
+          color: #111;
+          text-align: center;
+          font-size: 30px;
+          font-style: normal;
+          font-weight: 500;
+          //line-height: 100%; /* 30px */
+          letter-spacing: -0.6px;
+          text-transform: capitalize;
+        }
+
+        >.captions {
+          color: #333;
+          text-align: center;
+          font-size: 18px;
+          font-style: normal;
+          font-weight: 300;
+          line-height: 1.7;
+          text-transform: uppercase;
+        }
+      }
+    }
+
+    .columb--thume--3 {
+      >ul {
+        display: flex;
+        align-items: flex-start;
+        justify-content: center;
+        gap: 50px;
+
+        >li {
+          width: calc((100% - 100px) / 3);
+          text-align: center;
+
+          .thumb {
+            overflow: hidden;
+            border-radius: 30px;
+          }
+
+          .captions {
+            padding-top: 30px;
+            color: #000;
+            text-align: center;
+            font-size: 18px;
+            font-style: normal;
+            font-weight: 500;
+            //line-height: 100%; /* 18px */
+            text-transform: uppercase;
+          }
+        }
+      }
+    }
+
+    .column--3 {
+      >ul {
+        display: flex;
+        align-items: flex-start;
+        justify-content: center;
+
+        >li {
+          width: calc(100% / 3);
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          flex-direction: column;
+
+          .thumb {
+            width: 150px;
+            height: 150px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            border-radius: 1000px;
+            background: #F3F3F3;
+          }
+
+          .caption {
+            padding-top: 35px;
+            color: #000;
+            text-align: center;
+            font-size: 17px;
+            font-style: normal;
+            font-weight: 300;
+            //line-height: 100%; /* 17px */
+          }
+        }
+      }
+    }
+  }
+
+  .used--essential--wrap {
+    width: 100%;
+    margin: 0 auto;
+    max-width: 1440px;
+    padding-bottom: 150px;
+
+    .title--visual {
+      >h2 {
+        color: #111;
+        text-align: center;
+        font-size: 30px;
+        font-style: normal;
+        font-weight: 500;
+        //line-height: 100%; /* 30px */
+        letter-spacing: -0.6px;
+        text-transform: capitalize;
+      }
+
+      .captions {
+        color: #333;
+        text-align: center;
+        font-size: 18px;
+        font-style: normal;
+        font-weight: 300;
+        line-height: 1.7;
+        /* 18px */
+        text-transform: uppercase;
+        padding-top: 40px;
+      }
+    }
+
+    .column--3 {
+      padding-top: 70px;
+
+      >ul {
+        display: flex;
+        align-items: flex-start;
+        justify-content: center;
+
+        >li {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          flex-direction: column;
+          text-align: center;
+          width: calc(100% / 3);
+
+          .thumb {
+            width: 150px;
+            height: 150px;
+            border-radius: 1000px;
+            background: #F6F6F6;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+          }
+
+          .caption {
+            padding-top: 35px;
+            color: #000;
+            text-align: center;
+            font-size: 17px;
+            font-style: normal;
+            font-weight: 300;
+            line-height: 1.7;
+            /* 17px */
+            text-transform: uppercase;
+          }
+        }
+      }
+    }
+  }
+
+
+  .column--4 {
+    >h2 {
+      color: #111;
+      text-align: center;
+      font-size: 30px;
+      font-style: normal;
+      font-weight: 700;
+      //line-height: 100%; /* 30px */
+      text-transform: uppercase;
+    }
+
+    >ul {
+      padding-top: 70px;
+      display: flex;
+      gap: 20px;
+
+      li {
+        width: calc((100% - 60px) / 3);
+        border-radius: 30px;
+        border: 1px solid #E0E0E0;
+        background: #FFF;
+        padding: 45px;
+
+        .thumb {
+          width: 56px;
+          height: 56px;
+        }
+
+        >h2 {
+          padding-top: 50px;
+          color: #000;
+          font-size: 20px;
+          font-style: normal;
+          font-weight: 500;
+          line-height: 1.7;
+          /* 20px */
+          letter-spacing: -0.4px;
+          text-transform: uppercase;
+          margin-bottom: 30px;
+        }
+
+        >div {
+          color: #000;
+          font-size: 16px;
+          font-style: normal;
+          font-weight: 300;
+          line-height: 1.7;
+          /* 16px */
+          letter-spacing: -0.32px;
+          text-transform: uppercase;
+        }
+      }
+    }
+  }
+}
+
+.service--card--wrap {
+  display: flex;
+  gap: 100px;
+  align-items: center;
+
+  .img--wrap {
+    width: 50%;
+  }
+
+  .desc--wrap {
+    width: 50%;
+
+    >h3 {
+      margin-bottom: 25px;
+      color: #000;
+      font-size: 30px;
+      font-weight: 500;
+    }
+
+    >p {
+      color: #000;
+      font-size: 18px;
+      font-weight: 400;
+      letter-spacing: -0.36px;
+      margin-bottom: 25px;
+    }
+
+    >ul {
+      display: flex;
+      flex-direction: column;
+      gap: 25px;
+
+      >li {
+        color: #222;
+        font-size: 17px;
+        font-weight: 300;
+        position: relative;
+        padding-left: 13px;
+
+        &::before {
+          top: 10px;
+          left: 0;
+          position: absolute;
+          content: '';
+          width: 3px;
+          height: 3px;
+          background-color: #595959;
+          display: inline-block;
+        }
+      }
+    }
+  }
+}
+
+.dbl--info--cont {
+  >ul {
+    display: flex;
+    gap: 50px;
+
+    >li {
+      background: #FAFAFA;
+      border-radius: 30px;
+      width: 100%;
+      max-width: calc((100% - 25px) / 2);
+      padding: 50px 70px;
+      display: flex;
+      align-items: center;
+      gap: 50px;
+
+      .thumb {
+        width: 100px;
+        height: 100px;
+        min-width: 100px;
+        border-radius: 100px;
+        background: #0AF;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+
+        &.lincoln {
+          background-color: #DB784D;
+        }
+      }
+
+      .desc {
+        >h2 {
+          color: #111;
+          font-size: 20px;
+          font-style: normal;
+          font-weight: 700;
+          //line-height: 100%; /* 20px */
+          letter-spacing: -0.4px;
+          text-transform: uppercase;
+          margin-bottom: 24px;
+        }
+
+        .phone {
+          color: #00095B;
+          font-size: 20px;
+          font-style: normal;
+          font-weight: 700;
+          //line-height: 100%; /* 20px */
+          letter-spacing: -0.4px;
+          text-transform: uppercase;
+
+          &.lincoln {
+            color: #e0571c;
+          }
+        }
+      }
+    }
+  }
+}
+
+.tfs--drop--menus {
+  >ul {
+    >li {
+      .title {
+        margin-top: -1px;
+        width: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        font-weight: 700;
+        border-top: 1px solid #DBDBDB;
+        border-bottom: 1px solid #DBDBDB;
+        background: #F8F8F8;
+        padding: 32px 25px;
+        position: relative;
+        cursor: pointer;
+
+        &:after {
+          content: '';
+          display: block;
+          width: 24px;
+          height: 24px;
+          background: url(/img/ico--chv--down.svg) no-repeat center;
+          position: absolute;
+          top: 50%;
+          transform: translateY(-50%) rotate(180deg);
+          right: 25px;
+        }
+      }
+
+      &.open {
+        .title {
+          &:after {
+            transform: translateY(-50%) rotate(0deg);
+          }
+        }
+
+        .drop--contents {
+          display: block;
+        }
+      }
+
+      .drop--contents {
+        padding: 50px 25px;
+        display: none;
+
+        &.type2 {
+          padding: 50px;
+        }
+
+        h2 {
+          color: #111;
+          font-size: 18px;
+          font-style: normal;
+          font-weight: 500;
+          line-height: 100%;
+          /* 18px */
+          letter-spacing: -0.36px;
+          text-transform: uppercase;
+          margin-bottom: 20px;
+          position: relative;
+          padding-left: 20px;
+
+          &::before {
+            content: '';
+            display: block;
+            width: 3px;
+            height: 3px;
+            background-color: #111;
+            position: absolute;
+            top: 7px;
+            left: 10px;
+          }
+        }
+
+        h3 {
+          color: #111;
+          font-size: 18px;
+          font-style: normal;
+          font-weight: 500;
+          //line-height: 100%; /* 18px */
+          letter-spacing: -0.36px;
+          text-transform: uppercase;
+          margin-bottom: 20px;
+        }
+
+        ul {
+          >li {
+            color: #333;
+            font-size: 17px;
+            font-style: normal;
+            font-weight: 300;
+            //line-height: 100%; /* 17px */
+            letter-spacing: -0.34px;
+            text-transform: lowercase;
+            margin-bottom: 20px;
+            position: relative;
+            padding-left: 20px;
+
+            &:before {
+              content: '';
+              display: block;
+              width: 3px;
+              height: 3px;
+              background-color: #333;
+              position: absolute;
+              top: 10px;
+              left: 10px;
+            }
+
+            &:last-child {
+              margin-bottom: 0px;
+            }
+          }
+        }
+
+        div {
+          color: #333;
+          font-size: 17px;
+          font-style: normal;
+          font-weight: 300;
+          line-height: 1.7;
+          /* 17px */
+          letter-spacing: -0.34px;
+          text-transform: lowercase;
+          margin-bottom: 55px;
+          padding-left: 20px;
+
+          &.solo {
+            margin-bottom: 0px;
+            padding-left: 0px;
+          }
+        }
+
+        p {
+          color: #333;
+          font-size: 17px;
+          font-style: normal;
+          font-weight: 300;
+          line-height: 1.7;
+          letter-spacing: -0.34px;
+          text-transform: lowercase;
+          position: relative;
+          padding-left: 25px;
+
+          &:before {
+            content: '*';
+            display: block;
+            position: absolute;
+            left: 10px;
+            top: 2px;
+          }
+        }
+
+        .drop--part--wrap {
+          padding: 0;
+          margin-bottom: 0;
+          align-items: center;
+          display: flex;
+          gap: 55px;
+
+          .img--wrap {
+            width: 258px;
+            min-width: 258px;
+            padding: 0;
+            margin: 0;
+          }
+
+          .desc--wrap {
+            padding: 0;
+            margin: 0;
+
+            ul {
+              li {
+                color: #111;
+                line-height: 1.7;
+
+                &::before {
+                  top: 12px;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+
+/* 버튼 리스트 */
+.location--btn {
+  color: #111;
+  font-size: 15px;
+  font-style: normal;
+  font-weight: 500;
+  line-height: 100%;
+  /* 15px */
+  letter-spacing: -0.3px;
+  text-transform: uppercase;
+  border-radius: 10px;
+  border: 1px solid rgba(0, 0, 0, 0.50);
+  display: inline-flex;
+  align-items: center;
+  border-radius: 10px;
+  justify-content: center;
+  padding: 13px 18px;
+  gap: 7px;
+}
+
+
+.sticky--banner {
+  position: sticky;
+  top: 0px;
+  z-index: 1;
+}
+
+.outer--wrapper {
+  position: relative;
+  z-index: 2;
+  background-color: #fff;
+  padding-top: 100px;
+
+  @media (max-width: 768px) {
+    padding-top: 50px;
+  }
+
+}
+
+.about--wrap {
+
+  .visual--banner--02 {
+    .thumb--wrap {
+      width: 100%;
+      display: flex;
+      align-items: flex-start;
+      gap: 50px;
+      row-gap: 120px;
+      flex-wrap: wrap;
+
+      @media (max-width: 768px) {
+        row-gap: 50px;
+      }
+
+      .thumb--item {
+        width: calc(50% - 25px);
+
+        @media (max-width: 768px) {
+          width: 100%;
+        }
+
+        .desc {
+          .t--title {
+            color: #000;
+            font-size: 20px;
+            font-style: normal;
+            font-weight: 500;
+            line-height: 100%;
+            /* 20px */
+            text-transform: uppercase;
+            padding-top: 30px;
+          }
+
+          .captions {
+            margin-top: 30px;
+            color: #222;
+            font-size: 17px;
+            font-style: normal;
+            font-weight: 300;
+            line-height: 1.7;
+            text-transform: uppercase;
+
+          }
+        }
+
+        .thumb {
+          border-radius: 30px;
+          overflow: hidden;
+          width: 100%;
+
+          img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+          }
+        }
+      }
+    }
+  }
+
+  .our--vision {
+    background: #FAFAFA;
+    padding-bottom: 150px;
+
+    @media (max-width:768px) {
+      padding-bottom: 50px;
+    }
+
+    .vision--wrap {
+      padding-top: 70px;
+
+      >ul {
+        max-width: 1440px;
+        display: flex;
+        margin: 0 auto;
+        gap: 50px;
+        row-gap: 50px;
+
+        li {
+          width: calc((100% - 150px) / 4);
+
+          @media(max-width:1440px) {
+            width: calc((100% - 50px) / 2);
+          }
+
+          text-align: center;
+          display: flex;
+          flex-direction: column;
+          justify-content: flex-start;
+          align-items: center;
+
+          .thumb {
+            border-radius: 1000px;
+            background: #F3F3F3;
+            width: 150px;
+            height: 150px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+
+          }
+
+          .desc {
+            padding-top: 40px;
+
+            h2 {
+              color: #000;
+              text-align: center;
+              font-size: 19px;
+              font-style: normal;
+              font-weight: 500;
+              line-height: 100%;
+              /* 19px */
+              text-transform: uppercase;
+            }
+
+            .captions {
+              color: #333;
+              text-align: center;
+              font-size: 16px;
+              font-style: normal;
+              font-weight: 300;
+              line-height: 1.7;
+              padding-top: 30px;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .strong--business {
+    text-align: center;
+    padding-bottom: 150px;
+
+    @media(max-width:768px) {
+      padding-bottom: 50px;
+    }
+
+    .invest--more--btn {
+      color: #FFF;
+      font-size: 16px;
+      font-style: normal;
+      font-weight: 500;
+      line-height: 100%;
+      /* 16px */
+      text-transform: capitalize;
+      display: inline-flex;
+      padding: 23px 40px;
+      justify-content: center;
+      align-items: center;
+      gap: 10px;
+      border-radius: 100px;
+      background: #0AF;
+      margin-top: 70px;
+
+      @media(max-width:768px) {
+        margin-top: 35px;
+      }
+    }
+  }
+}
+
+.grid--list--items {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 60px;
+
+  .grid--items {
+    width: calc((100% - 120px) / 3);
+    border: 1px solid #E5E5E5;
+    background: #FFF;
+    display: flex;
+    padding: 70px 50px;
+    flex-direction: column;
+    align-items: center;
+    gap: 40px;
+    align-self: stretch;
+
+    .thumb {
+      border-radius: 1000px;
+      background: #F3F3F3;
+      width: 110px;
+      height: 110px;
+      border-radius: 110px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    .desc {
+      >h2 {
+        color: #000;
+        text-align: center;
+        font-size: 22px;
+        font-style: normal;
+        font-weight: 700;
+        line-height: 100%;
+        /* 22px */
+        letter-spacing: -0.44px;
+        text-transform: capitalize;
+      }
+
+      >div {
+        color: #444;
+        text-align: center;
+        font-size: 16px;
+        font-style: normal;
+        font-weight: 400;
+        line-height: 1.7;
+        letter-spacing: -0.32px;
+        text-transform: capitalize;
+        margin-top: 25px;
+      }
+    }
+  }
+}
+
+.grid--banner--wrapper {
+  &.type--2 {
+    padding: 95px 60px;
+
+    .grid--items {
+      padding: 0px;
+    }
+  }
+
+  .grid--items {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 80px;
+    padding-top: 80px;
+    padding-bottom: 110px;
+    width: 100%;
+
+    @media(max-width:1024px) {
+      flex-direction: column;
+
+      .grid--desc {
+        max-width: 100% !important;
+      }
+
+      .grid--banner {
+        max-width: 100% !important;
+        min-width: 100% !important;
+
+        img {
+          width: 100%;
+        }
+      }
+    }
+
+    @media(max-width:1440px) {
+      padding-left: 0px;
+      padding-right: 0px;
+    }
+
+    &.dark {
+      background: #FAFAFA;
+    }
+
+    &.revers {
+      flex-direction: row-reverse;
+
+      @media(max-width:1024px) {
+        flex-direction: column;
+      }
+    }
+
+    @media(max-width:768px) {
+      gap: 40px;
+    }
+
+    .grid--banner {
+      max-width: 650px;
+      min-width: 650px;
+    }
+
+    .grid--desc {
+      max-width: 710px;
+
+      h2 {
+        color: #000;
+        font-size: 30px;
+        font-style: normal;
+        font-weight: 500;
+        line-height: 100%;
+        /* 30px */
+        text-transform: uppercase;
+        margin-bottom: 35px;
+      }
+
+      .captions {
+        color: #000;
+        font-size: 18px;
+        font-style: normal;
+        font-weight: 300;
+        line-height: 1.7;
+        white-space: normal;
+        text-transform: uppercase;
+
+        >ul {
+          >li {
+            color: #333;
+            font-size: 18px;
+            font-style: normal;
+            font-weight: 700;
+            //line-height: 100%; /* 18px */
+            text-transform: uppercase;
+            position: relative;
+            padding-left: 10px;
+            margin-bottom: 15px;
+
+            &.not--sq {
+              li {
+                color: #444;
+                font-size: 17px;
+                font-style: normal;
+                font-weight: 400;
+                //line-height: 100%; /* 17px */
+                text-transform: uppercase;
+              }
+
+              &::before {
+                display: none;
+              }
+            }
+
+            &:before {
+              content: '';
+              display: inline-flex;
+              width: 3px;
+              height: 3px;
+              background: #595959;
+              position: absolute;
+              left: 0px;
+              top: 10px;
+            }
+          }
+        }
+      }
+
+      .list--content {
+        >ul {
+          >li {
+            color: #444;
+            font-size: 18px;
+            font-style: normal;
+            font-weight: 400;
+            line-height: 1.5;
+            text-transform: lowercase;
+            margin-bottom: 10px;
+            position: relative;
+            display: flex;
+            align-items: center;
+            justify-content: flex-start;
+            padding-left: 10px;
+
+            &:before {
+              content: '';
+              display: inline-flex;
+              width: 3px;
+              height: 3px;
+              background: #595959;
+              position: absolute;
+              left: 0px;
+              top: 12px;
+            }
+
+            &:last-child {
+              margin-bottom: 0px;
+            }
+          }
+        }
+      }
+    }
+
+  }
+}
+
+.g-layout {
+  &.active {
+    position: relative;
+    z-index: 1100;
+  }
+}
+
+
+.owner--service {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 65px;
+  padding: 60px;
+  background: #FAFAFA;
+
+  .thumb {}
+
+  ;
+
+  .desc {
+    max-width: 580px;
+
+    h2 {
+      color: #111;
+      font-family: "Lincoln Proxima Nova";
+      font-size: 30px;
+      font-style: normal;
+      font-weight: 700;
+      line-height: 100%;
+      /* 30px */
+      letter-spacing: -0.6px;
+      text-transform: uppercase;
+    }
+
+    >div {
+      color: #444;
+      font-family: "Lincoln Proxima Nova";
+      font-size: 17px;
+      font-style: normal;
+      font-weight: 400;
+      line-height: 1.7;
+      letter-spacing: -0.34px;
+      margin-top: 20px;
+    }
+  }
+}
+
+.owner--list--content {
+  .title {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    h2 {
+      color: #222;
+      text-align: center;
+      font-size: 35px;
+      font-style: normal;
+      font-weight: 700;
+      //line-height: 100%; /* 35px */
+      letter-spacing: -0.7px;
+      text-transform: uppercase;
+    }
+  }
+
+  .list--content {
+    display: flex;
+    align-items: flex-start;
+    justify-content: space-between;
+    padding-top: 70px;
+    gap: 80px;
+
+    >ul {
+      width: calc((100% - 160px) / 3);
+
+      @media(max-width:1024px) {
+        width: 100%;
+      }
+
+      >li {
+        position: relative;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        gap: 10px;
+        margin-bottom: 15px;
+
+        &:last-child {
+          margin-bottom: 0px;
+        }
+
+        &:before {
+          content: '';
+          width: 3px;
+          height: 3px;
+          display: inline-flex;
+          background: #595959;
+        }
+      }
+    }
+  }
+}
+
+
+.outer--desc {
+  padding-top: 70px;
+
+  >ul {
+    >li {
+      position: relative;
+      color: #444;
+      font-size: 18px;
+      font-style: normal;
+      font-weight: 400;
+      //line-height: 100%; /* 18px */
+      text-transform: lowercase;
+      padding-left: 10px;
+      margin-bottom: 40px;
+
+      &:last-child {
+        margin-bottom: 0px;
+      }
+
+      &:before {
+        content: '';
+        display: inline-flex;
+        width: 3px;
+        height: 3px;
+        background: #595959;
+        position: absolute;
+        top: 8px;
+        left: 0px;
+      }
+    }
+  }
+}
+
+
+.column--caption3 {
+  max-width: 1440px;
+  margin: 0 auto;
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+
+  >ul {
+    width: calc((100% - 160px) / 3);
+
+    >li {
+      position: relative;
+      padding-left: 12px;
+      margin-bottom: 20px;
+
+      &:last-child {
+        margin-bottom: 0px;
+      }
+
+      &.not--sq {
+        padding-left: 10px;
+
+        &::before {
+          display: none;
+        }
+
+        >ul {
+          li {
+            padding-left: 12px;
+            color: #444;
+            font-size: 17px;
+            font-style: normal;
+            font-weight: 400;
+            line-height: 1.7;
+            text-transform: uppercase;
+            position: relative;
+
+            &:before {
+              content: '-';
+              position: absolute;
+              top: -1px;
+              left: 0px;
+            }
+          }
+        }
+      }
+
+      &:before {
+        content: '';
+        display: block;
+        width: 3px;
+        height: 3px;
+        background: #595959;
+        position: absolute;
+        top: 9px;
+        left: 0px;
+      }
+    }
+  }
+}
+
+.inner--dbl--content {
+  padding-top: 50px;
+
+  >ul {
+    display: flex;
+    align-items: flex-start;
+    justify-content: center;
+    gap: 80px;
+
+    li {
+      .thumb {
+        border-radius: 20px;
+        border-radius: 20px;
+        border: 1px solid #D9D9D9;
+        background: #FFF;
+        overflow: hidden;
+      }
+
+      a {
+        margin-top: 30px;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        height: 40px;
+        padding: 15px 20px;
+        justify-content: center;
+        align-items: center;
+        gap: 5px;
+        border-radius: 100px;
+        background: #111;
+        color: #FFF;
+        font-size: 14px;
+        font-style: normal;
+        font-weight: 700;
+        line-height: 100%;
+        /* 14px */
+        text-transform: capitalize;
+      }
+    }
+  }
+}
+
+.lincoln {
+  .link {
+    color: #DB784D;
+    font-size: 20px;
+    font-style: normal;
+    font-weight: 400;
+    line-height: 100%;
+    letter-spacing: -0.4px;
+    text-decoration-line: underline;
+    text-decoration-style: solid;
+    text-decoration-skip-ink: auto;
+    text-decoration-thickness: auto;
+    text-underline-offset: auto;
+  }
+
+  .lincoln--prm--service {
+    background: #FAFAFA;
+  }
+
+  .btn--sky {
+    background: #DB784D;
+
+    &.reverse {
+      background: #fff;
+      border: 1px solid #DB784D;
+      color: #DB784D;
+    }
+  }
+
+  .title--visual {
+    h2 {
+      font-weight: 700;
+    }
+  }
+
+  .ovwner--wrapper {
+    .title--visual {
+      .desc {
+        font-weight: 400;
+      }
+    }
+  }
+
+  .tfs--drop--menus {
+    >ul {
+      >li {
+        .drop--contents {
+          p {
+            color: #333;
+            font-size: 17px;
+            font-style: normal;
+            font-weight: 400;
+            //line-height: 100%; /* 17px */
+            letter-spacing: -0.34px;
+            text-transform: uppercase;
+            padding-left: 10px;
+
+            &::before {
+              display: none;
+            }
+          }
+
+          ul {
+            >li {
+              color: #444;
+              font-size: 17px;
+              font-style: normal;
+              font-weight: 400;
+              //line-height: 100%; /* 17px */
+              letter-spacing: -0.34px;
+              text-transform: uppercase;
+            }
+          }
+
+          h4 {
+            color: #333;
+            font-size: 17px;
+            font-style: normal;
+            font-weight: 400;
+            line-height: 100%;
+            /* 17px */
+            letter-spacing: -0.34px;
+            text-transform: uppercase;
+            position: relative;
+            padding-left: 20px;
+            margin-bottom: 10px;
+
+            &:before {
+              content: '';
+              display: block;
+              width: 3px;
+              height: 3px;
+              background: #313131;
+              position: absolute;
+              top: 5px;
+              left: 5px;
+            }
+          }
+
+          h2 {
+            color: #333;
+            font-size: 17px;
+            font-style: normal;
+            font-weight: 400;
+            line-height: 100%;
+            /* 17px */
+            letter-spacing: -0.34px;
+            text-transform: uppercase;
+
+            &:before {
+              display: none;
+            }
+
+            padding-left:0px;
+          }
+
+          div {
+            color: #444;
+            font-size: 17px;
+            font-style: normal;
+            font-weight: 400;
+            line-height: 1.6;
+            letter-spacing: -0.34px;
+
+            &.phone {
+              color: #E0571C;
+              font-size: 25px;
+              font-style: normal;
+              font-weight: 700;
+              line-height: 100%;
+              /* 25px */
+              letter-spacing: -0.5px;
+              padding-left: 0px;
+              text-transform: uppercase;
+              margin-bottom: 0px;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .nav--dis--wrap {
+    p {
+      color: #333;
+      font-size: 16px;
+      font-style: normal;
+      font-weight: 400;
+      //line-height: 100%; /* 16px */
+      text-transform: uppercase;
+    }
+
+    .dis--btn {
+      color: #222;
+      font-size: 17px;
+      font-style: normal;
+      font-weight: 700;
+      line-height: 100%;
+      /* 17px */
+      text-transform: uppercase;
+    }
+  }
+
+  .breadcrumbs--wrap {
+    background: #111;
+
+    .menu--wrap {
+      .sub--menu--wrap {
+        .nav--wrap {
+          .sub--menu2 {
+            &.active {
+              color: #DB784D !important;
+            }
+
+            &:hover {
+              color: #DB784D !important;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .swiper--banner--wrapper3 {
+    .top--text--wrap {
+      >div {
+        font-weight: 400;
+      }
+    }
+  }
+
+  .car--price--small--pic--wrap {
+    >div {
+      .car--list--wrap {
+        ul {
+          li {
+            .desc--wrap {
+              .price--wrap {
+                font-weight: 400;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .models--visual--grid {
+    ul {
+      li {
+        .desc--wrap {
+          h2 {
+            font-size: 26px;
+            font-weight: 700;
+          }
+
+          .captions {
+            font-weight: 400;
+          }
+        }
+      }
+    }
+  }
+
+  .caution--text--foot {
+    .caution--text {
+      font-weight: 400;
+    }
+  }
+
+  .grid--banner--wrapper {
+    .grid--items {
+      .grid--desc {
+        max-width: 710px;
+        width: 100%;
+
+        h2 {
+          color: #000;
+          font-size: 30px;
+          font-style: normal;
+          font-weight: 700;
+          line-height: 1.4;
+          text-transform: capitalize;
+        }
+
+        .captions {
+          font-weight: 400;
+        }
+      }
+    }
+  }
+}
+
+// 전체 메뉴 (사이트맵)
+.all--menu--wrapper {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  background: #fff;
+  z-index: 1002;
+  transform: translateY(-100%);
+  transition: transform 0.4s ease;
+  overflow-y: auto;
+
+  &.active {
+    transform: translateY(0);
+  }
+
+  .close--btn {
+    position: absolute;
+    top: 25px;
+    right: 0px;
+    width: 40px;
+    height: 40px;
+    background: none;
+    border: none;
+    cursor: pointer;
+    z-index: 10;
+
+    svg {
+      width: 100%;
+      height: 100%;
+    }
+  }
+
+  .all--menu--inner {
+    max-width: 1440px;
+    margin: 0 auto;
+    position: relative;
+    padding: 35px 0 100px;
+
+    .main--menu--list {
+      display: flex;
+      gap: 60px;
+
+      li {
+        width: 20%;
+
+        h3 {
+          font-size: 16px;
+          font-weight: 700;
+          margin-bottom: 50px;
+          line-height: 1;
+          color: #00095B;
+          text-transform: uppercase;
+        }
+      }
+    }
+
+    .sitemap--list {
+      display: flex;
+      gap: 60px;
+      max-height: 60vh;
+      overflow-y: auto;
+
+      >ul {
+        width: 20%;
+        display: flex;
+        flex-direction: column;
+        gap: 20px;
+
+        li {
+          width: 100%;
+          display: flex;
+
+          &:first-child {
+            a {
+              padding-top: 0;
+
+              &::before {
+                top: 10px;
+              }
+            }
+          }
+
+          a {
+            padding-top: 15px;
+            color: #000;
+            font-size: 16px;
+            padding-left: 20px;
+            width: 100%;
+            font-weight: 500;
+            position: relative;
+
+            &::before {
+              position: absolute;
+              content: '';
+              display: inline-block;
+              width: 5px;
+              height: 5px;
+              background-color: #333;
+              top: 25px;
+              left: 0;
+            }
+
+            &.sub--type {
+              color: #444;
+              font-size: 15px;
+              padding: 0 20px;
+              font-weight: 400;
+
+              &::before {
+                display: none;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+// 햄버거 버튼 active 상태 (X 모양)
+.hmb--btn {
+  position: relative;
+  z-index: 1002;
+
+  svg line {
+    transition: all 0.3s ease;
+  }
+
+  &.active {
+    svg {
+      line:nth-child(1) {
+        transform: translateY(11px) rotate(45deg);
+        transform-origin: 20px 8px;
+      }
+
+      line:nth-child(2) {
+        opacity: 0;
+      }
+
+      line:nth-child(3) {
+        transform: translateY(-11px) rotate(-45deg);
+        transform-origin: 20px 30px;
+      }
+    }
+  }
+}
+
+// 모바일 메뉴
+.mobile--menu--wrapper {
+  display: none;
+  position: fixed;
+  top: 0;
+  right: 0;
+  width: 100%;
+  height: 100vh;
+  background: #fff;
+  z-index: 999;
+  transform: translateX(100%);
+  transition: transform 0.4s ease;
+  overflow-y: auto;
+
+  &.active {
+    transform: translateX(0);
+  }
+
+  .mobile--menu--inner {
+    padding: 72px 20px 60px;
+
+    .mobile--menu--list {
+      >li {
+
+        &.active {
+          .menu--title {
+            span {
+              color: #076FED;
+            }
+
+            .arrow {
+              background-image: url(/img/ico--minus.svg);
+            }
+          }
+
+          .mobile--sub--menu {
+            max-height: 1000px;
+            padding: 25px 0;
+            border-bottom: 1px solid #b2b2b2;
+          }
+        }
+
+        .menu--title {
+          border-bottom: 1px solid #b2b2b2;
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          padding: 30px 0;
+          cursor: pointer;
+
+          span {
+            font-size: 15px;
+            font-weight: 700;
+            color: #000;
+            text-transform: uppercase;
+          }
+
+          .arrow {
+            width: 22px;
+            height: 22px;
+            transition: all 0.3s;
+            background-image: url(/img/ico--plus.svg);
+          }
+        }
+
+        .mobile--sub--menu {
+          max-height: 0;
+          overflow: hidden;
+          transition: max-height 0.3s ease, padding 0.3s ease;
+          padding: 0;
+          display: flex;
+          flex-direction: column;
+          gap: 15px;
+
+          &.car--menu {
+            text-align: center;
+            flex-direction: row;
+            flex-wrap: wrap;
+            gap: 15px;
+
+            li {
+              width: calc((100% - 30px) / 3);
+
+              a {
+                display: flex;
+                flex-direction: column;
+                gap: 15px;
+                font-size: 14px;
+                color: #000;
+                font-weight: 400;
+                padding: 0;
+              }
+            }
+          }
+
+          li {
+            display: flex;
+            width: 100%;
+
+            &:first-child {
+              h4 {
+                margin-top: 0;
+              }
+            }
+
+            .lincoln--link {
+              padding-left: 20px;
+              position: relative;
+              font-size: 16px;
+              font-weight: 500;
+              color: #000;
+
+              &::before {
+                content: '';
+                position: absolute;
+                width: 5px;
+                top: 8px;
+                left: 0;
+                height: 5px;
+                background-color: #333;
+              }
+            }
+
+            h4 {
+              padding-left: 20px;
+              position: relative;
+              font-size: 16px;
+              font-weight: 500;
+              margin-bottom: 5px;
+              margin-top: 20px;
+
+              >a {
+                color: #000;
+                font-size: 16px;
+                font-weight: 500;
+              }
+
+              &::before {
+                content: '';
+                position: absolute;
+                width: 5px;
+                top: 8px;
+                left: 0;
+                height: 5px;
+                background-color: #333;
+              }
+            }
+
+            a {
+              font-size: 15px;
+              width: 100%;
+              padding-left: 20px;
+              color: #444;
+              font-weight: 400;
+
+              &:hover {}
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+
+.text--justify--left {
+  text-align: left !important;
+}
+
+.text--center {
+  text-align: center !important;
+}
+
+
+.service--center--ford {
+  td {
+    text-align: left !important;
+    padding-left: 20px !important;
+
+    &.text--center,
+    &:last-child {
+      text-align: center !important;
+
+      &.text--justify--left {
+        text-align: left !important;
+      }
+    }
+  }
+
+  a {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 50px;
+    background: #076FED;
+    color: #FFF;
+    height: 30px;
+    font-size: 13px;
+    font-style: normal;
+    font-weight: 500;
+    letter-spacing: -0.26px;
+    text-transform: uppercase;
+    padding: 0 16px;
+    gap: 9px;
+
+    &:before {
+      content: "";
+      width: 15px;
+      min-width: 15px;
+      height: 14px;
+      display: inline-flex;
+      background: url(/img/ico--res--service.svg);
+      background-size: contain;
+      position: relative;
+      top: -1px;
+    }
+  }
+}
+
+.lincoln {
+  .service--center--ford {
+    a {
+      background-color: #DB784D;
+    }
+  }
+}

+ 0 - 0
app/assets/scss/media.scss


+ 142 - 0
app/assets/scss/style.scss

@@ -0,0 +1,142 @@
+// Pretendard Font Face
+@font-face {
+    font-family: 'Pretendard';
+    font-weight: 100;
+    font-style: normal;
+    font-display: swap;
+    src: url('/font/Pretendard-Thin.woff2') format('woff2'),
+         url('/font/Pretendard-Thin.woff') format('woff');
+}
+
+@font-face {
+    font-family: 'Pretendard';
+    font-weight: 200;
+    font-style: normal;
+    font-display: swap;
+    src: url('/font/Pretendard-ExtraLight.woff2') format('woff2'),
+         url('/font/Pretendard-ExtraLight.woff') format('woff');
+}
+
+@font-face {
+    font-family: 'Pretendard';
+    font-weight: 300;
+    font-style: normal;
+    font-display: swap;
+    src: url('/font/Pretendard-Light.woff2') format('woff2'),
+         url('/font/Pretendard-Light.woff') format('woff');
+}
+
+@font-face {
+    font-family: 'Pretendard';
+    font-weight: 400;
+    font-style: normal;
+    font-display: swap;
+    src: url('/font/Pretendard-Regular.woff2') format('woff2'),
+         url('/font/Pretendard-Regular.woff') format('woff');
+}
+
+@font-face {
+    font-family: 'Pretendard';
+    font-weight: 500;
+    font-style: normal;
+    font-display: swap;
+    src: url('/font/Pretendard-Medium.woff2') format('woff2'),
+         url('/font/Pretendard-Medium.woff') format('woff');
+}
+
+@font-face {
+    font-family: 'Pretendard';
+    font-weight: 600;
+    font-style: normal;
+    font-display: swap;
+    src: url('/font/Pretendard-SemiBold.woff2') format('woff2'),
+         url('/font/Pretendard-SemiBold.woff') format('woff');
+}
+
+@font-face {
+    font-family: 'Pretendard';
+    font-weight: 700;
+    font-style: normal;
+    font-display: swap;
+    src: url('/font/Pretendard-Bold.woff2') format('woff2'),
+         url('/font/Pretendard-Bold.woff') format('woff');
+}
+
+@font-face {
+    font-family: 'Pretendard';
+    font-weight: 800;
+    font-style: normal;
+    font-display: swap;
+    src: url('/font/Pretendard-ExtraBold.woff2') format('woff2'),
+         url('/font/Pretendard-ExtraBold.woff') format('woff');
+}
+
+@font-face {
+    font-family: 'Pretendard';
+    font-weight: 900;
+    font-style: normal;
+    font-display: swap;
+    src: url('/font/Pretendard-Black.woff2') format('woff2'),
+         url('/font/Pretendard-Black.woff') format('woff');
+}
+
+html,
+body,
+button,
+input,
+select,
+textarea {
+    font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+}
+
+// Utility classes for padding and margin (1-100)
+@for $i from 0 through 200 {
+
+    // Padding
+    .pt--#{$i} {
+        padding-top: #{$i}px !important;
+    }
+
+    .pr--#{$i} {
+        padding-right: #{$i}px !important;
+    }
+
+    .pb--#{$i} {
+        padding-bottom: #{$i}px !important;
+    }
+
+    .pl--#{$i} {
+        padding-left: #{$i}px !important;
+    }
+
+    .p--#{$i} {
+        padding: #{$i}px !important;
+    }
+
+    // Margin
+    .mt--#{$i} {
+        margin-top: #{$i}px !important;
+    }
+
+    .mr--#{$i} {
+        margin-right: #{$i}px !important;
+    }
+
+    .mb--#{$i} {
+        margin-bottom: #{$i}px !important;
+    }
+
+    .ml--#{$i} {
+        margin-left: #{$i}px !important;
+    }
+
+    .m--#{$i} {
+        margin: #{$i}px !important;
+    }
+}
+
+br {
+    &.mo {
+        display: none;
+    }
+}

+ 214 - 0
app/assets/scss/suneditor-content.scss

@@ -0,0 +1,214 @@
+// SunEditor 콘텐츠 뷰 전용 스타일
+.suneditor-content {
+    // 기본 색상 상속
+    * {
+        color: inherit;
+    }
+
+    // SunEditor 이미지 컨테이너
+    .se-component {
+        margin: 0px 0;
+        display: inline-block;
+
+        &.se-image-container {
+            &.__se__float-center{
+                text-align: center;
+                margin:0 auto;
+                figure{
+                    margin:0 auto;
+                }
+            }
+            &.__se__float-left {
+                float: left;
+                margin-right: 20px;
+            }
+            &.__se__float-right {
+                float: right;
+                margin-left: 20px;
+            }
+            &.__se__float-none {
+                display: block;
+                margin-left: auto;
+                margin-right: auto;
+                text-align: center;
+            }
+        }
+    }
+
+    // 폰트 태그 스타일 초기화 (다크 배경 대응)
+    font {
+        &[face] {
+            font-family: inherit !important;
+        }
+        &[color="#000000"],
+        &[color="rgb(0, 0, 0)"],
+        &[color="black"] {
+            color: rgba(252, 252, 253, 0.9) !important;
+        }
+    }
+
+    // 이미지
+    img {
+        max-width: 100%;
+        height: auto;
+        display: inline-block;
+        vertical-align: middle;
+    }
+
+    // 문단
+    p {
+        font-size: 16px;
+        line-height: 1.8;
+        margin-bottom: 16px;
+        color: rgba(252, 252, 253, 0.9);
+    }
+
+    // div
+    div {
+        font-size: 16px;
+        line-height: 1.8;
+        color: rgba(252, 252, 253, 0.9);
+    }
+
+    // span
+    span {
+        font-size: inherit;
+        line-height: inherit;
+
+        &[style*="color: rgb(0, 0, 0)"],
+        &[style*="color:#000000"],
+        &[style*="color: black"] {
+            color: rgba(252, 252, 253, 0.9) !important;
+        }
+    }
+
+    // 제목
+    h1, h2, h3, h4, h5, h6 {
+        margin-top: 32px;
+        margin-bottom: 16px;
+        font-weight: 600;
+        color: rgba(252, 252, 253, 1);
+    }
+    h1 { font-size: 36px; }
+    h2 { font-size: 32px; }
+    h3 { font-size: 28px; }
+    h4 { font-size: 24px; }
+    h5 { font-size: 20px; }
+    h6 { font-size: 18px; }
+
+    // 링크
+    a {
+        color: #fff;
+        text-decoration: underline;
+        &:hover {
+            opacity: 0.8;
+        }
+    }
+
+    // 리스트
+    ul, ol {
+        margin-left: 20px;
+        margin-bottom: 16px;
+        padding-left: 20px;
+
+        li {
+            font-size: 16px;
+            line-height: 1.8;
+            margin-bottom: 8px;
+            color: rgba(252, 252, 253, 0.9);
+        }
+    }
+
+    // 인용문
+    blockquote {
+        border-left: 4px solid rgba(252, 252, 253, 0.3);
+        padding-left: 20px;
+        margin: 20px 0;
+        color: rgba(252, 252, 253, 0.7);
+    }
+
+    // 테이블
+    table {
+        width: 100%;
+        border-collapse: collapse;
+        margin: 20px 0;
+
+        th, td {
+            border: 1px solid rgba(252, 252, 253, 0.3);
+            padding: 12px;
+            text-align: left;
+            color: rgba(252, 252, 253, 0.9);
+        }
+
+        th {
+            background-color: rgba(255, 255, 255, 0.1);
+            font-weight: 600;
+        }
+    }
+
+    // iframe (비디오)
+    iframe {
+        max-width: 100%;
+        margin: 20px 0;
+        display: block;
+    }
+
+    // 수평선
+    hr {
+        border: none;
+        border-top: 1px solid rgba(252, 252, 253, 0.3);
+        margin: 24px 0;
+    }
+
+    // 코드
+    pre, code {
+        background-color: rgba(255, 255, 255, 0.05);
+        padding: 4px 8px;
+        border-radius: 4px;
+        font-family: monospace;
+        color: rgba(252, 252, 253, 0.9);
+    }
+
+    pre {
+        padding: 16px;
+        overflow-x: auto;
+
+        code {
+            padding: 0;
+            background: none;
+        }
+    }
+
+    // clearfix for floated images
+    &::after {
+        content: "";
+        display: table;
+        clear: both;
+    }
+
+    // 텍스트 정렬
+    .se-align-left,
+    [style*="text-align: left"] {
+        text-align: left;
+    }
+
+    .se-align-center,
+    [style*="text-align: center"] {
+        text-align: center;
+    }
+
+    .se-align-right,
+    [style*="text-align: right"] {
+        text-align: right;
+    }
+
+    // figure (SunEditor가 생성하는 경우) - user agent stylesheet 리셋
+    figure {
+        figcaption {
+            font-size: 14px;
+            color: rgba(252, 252, 253, 0.7);
+            text-align: center;
+            margin-top: 8px;
+        }
+    }
+}

+ 40 - 0
app/components/ColorPicker.vue

@@ -0,0 +1,40 @@
+<template>  
+  <div class="car--list--wrap" :class="titleOption ? 'title--type' : ''">
+    <h3 v-if="titleOption" class="title--exterior">Exterior Colors</h3>
+    <div class="thumb">
+      <img :src="colors[selectedIndex].image" :alt="colors[selectedIndex].title" />
+    </div>
+    <div class="color--pick--contents">
+      <div class="color--pick">
+        <div
+          v-for="(color, index) in colors"
+          :key="index"
+          :style="{ backgroundColor: color.colorValue , borderColor : color.colorValue }"
+          :class="{ actv: selectedIndex === index }"
+          @click="selectedIndex = index"
+        >
+          <div :style="{ backgroundColor: color.colorValue}"></div>
+        </div>
+      </div>
+      <div class="color--info">
+        <h1>Paint Color : </h1>
+        <span>{{ colors[selectedIndex].title }}</span>
+      </div>
+    </div>
+  </div>    
+</template>
+
+<script setup>
+const props = defineProps({
+  titleOption: {
+    type: Boolean,
+    default: false
+  },
+  colors: {
+    type: Array,
+    required: true
+  },  
+})
+
+const selectedIndex = ref(0)
+</script>

+ 22 - 0
app/components/ImageTabSwitch.vue

@@ -0,0 +1,22 @@
+<template>
+  <div class="models--visual--tab--type">
+    <div class="thumb--wrap">
+      <img :src="activeTab === 'exterior' ? exteriorImg : interiorImg" />
+    </div>
+    <p v-if="activeTab === 'interior'" class="theme--name">{{ interiorName }}</p>
+    <div class="chg--tab--wrap">
+      <button class="ex--btn" :class="{ active: activeTab === 'exterior' }" @click="activeTab = 'exterior'"><i class="ico"></i>EXTERIOR</button>
+      <button class="in--btn" :class="{ active: activeTab === 'interior' }" @click="activeTab = 'interior'"><i class="ico"></i>INTERIOR</button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+const props = defineProps({
+  exteriorImg: { type: String, required: true },
+  interiorImg: { type: String, required: true },
+  interiorName: { type: String, required: false }
+});
+
+const activeTab = ref('exterior');
+</script>

+ 419 - 0
app/components/Popup.vue

@@ -0,0 +1,419 @@
+<template>
+  <div v-if="visiblePopups.length > 0" class="popup--container">
+    <div
+      v-for="(popup, index) in visiblePopups"
+      :key="popup.id"
+      class="popup--wrapper"
+      :style="{
+        top: popupPositions[index]?.top || popup.position_top + 'px',
+        left: popupPositions[index]?.left || popup.position_left + 'px',
+        width: popup.width + 'px',
+        height: popup.type === 'image' ? 'auto' : popup.height + 'px',
+      }"
+    >
+      <!-- 스크롤 가능한 콘텐츠 영역 -->
+      <div class="popup--content">
+        <!-- HTML 타입 팝업 -->
+        <div
+          v-if="popup.type === 'html'"
+          class="popup--html"
+          v-html="popup.content"
+        ></div>
+
+        <!-- 이미지 타입 팝업 -->
+        <div v-else-if="popup.type === 'image'" class="popup--image">
+          <a
+            v-if="popup.link_url"
+            :href="popup.link_url"
+            :target="popup.link_target || '_blank'"
+            :rel="popup.link_target === '_self' ? '' : 'noopener noreferrer'"
+          >
+            <img :src="popup.image_url" :alt="popup.title" />
+          </a>
+          <img v-else :src="popup.image_url" :alt="popup.title" />
+        </div>
+      </div>
+
+      <!-- 하단 고정 푸터 -->
+      <div class="popup--footer" :class="{ solo: popup.cookie_setting == 'none' }">
+        <label class="popup--checkbox" v-if="popup.cookie_setting !== 'none'">
+          <input
+            type="checkbox"
+            @change="(e) => handleDontShowToday(e, popup.id, popup.cookie_setting)"
+          />
+          <span v-if="popup.cookie_setting === 'today'">오늘 하루 창 띄우지 않음</span>
+          <span v-else-if="popup.cookie_setting === 'forever'"
+            >다시는 창을 띄우지 않음</span
+          >
+        </label>
+        <button
+          class="popup--close"
+          @click.stop="closePopup(popup.id)"
+          @mousedown.stop
+          @touchstart.stop
+          @touchend.stop="closePopup(popup.id)"
+          aria-label="팝업 닫기"
+        >
+          닫기
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref, onMounted, onUnmounted } from "vue";
+  import axios from "axios";
+
+  const props = defineProps({
+    site: {
+      type: String,
+      required: true,
+      validator: (value) => ["ford", "lincoln"].includes(value),
+    },
+  });
+
+  const config = useRuntimeConfig();
+  const visiblePopups = ref([]);
+  const popupPositions = ref([]);
+  const { getImageUrl } = useImage();
+
+  // 드래그 상태
+  const dragState = ref({
+    isDragging: false,
+    currentIndex: null,
+    startX: 0,
+    startY: 0,
+    initialLeft: 0,
+    initialTop: 0,
+  });
+
+  // 쿠키 가져오기
+  const getCookie = (name) => {
+    if (typeof document === "undefined") return null;
+    const value = `; ${document.cookie}`;
+    const parts = value.split(`; ${name}=`);
+    if (parts.length === 2) return parts.pop().split(";").shift();
+    return null;
+  };
+
+  // 쿠키 설정
+  const setCookie = (name, value, days) => {
+    if (typeof document === "undefined") return;
+    const date = new Date();
+    date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
+    const expires = `expires=${date.toUTCString()}`;
+    document.cookie = `${name}=${value};${expires};path=/`;
+  };
+
+  // 팝업 데이터 가져오기
+  const fetchPopups = async () => {
+    try {
+      const apiUrl = `${config.public.apiBase}/api/popup/active?site=${props.site}`;
+      const response = await axios.get(apiUrl);
+
+      if (response.data.success && response.data.data) {
+        // 쿠키 설정 필드 정규화 (cookie_setting 또는 cookie_type 지원)
+        const popups = response.data.data.map((popup) => ({
+          ...popup,
+          cookie_setting: popup.cookie_setting || popup.cookie_type || "none",
+        }));
+
+        // 1차 필터: 기간(시작/종료 시각 포함) 재검증 — 백엔드 버그/캐시 대비 안전장치
+        const now = new Date();
+        const inPeriod = popups.filter((popup) => {
+          if (!popup.start_date || !popup.end_date) return false;
+          const start = new Date(
+            `${popup.start_date}T${popup.start_time || "00:00:00"}`
+          );
+          const end = new Date(
+            `${popup.end_date}T${popup.end_time || "23:59:59"}`
+          );
+          const active = now >= start && now <= end;
+          if (!active) {
+            console.log(
+              `[Popup] ID ${popup.id} "${popup.title}" - 기간 외 노출 차단`,
+              { now, start, end }
+            );
+          }
+          return active;
+        });
+
+        // 2차 필터: 쿠키로 숨긴 팝업 제외
+        visiblePopups.value = inPeriod.filter((popup) => {
+          const cookieName = `popup_hide_${popup.id}`;
+          const isHidden = getCookie(cookieName);
+          console.log(
+            `[Popup] ID ${popup.id} "${popup.title}" - Hidden by cookie:`,
+            !!isHidden
+          );
+          return !isHidden;
+        });
+
+        // 초기 위치 설정
+        popupPositions.value = visiblePopups.value.map(() => ({}));
+      }
+    } catch (error) {
+      console.error("[Popup] Failed to fetch popups:", error);
+    }
+  };
+
+  // 드래그 시작
+  const startDrag = (event, index) => {
+    // 1024px 이하에서는 드래그 비활성화
+    if (window.innerWidth <= 1024) return;
+
+    event.preventDefault();
+
+    const e = event.touches ? event.touches[0] : event;
+    const popup = visiblePopups.value[index];
+
+    dragState.value = {
+      isDragging: true,
+      currentIndex: index,
+      startX: e.clientX,
+      startY: e.clientY,
+      initialLeft: parseInt(popupPositions.value[index]?.left || popup.position_left),
+      initialTop: parseInt(popupPositions.value[index]?.top || popup.position_top),
+    };
+
+    document.addEventListener("mousemove", onDrag);
+    document.addEventListener("mouseup", stopDrag);
+    document.addEventListener("touchmove", onDrag);
+    document.addEventListener("touchend", stopDrag);
+  };
+
+  // 드래그 중
+  const onDrag = (event) => {
+    if (!dragState.value.isDragging) return;
+
+    const e = event.touches ? event.touches[0] : event;
+    const deltaX = e.clientX - dragState.value.startX;
+    const deltaY = e.clientY - dragState.value.startY;
+
+    const newLeft = dragState.value.initialLeft + deltaX;
+    const newTop = dragState.value.initialTop + deltaY;
+
+    popupPositions.value[dragState.value.currentIndex] = {
+      left: newLeft + "px",
+      top: newTop + "px",
+    };
+  };
+
+  // 드래그 종료
+  const stopDrag = () => {
+    dragState.value.isDragging = false;
+    dragState.value.currentIndex = null;
+
+    document.removeEventListener("mousemove", onDrag);
+    document.removeEventListener("mouseup", stopDrag);
+    document.removeEventListener("touchmove", onDrag);
+    document.removeEventListener("touchend", stopDrag);
+  };
+
+  // 팝업 닫기 (모바일에서 연속 닫힘 방지)
+  let lastCloseTime = 0;
+  const closePopup = (popupId) => {
+    const now = Date.now();
+    // 300ms 이내 연속 닫기 시도 무시 (터치 이벤트 중복 방지)
+    if (now - lastCloseTime < 300) return;
+    lastCloseTime = now;
+
+    const index = visiblePopups.value.findIndex((p) => p.id === popupId);
+    if (index !== -1) {
+      visiblePopups.value.splice(index, 1);
+      popupPositions.value.splice(index, 1);
+    }
+  };
+
+  // 오늘 하루 보지 않기
+  const handleDontShowToday = (event, popupId, cookieSetting) => {
+    // 체크박스가 체크된 경우에만 실행
+    if (!event.target.checked) return;
+
+    let days = 1; // 기본 1일
+    if (cookieSetting === "forever") days = 365 * 10;
+    // 10년 (사실상 영구)
+    else if (cookieSetting === "today") days = 1;
+
+    const cookieName = `popup_hide_${popupId}`;
+    setCookie(cookieName, "true", days);
+    closePopup(popupId);
+  };
+
+  onMounted(() => {
+    console.log("[Popup] Component mounted, starting to fetch popups...");
+    fetchPopups();
+  });
+
+  onUnmounted(() => {
+    stopDrag();
+  });
+</script>
+
+<style scoped>
+  .popup--container {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    pointer-events: none;
+    z-index: 999;
+  }
+
+  .popup--wrapper {
+    position: absolute;
+    pointer-events: auto;
+    background: #fff;
+    /* border: 1px solid #333; */
+    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
+    border-radius: 8px;
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+    max-width: calc(100% - 40px);
+    max-height: 90vh;
+  }
+  @media (max-width: 1024px) {
+    .popup--wrapper {
+      left: 20px !important;
+    }
+  }
+
+  .lincoln .popup--header {
+    background-color: #db784d;
+  }
+
+  .popup--header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 12px 16px;
+    background-color: #076fed;
+    cursor: move;
+    user-select: none;
+  }
+
+  .popup--header:active {
+    cursor: grabbing;
+  }
+
+  .popup--title {
+    font-size: 14px;
+    font-weight: 600;
+    color: #ffffff;
+    flex: 1;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .popup--close {
+    height: 28px;
+    background: rgba(255, 255, 255, 0.1);
+    color: #000;
+    border: 1px solid rgba(0, 0, 0, 0.2);
+    border-radius: 4px;
+    cursor: pointer;
+    font-size: 12px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    transition: all 0.2s;
+    flex-shrink: 0;
+    margin-left: 10px;
+    padding: 0 20px;
+  }
+
+  .popup--close:hover {
+    background: rgba(255, 255, 255, 0.2);
+    border-color: rgba(255, 255, 255, 0.3);
+  }
+
+  .popup--content {
+    flex: 1;
+    overflow: auto;
+    background: #fff;
+    min-height: 0;
+  }
+
+  .popup--html,
+  .popup--image {
+    padding: 20px;
+    color: #ffffff;
+  }
+
+  .popup--html {
+    background: #fff;
+  }
+
+  .popup--html :deep(p),
+  .popup--html :deep(div),
+  .popup--html :deep(span) {
+    color: #444;
+  }
+
+  .popup--html :deep(a) {
+    color: #4a9eff;
+  }
+
+  .popup--image {
+    padding: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: #0a0a0a;
+  }
+
+  .popup--image img {
+    max-width: 100%;
+    max-height: 100%;
+    object-fit: contain;
+  }
+
+  .popup--footer {
+    padding: 12px 20px;
+    border-top: 1px solid #333;
+    flex-shrink: 0;
+    color: #000;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+
+  .popup--footer.solo {
+    justify-content: flex-end;
+  }
+
+  .popup--checkbox {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    font-size: 14px;
+    cursor: pointer;
+    user-select: none;
+    color: #444;
+  }
+
+  .popup--checkbox input[type="checkbox"] {
+    cursor: pointer;
+    accent-color: #4a9eff;
+  }
+
+  .lincoln .popup--checkbox input[type="checkbox"] {
+    accent-color: #db784d;
+  }
+
+  .popup--checkbox span {
+    color: #444;
+  }
+
+  @media (max-width: 768px) {
+    .popup--wrapper {
+      left: 50% !important;
+      transform: translateX(-50%);
+      max-width: calc(100% - 40px);
+    }
+  }
+</style>

+ 294 - 0
app/components/SwiperBanner.vue

@@ -0,0 +1,294 @@
+<template>
+  <section class="swiper--banner--wrapper" :data-type="type" :data-fit="fit">
+    <div class="swiper--banner--container">
+      <div class="swiper-button-prev" ref="prevRef" v-if="slides.length > 1">
+        <svg
+          xmlns="http://www.w3.org/2000/svg"
+          width="24"
+          height="24"
+          viewBox="0 0 24 24"
+          fill="none"
+        >
+          <path
+            d="M15 18L9 12L15 6"
+            stroke="white"
+            stroke-width="2"
+            stroke-linecap="round"
+            stroke-linejoin="round"
+          />
+        </svg>
+      </div>
+      <div class="swiper-pagination" ref="paginationRef" v-if="slides.length > 1"></div>
+      <div class="swiper-button-next" ref="nextRef" v-if="slides.length > 1">
+        <svg
+          xmlns="http://www.w3.org/2000/svg"
+          width="24"
+          height="24"
+          viewBox="0 0 24 24"
+          fill="none"
+        >
+          <path
+            d="M9 18L15 12L9 6"
+            stroke="white"
+            stroke-width="2"
+            stroke-linecap="round"
+            stroke-linejoin="round"
+          />
+        </svg>
+      </div>
+
+      <!-- 70% 영역: 단일 배너 -->
+      <div class="swiper--banner--section">
+        <div class="swiper--container" ref="swiperContainer">
+          <div class="swiper-wrapper">
+            <div v-for="(slide, index) in slides" :key="index" class="swiper-slide">
+              <div class="slide--image" v-if="slide.image">
+                <picture>
+                  <source
+                    v-if="slide.moImage"
+                    media="(max-width: 768px)"
+                    :srcset="slide.moImage"
+                  />
+                  <img
+                    :src="slide.image"
+                    :alt="slide.alt || 'Banner Image'"
+                    loading="lazy"
+                  />
+                </picture>
+
+                <div class="desc--wrapper">
+                  <h2
+                    v-if="getSlideTitle(index)"
+                    class="main--title"
+                    v-html="getSlideTitle(index)"
+                  ></h2>
+                  <h3 v-if="getSlideSubtitle(index)" class="sub--title">
+                    {{ getSlideSubtitle(index) }}
+                  </h3>
+                  <h4 v-if="getSlideSmalldesc(index)" class="desc--title">
+                    {{ getSlideSmalldesc(index) }}
+                  </h4>
+                  <h4
+                    v-if="getSlideCautiondesc(index)"
+                    class="caution--title"
+                    v-html="getSlideCautiondesc(index)"
+                  ></h4>
+                  <NuxtLink
+                    v-if="getSlideMorelink(index)"
+                    class="more--detail--href mt--45"
+                    :to="getSlideMorelink(index)"
+                    :target="getSlideTarget(index)"
+                    >{{ getSlideMorelinktitle(index) }} <i class="ico"></i
+                  ></NuxtLink>
+                </div>
+              </div>
+              <div class="slide--image" v-if="slide.video">
+                <video
+                  autoplay
+                  muted
+                  loop
+                  playsinline
+                  :poster="slide.video"
+                  controlslist="nodownload noplaybackrate"
+                  disablepictureinpicture
+                  preload="metadata"
+                >
+                  <source media="(min-width:320px)" :src="slide.video" type="video/mp4" />
+                </video>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script setup>
+  import { Swiper } from "swiper";
+  import { Navigation, Pagination, Autoplay, EffectFade } from "swiper/modules";
+  import "swiper/css";
+  import "swiper/css/navigation";
+  import "swiper/css/pagination";
+  import "swiper/css/effect-fade";
+
+  const { getMediaUrl } = useImage();
+
+  // Props 정의
+  const props = defineProps({
+    slides: {
+      type: Array,
+      default: () => [],
+    },
+    height: {
+      type: String,
+      default: "60%",
+    },
+    title: {
+      type: String,
+      default: "",
+    },
+    subtitle: {
+      type: String,
+      default: "",
+    },
+    smalldesc: {
+      type: String,
+      default: "",
+    },
+    cautiondesc: {
+      type: String,
+      default: "",
+    },
+    morelink: {
+      type: String,
+      default: "",
+    },
+    morelinktitle: {
+      type: String,
+      default: "자세히 보기",
+    },
+    morelinktarget: {
+      type: String,
+      default: "_self",
+    },
+    notice: {
+      type: String,
+      default: "",
+    },
+    autoplay: {
+      type: [Boolean, Object],
+      default: () => ({ delay: 5000 }),
+    },
+    loop: {
+      type: Boolean,
+      default: true,
+    },
+    type: {
+      type: String,
+      default: "horizental", // 'horz' , 'vert'
+      validator: (value) => ["horizental", "vertical"].includes(value),
+    },
+    fit: {
+      type: String,
+      default: "cover",
+      validator: (value) => ["cover", "contain"].includes(value),
+    },
+  });
+
+  // Refs
+  const swiperContainer = ref(null);
+  const paginationRef = ref(null);
+  const prevRef = ref(null);
+  const nextRef = ref(null);
+  const textSlider = ref(null);
+  let swiperInstance = null;
+
+  // 현재 슬라이드 인덱스
+  const currentSlide = ref(0);
+
+  // 슬라이드별 텍스트 반환 함수들
+  const getSlideTitle = (index) => {
+    return props.slides[index]?.title || props.title;
+  };
+
+  const getSlideSubtitle = (index) => {
+    return props.slides[index]?.subtitle || props.subtitle;
+  };
+
+  const getSlideSmalldesc = (index) => {
+    return props.slides[index]?.smalldesc || props.smalldesc;
+  };
+
+  const getSlideMorelink = (index) => {
+    return props.slides[index]?.morelink || props.morelink;
+  };
+
+  const getSlideMorelinktitle = (index) => {
+    return props.slides[index]?.morelinktitle || props.morelinktitle;
+  };
+
+  const getSlideTarget = (index) => {
+    return props.slides[index]?.morelinktarget || props.morelinktarget;
+  };
+
+  const getSlideCautiondesc = (index) => {
+    return props.slides[index]?.cautiondesc || props.cautiondesc;
+  };
+
+  onMounted(() => {
+    // Swiper 인스턴스 초기화
+    swiperInstance = new Swiper(swiperContainer.value, {
+      modules: [Navigation, Pagination, Autoplay],
+      slidesPerView: 1,
+      spaceBetween: 0,
+      loop: props.loop,
+      autoplay: props.autoplay
+        ? {
+            delay:
+              typeof props.autoplay === "object" ? props.autoplay.delay || 5000 : 5000,
+            disableOnInteraction: false,
+            pauseOnMouseEnter: true,
+          }
+        : false,
+      navigation: {
+        nextEl: nextRef.value,
+        prevEl: prevRef.value,
+      },
+      pagination: {
+        el: paginationRef.value,
+        clickable: true,
+        type: "bullets",
+        // renderBullet: function (index, className) {
+        //   return '<span class="' + className + '">' + (index + 1) + "</span>";
+        // },
+      },
+      effect: "fade",
+      fadeEffect: {
+        crossFade: true,
+      },
+      speed: 800,
+      // 슬라이드 변경 이벤트
+      on: {
+        slideChange: function () {
+          if (this && this.realIndex !== undefined) {
+            currentSlide.value = this.realIndex;
+          }
+          // 모든 비디오 일시정지
+          const allVideos = this.el.querySelectorAll("video");
+          allVideos.forEach((video) => {
+            video.pause();
+            video.currentTime = 0;
+          });
+          // 현재 슬라이드의 비디오 재생
+          const currentSlideEl = this.slides[this.activeIndex];
+          if (currentSlideEl) {
+            const video = currentSlideEl.querySelector("video");
+            if (video) {
+              video.play().catch((err) => console.log("Video play failed:", err));
+            }
+          }
+        },
+        init: function () {
+          if (this && this.realIndex !== undefined) {
+            currentSlide.value = this.realIndex;
+          }
+          // 초기 슬라이드의 비디오 재생
+          const currentSlideEl = this.slides[this.activeIndex];
+          if (currentSlideEl) {
+            const video = currentSlideEl.querySelector("video");
+            if (video) {
+              video.play().catch((err) => console.log("Video play failed:", err));
+            }
+          }
+        },
+      },
+    });
+  });
+
+  onUnmounted(() => {
+    if (swiperInstance) {
+      swiperInstance.destroy(true, true);
+    }
+  });
+</script>

+ 496 - 0
app/components/SwiperBanner2.vue

@@ -0,0 +1,496 @@
+<template>
+  <section class="swiper--banner--wrapper2" :data-type="type" :data-fit="fit">
+    <div class="swiper--banner--container">
+      <div class="type--connection" :class="type" v-if="type == 'exterior'">
+        <svg
+          xmlns="http://www.w3.org/2000/svg"
+          width="30"
+          height="30"
+          viewBox="0 0 30 30"
+          fill="none"
+        >
+          <path
+            d="M15.1354 5H20.9482C21.5223 5 22.034 5.35992 22.2251 5.89813L25.1563 12.8077M25.1563 12.8077C26.1042 12.9423 28 13.6962 28 15.6346V16.8462M25.1563 12.8077H15.1354M25.1563 12.8077L27.7292 10.9231M28 16.8462V20.3462M28 16.8462L23.3958 18.5962M28 20.3462V23.8462C27.9097 24.5641 27.3229 26 25.6979 26C23.6674 26 23.396 25.3274 22.0431 22.503M28 20.3462C26.8663 21.4731 24.5199 22.1343 22.0431 22.503M22.0431 22.503C22.0426 22.502 22.0421 22.501 22.0417 22.5M22.0431 22.503C19.5496 22.8742 16.924 22.9489 15.2708 22.9038H14.7292C13.076 22.9489 10.4504 22.8742 7.9569 22.503M19.3333 22.5V19C19.3333 18.7026 19.0908 18.4615 18.7917 18.4615H11.2083C10.9092 18.4615 10.6667 18.7026 10.6667 19V22.5M14.8646 5H9.05184C8.4777 5 7.96596 5.35992 7.77488 5.89813L4.84375 12.8077M4.84375 12.8077C3.89583 12.9423 2 13.6962 2 15.6346V16.8462M4.84375 12.8077H14.8646M4.84375 12.8077L2.27083 10.9231M2 16.8462V20.3462M2 16.8462L6.60417 18.5962M2 20.3462V23.8462C2.09028 24.5641 2.67708 26 4.30208 26C6.33261 26 6.60398 25.3274 7.9569 22.503M2 20.3462C3.13367 21.4731 5.48012 22.1343 7.9569 22.503M7.9569 22.503C7.95738 22.502 7.95785 22.501 7.95833 22.5"
+            stroke="white"
+            stroke-width="2"
+            stroke-linecap="round"
+          /></svg
+        >{{ type }}
+      </div>
+      <div class="type--connection" :class="type" v-else-if="type == 'interior'">
+        <svg
+          xmlns="http://www.w3.org/2000/svg"
+          width="30"
+          height="30"
+          viewBox="0 0 30 30"
+          fill="none"
+        >
+          <path
+            d="M2.30762 22.3072H13.8931M20.7409 22.3072H26.6922C27.2445 22.3072 27.6922 21.8595 27.6922 21.3072V18.0765M2.30762 3.8457C2.78804 3.8457 4.52124 3.8457 7.61071 3.8457C11.4725 3.8457 13.8931 6.92263 16.8791 9.61493C19.2679 11.7688 21.2214 12.3072 21.8995 12.3072C23.4442 12.3072 25.9288 12.5214 27.0908 14.9995M2.30762 6.92263H7.61071C8.12562 6.92263 9.46439 7.1534 10.7002 8.07647C11.7257 8.84247 13.8858 10.8974 15.3269 12.3072M2.30762 19.6149C4.84768 19.6149 10.1595 19.6149 11.0864 19.6149C12.05 19.6149 12.2121 19.0828 12.6838 18.4611M6.45216 13.8457L7.8038 11.9226M9.15544 9.99955L7.8038 11.9226M7.8038 11.9226L9.92781 13.4611M9.92781 13.4611C10.1853 13.0765 10.9319 12.3072 11.8587 12.3072C12.7856 12.3072 14.557 12.3072 15.3269 12.3072M9.92781 13.4611C9.30991 14.3842 9.67035 15.3842 9.92781 15.7688L12.6838 18.4611M15.3269 12.3072C15.6224 12.5963 15.8877 12.8583 16.1067 13.0765C16.3642 13.3329 16.8791 13.9995 16.8791 14.6149C16.8791 15.3842 16.8791 16.2288 15.7206 16.538C14.562 16.8472 13.7896 17.3072 13.0173 18.0765C12.8873 18.2059 12.7792 18.3353 12.6838 18.4611M27.6922 18.0765C27.6922 16.8049 27.464 15.7955 27.0908 14.9995M27.6922 18.0765H24.6345C23.124 18.0765 21.8995 16.852 21.8995 15.3414C21.8995 15.1526 22.0526 14.9995 22.2414 14.9995H27.0908"
+            stroke="white"
+            stroke-width="2"
+            stroke-linecap="round"
+          />
+          <path
+            d="M17.2653 20.2305C18.6364 20.2305 19.7408 21.3367 19.7408 22.6924C19.7406 24.0479 18.6362 25.1533 17.2653 25.1533C15.8943 25.1533 14.7899 24.0478 14.7897 22.6924C14.7897 21.3368 15.8942 20.2305 17.2653 20.2305Z"
+            stroke="white"
+            stroke-width="2"
+            stroke-linecap="round"
+          /></svg
+        >{{ type }}
+      </div>
+
+      <!-- Pagination with Progress and Fractions -->
+      <div class="swiper--pagination--wrapper">
+        <div class="swiper-pagination" ref="paginationRef"></div>
+        <div class="swiper--progressbar" ref="progressRef">
+          <div class="swiper--progressbar-fill" :style="{ width: progressWidth }"></div>
+        </div>
+        <div class="swiper--fraction" ref="fractionRef">
+          <span class="current">{{ currentSlide + 1 }}</span>
+          <span class="separator">/</span>
+          <span class="total">{{ slides.length }}</span>
+        </div>
+      </div>
+
+      <!-- 70% 영역: 단일 배너 -->
+      <div class="swiper--banner--section">
+        <div class="swiper--container" ref="swiperContainer">
+          <div class="swiper-wrapper">
+            <div v-for="(slide, index) in slides" :key="index" class="swiper-slide">
+              <div class="slide--image" v-if="slide.image">
+                <img
+                  :src="slide.image"
+                  :alt="slide.alt || 'Banner Image'"
+                  loading="lazy"
+                />
+
+                <!-- 확대 버튼 -->
+                <button class="zoom--btn" @click="openLightbox(index)" aria-label="확대">
+                  Image Details
+                  <i>
+                    <svg
+                      xmlns="http://www.w3.org/2000/svg"
+                      width="12"
+                      height="12"
+                      viewBox="0 0 12 12"
+                      fill="none"
+                    >
+                      <path
+                        d="M2.5 6H9.5"
+                        stroke="white"
+                        stroke-width="1.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M6 2.5L9.5 6L6 9.5"
+                        stroke="white"
+                        stroke-width="1.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                    </svg>
+                  </i>
+                </button>
+              </div>
+              <div class="slide--image" v-if="slide.video">
+                <video
+                  autoplay
+                  muted
+                  loop
+                  playsinline
+                  :poster="slide.video"
+                  controlslist="nodownload noplaybackrate"
+                  disablepictureinpicture
+                  preload="metadata"
+                >
+                  <source media="(min-width:320px)" :src="slide.video" type="video/mp4" />
+                </video>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- Lightbox -->
+    <Transition name="lightbox-fade">
+      <div v-if="isLightboxOpen" class="lightbox--overlay" @click.self="closeLightbox">
+        <button class="lightbox--close" @click="closeLightbox" aria-label="닫기">
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="32"
+            height="32"
+            viewBox="0 0 32 32"
+            fill="none"
+          >
+            <path
+              d="M24 8L8 24M8 8L24 24"
+              stroke="white"
+              stroke-width="2"
+              stroke-linecap="round"
+            />
+          </svg>
+        </button>
+
+        <button class="lightbox--prev" @click="prevLightboxSlide" aria-label="이전">
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="40"
+            height="40"
+            viewBox="0 0 40 40"
+            fill="none"
+          >
+            <path
+              d="M25 30L15 20L25 10"
+              stroke="white"
+              stroke-width="3"
+              stroke-linecap="round"
+              stroke-linejoin="round"
+            />
+          </svg>
+        </button>
+
+        <div class="lightbox--content">
+          <div class="lightbox--swiper" ref="lightboxSwiperContainer">
+            <div class="swiper-wrapper">
+              <div
+                v-for="(slide, index) in slides"
+                :key="`lightbox-${index}`"
+                class="swiper-slide"
+              >
+                <img
+                  v-if="slide.image"
+                  :src="slide.image"
+                  :alt="slide.alt || 'Banner Image'"
+                />
+              </div>
+            </div>
+          </div>
+
+          <!-- Lightbox Pagination Info -->
+          <div class="lightbox--info">
+            <span class="lightbox--fraction">
+              {{ lightboxCurrentSlide + 1 }} / {{ slides.length }}
+            </span>
+          </div>
+        </div>
+
+        <button class="lightbox--next" @click="nextLightboxSlide" aria-label="다음">
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="40"
+            height="40"
+            viewBox="0 0 40 40"
+            fill="none"
+          >
+            <path
+              d="M15 30L25 20L15 10"
+              stroke="white"
+              stroke-width="3"
+              stroke-linecap="round"
+              stroke-linejoin="round"
+            />
+          </svg>
+        </button>
+      </div>
+    </Transition>
+  </section>
+</template>
+
+<script setup>
+  import { Swiper } from "swiper";
+  import { Navigation, Pagination, Autoplay, EffectFade } from "swiper/modules";
+  import "swiper/css";
+  import "swiper/css/navigation";
+  import "swiper/css/pagination";
+  import "swiper/css/effect-fade";
+
+  const { getMediaUrl } = useImage();
+
+  // Props 정의
+  const props = defineProps({
+    slides: {
+      type: Array,
+      default: () => [],
+    },
+    height: {
+      type: String,
+      default: "60%",
+    },
+    title: {
+      type: String,
+      default: "",
+    },
+    subtitle: {
+      type: String,
+      default: "",
+    },
+    smalldesc: {
+      type: String,
+      default: "",
+    },
+    cautiondesc: {
+      type: String,
+      default: "",
+    },
+    morelink: {
+      type: String,
+      default: "",
+    },
+    morelinktitle: {
+      type: String,
+      default: "자세히 보기",
+    },
+    morelinktarget: {
+      type: String,
+      default: "_self",
+    },
+    notice: {
+      type: String,
+      default: "",
+    },
+    autoplay: {
+      type: [Boolean, Object],
+      default: () => ({ delay: 5000 }),
+    },
+    loop: {
+      type: Boolean,
+      default: true,
+    },
+    type: {
+      type: String,
+      default: "", // 'horz' , 'vert'
+      validator: (value) => ["exterior", "interior"].includes(value),
+    },
+    fit: {
+      type: String,
+      default: "cover",
+      validator: (value) => ["cover", "contain"].includes(value),
+    },
+  });
+
+  // Refs
+  const swiperContainer = ref(null);
+  const lightboxSwiperContainer = ref(null);
+  const paginationRef = ref(null);
+  const progressRef = ref(null);
+  const fractionRef = ref(null);
+  const prevRef = ref(null);
+  const nextRef = ref(null);
+  const textSlider = ref(null);
+  let swiperInstance = null;
+  let lightboxSwiperInstance = null;
+
+  // 현재 슬라이드 인덱스
+  const currentSlide = ref(0);
+  const lightboxCurrentSlide = ref(0);
+  const progressWidth = ref("0%");
+
+  // 라이트박스 상태
+  const isLightboxOpen = ref(false);
+
+  // 슬라이드별 텍스트 반환 함수들
+  const getSlideTitle = (index) => {
+    return props.slides[index]?.title || props.title;
+  };
+
+  const getSlideSubtitle = (index) => {
+    return props.slides[index]?.subtitle || props.subtitle;
+  };
+
+  const getSlideSmalldesc = (index) => {
+    return props.slides[index]?.smalldesc || props.smalldesc;
+  };
+
+  const getSlideMorelink = (index) => {
+    return props.slides[index]?.morelink || props.morelink;
+  };
+
+  const getSlideMorelinktitle = (index) => {
+    return props.slides[index]?.morelinktitle || props.morelinktitle;
+  };
+
+  const getSlideTarget = (index) => {
+    return props.slides[index]?.morelinktarget || props.morelinktarget;
+  };
+
+  const getSlideCautiondesc = (index) => {
+    return props.slides[index]?.cautiondesc || props.cautiondesc;
+  };
+
+  // Progress bar 업데이트 함수
+  const updateProgress = () => {
+    const progress = ((currentSlide.value + 1) / props.slides.length) * 100;
+    progressWidth.value = `${progress}%`;
+  };
+
+  // 라이트박스 열기
+  const openLightbox = (index) => {
+    isLightboxOpen.value = true;
+    document.body.style.overflow = "hidden";
+    document.querySelector(".g-layout").classList.add("active");
+    nextTick(() => {
+      initLightboxSwiper(index);
+    });
+  };
+
+  // 라이트박스 닫기
+  const closeLightbox = () => {
+    isLightboxOpen.value = false;
+    document.body.style.overflow = "";
+    document.querySelector(".g-layout").classList.remove("active");
+    if (lightboxSwiperInstance) {
+      lightboxSwiperInstance.destroy(true, true);
+      lightboxSwiperInstance = null;
+    }
+  };
+
+  // 라이트박스 Swiper 초기화
+  const initLightboxSwiper = (initialIndex = 0) => {
+    if (!lightboxSwiperContainer.value) return;
+
+    lightboxSwiperInstance = new Swiper(lightboxSwiperContainer.value, {
+      modules: [Navigation],
+      slidesPerView: 1,
+      spaceBetween: 0,
+      initialSlide: initialIndex,
+      loop: false,
+      keyboard: {
+        enabled: true,
+      },
+      on: {
+        slideChange: function () {
+          lightboxCurrentSlide.value = this.activeIndex;
+
+          // 메인 스와이퍼도 동일한 인덱스로 이동
+          if (swiperInstance && !swiperInstance.destroyed) {
+            swiperInstance.slideToLoop(this.activeIndex, 0);
+          }
+        },
+        init: function () {
+          lightboxCurrentSlide.value = this.activeIndex;
+        },
+      },
+    });
+  };
+
+  // 라이트박스 이전 슬라이드
+  const prevLightboxSlide = () => {
+    if (lightboxSwiperInstance) {
+      lightboxSwiperInstance.slidePrev();
+    }
+  };
+
+  // 라이트박스 다음 슬라이드
+  const nextLightboxSlide = () => {
+    if (lightboxSwiperInstance) {
+      lightboxSwiperInstance.slideNext();
+    }
+  };
+
+  // ESC 키로 라이트박스 닫기
+  const handleKeydown = (e) => {
+    if (e.key === "Escape" && isLightboxOpen.value) {
+      closeLightbox();
+    }
+  };
+
+  onMounted(() => {
+    // 키보드 이벤트 리스너 추가
+    window.addEventListener("keydown", handleKeydown);
+    // Swiper 인스턴스 초기화
+    swiperInstance = new Swiper(swiperContainer.value, {
+      modules: [Navigation, Pagination, Autoplay],
+      slidesPerView: 1,
+      spaceBetween: 0,
+      loop: props.loop,
+      autoplay: props.autoplay
+        ? {
+            delay:
+              typeof props.autoplay === "object" ? props.autoplay.delay || 5000 : 5000,
+            disableOnInteraction: false,
+            pauseOnMouseEnter: true,
+          }
+        : false,
+      navigation: {
+        nextEl: nextRef.value,
+        prevEl: prevRef.value,
+      },
+      pagination: {
+        el: paginationRef.value,
+        clickable: true,
+        type: "bullets",
+        // renderBullet: function (index, className) {
+        //   return '<span class="' + className + '">' + (index + 1) + "</span>";
+        // },
+      },
+      effect: "fade",
+      fadeEffect: {
+        crossFade: true,
+      },
+      speed: 800,
+      // 슬라이드 변경 이벤트
+      on: {
+        slideChange: function () {
+          if (this && this.realIndex !== undefined) {
+            currentSlide.value = this.realIndex;
+          }
+          // Progress bar 업데이트
+          updateProgress();
+
+          // 모든 비디오 일시정지
+          const allVideos = this.el.querySelectorAll("video");
+          allVideos.forEach((video) => {
+            video.pause();
+            video.currentTime = 0;
+          });
+          // 현재 슬라이드의 비디오 재생
+          const currentSlideEl = this.slides[this.activeIndex];
+          if (currentSlideEl) {
+            const video = currentSlideEl.querySelector("video");
+            if (video) {
+              video.play().catch((err) => console.log("Video play failed:", err));
+            }
+          }
+        },
+        init: function () {
+          if (this && this.realIndex !== undefined) {
+            currentSlide.value = this.realIndex;
+          }
+
+          // 초기 Progress bar 업데이트 - nextTick으로 지연
+          nextTick(() => {
+            updateProgress();
+          });
+
+          // 초기 슬라이드의 비디오 재생
+          const currentSlideEl = this.slides[this.activeIndex];
+          if (currentSlideEl) {
+            const video = currentSlideEl.querySelector("video");
+            if (video) {
+              video.play().catch((err) => console.log("Video play failed:", err));
+            }
+          }
+        },
+      },
+    });
+  });
+
+  onUnmounted(() => {
+    // 키보드 이벤트 리스너 제거
+    window.removeEventListener("keydown", handleKeydown);
+
+    // Swiper 인스턴스 정리
+    if (swiperInstance) {
+      swiperInstance.destroy(true, true);
+    }
+    if (lightboxSwiperInstance) {
+      lightboxSwiperInstance.destroy(true, true);
+    }
+
+    // body overflow 복원
+    document.body.style.overflow = "";
+  });
+</script>

+ 272 - 0
app/components/SwiperBanner3.vue

@@ -0,0 +1,272 @@
+<template>
+  <section class="swiper--banner--wrapper3" :data-type="type" :data-fit="fit">
+    <div class="top--text--wrap">
+      <h2 v-html="mtitle"></h2>
+      <div class="mt--40" v-html="stitle"></div>
+    </div>
+    <div class="swiper--banner--container">
+      <!-- 70% 영역: 단일 배너 -->
+      <div class="swiper--banner--section">
+        <div class="swiper--container" ref="swiperContainer">
+          <div class="swiper-wrapper">
+            <div v-for="(slide, index) in slides" :key="index" class="swiper-slide">
+              <div class="slide--image" v-if="slide.image">
+                <img
+                  :src="slide.image"
+                  :alt="slide.alt || 'Banner Image'"
+                  loading="lazy"
+                />
+
+                <div class="desc--wrapper">
+                  <div class="desc--wrap">
+                    <div class="btn--actions">
+                      <div class="swiper-button-prev" ref="prevRef">
+                        <svg
+                          xmlns="http://www.w3.org/2000/svg"
+                          width="24"
+                          height="24"
+                          viewBox="0 0 24 24"
+                          fill="none"
+                        >
+                          <path
+                            d="M15 18L9 12L15 6"
+                            stroke="black"
+                            stroke-width="2"
+                            stroke-linecap="round"
+                            stroke-linejoin="round"
+                          />
+                        </svg>
+                      </div>
+                      <div class="swiper-button-next" ref="nextRef">
+                        <svg
+                          xmlns="http://www.w3.org/2000/svg"
+                          width="24"
+                          height="24"
+                          viewBox="0 0 24 24"
+                          fill="none"
+                        >
+                          <path
+                            d="M9 18L15 12L9 6"
+                            stroke="black"
+                            stroke-width="2"
+                            stroke-linecap="round"
+                            stroke-linejoin="round"
+                          />
+                        </svg>
+                      </div>
+                    </div>
+                    <h2 v-if="getSlideTitle(index)" class="main--title">
+                      {{ getSlideTitle(index) }}
+                    </h2>
+                    <h4
+                      v-if="getSlideSmalldesc(index)"
+                      class="desc--title"
+                      v-html="getSlideSmalldesc(index)"
+                    ></h4>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script setup>
+  import { Swiper } from "swiper";
+  import { Navigation, Pagination, Autoplay, EffectFade } from "swiper/modules";
+  import "swiper/css";
+  import "swiper/css/navigation";
+  import "swiper/css/pagination";
+  import "swiper/css/effect-fade";
+
+  const { getMediaUrl } = useImage();
+
+  // Props 정의
+  const props = defineProps({
+    slides: {
+      type: Array,
+      default: () => [],
+    },
+    height: {
+      type: String,
+      default: "60%",
+    },
+    mtitle: {
+      type: String,
+      default: "",
+    },
+    stitle: {
+      type: String,
+      default: "",
+    },
+    title: {
+      type: String,
+      default: "",
+    },
+    subtitle: {
+      type: String,
+      default: "",
+    },
+    smalldesc: {
+      type: String,
+      default: "",
+    },
+    cautiondesc: {
+      type: String,
+      default: "",
+    },
+    morelink: {
+      type: String,
+      default: "",
+    },
+    morelinktitle: {
+      type: String,
+      default: "자세히 보기",
+    },
+    morelinktarget: {
+      type: String,
+      default: "_self",
+    },
+    notice: {
+      type: String,
+      default: "",
+    },
+    autoplay: {
+      type: [Boolean, Object],
+      default: () => ({ delay: 5000 }),
+    },
+    loop: {
+      type: Boolean,
+      default: true,
+    },
+    type: {
+      type: String,
+      default: "horizental", // 'horz' , 'vert'
+      validator: (value) => ["horizental", "vertical"].includes(value),
+    },
+    fit: {
+      type: String,
+      default: "cover",
+      validator: (value) => ["cover", "contain"].includes(value),
+    },
+  });
+
+  // Refs
+  const swiperContainer = ref(null);
+  const paginationRef = ref(null);
+  const prevRef = ref(null);
+  const nextRef = ref(null);
+  const textSlider = ref(null);
+  let swiperInstance = null;
+
+  // 현재 슬라이드 인덱스
+  const currentSlide = ref(0);
+
+  // 슬라이드별 텍스트 반환 함수들
+  const getSlideTitle = (index) => {
+    return props.slides[index]?.title || props.title;
+  };
+
+  const getSlideSubtitle = (index) => {
+    return props.slides[index]?.subtitle || props.subtitle;
+  };
+
+  const getSlideSmalldesc = (index) => {
+    return props.slides[index]?.smalldesc || props.smalldesc;
+  };
+
+  const getSlideMorelink = (index) => {
+    return props.slides[index]?.morelink || props.morelink;
+  };
+
+  const getSlideMorelinktitle = (index) => {
+    return props.slides[index]?.morelinktitle || props.morelinktitle;
+  };
+
+  const getSlideTarget = (index) => {
+    return props.slides[index]?.morelinktarget || props.morelinktarget;
+  };
+
+  const getSlideCautiondesc = (index) => {
+    return props.slides[index]?.cautiondesc || props.cautiondesc;
+  };
+
+  onMounted(() => {
+    // Swiper 인스턴스 초기화
+    swiperInstance = new Swiper(swiperContainer.value, {
+      modules: [Navigation, Pagination, Autoplay],
+      slidesPerView: 1,
+      spaceBetween: 0,
+      loop: props.loop,
+      autoplay: props.autoplay
+        ? {
+            delay:
+              typeof props.autoplay === "object" ? props.autoplay.delay || 5000 : 5000,
+            disableOnInteraction: false,
+            pauseOnMouseEnter: true,
+          }
+        : false,
+      navigation: {
+        nextEl: nextRef.value,
+        prevEl: prevRef.value,
+      },
+      pagination: {
+        el: paginationRef.value,
+        clickable: true,
+        type: "bullets",
+        // renderBullet: function (index, className) {
+        //   return '<span class="' + className + '">' + (index + 1) + "</span>";
+        // },
+      },
+      effect: "fade",
+      fadeEffect: {
+        crossFade: true,
+      },
+      speed: 800,
+      // 슬라이드 변경 이벤트
+      on: {
+        slideChange: function () {
+          if (this && this.realIndex !== undefined) {
+            currentSlide.value = this.realIndex;
+          }
+          // 모든 비디오 일시정지
+          const allVideos = this.el.querySelectorAll("video");
+          allVideos.forEach((video) => {
+            video.pause();
+            video.currentTime = 0;
+          });
+          // 현재 슬라이드의 비디오 재생
+          const currentSlideEl = this.slides[this.activeIndex];
+          if (currentSlideEl) {
+            const video = currentSlideEl.querySelector("video");
+            if (video) {
+              video.play().catch((err) => console.log("Video play failed:", err));
+            }
+          }
+        },
+        init: function () {
+          if (this && this.realIndex !== undefined) {
+            currentSlide.value = this.realIndex;
+          }
+          // 초기 슬라이드의 비디오 재생
+          const currentSlideEl = this.slides[this.activeIndex];
+          if (currentSlideEl) {
+            const video = currentSlideEl.querySelector("video");
+            if (video) {
+              video.play().catch((err) => console.log("Video play failed:", err));
+            }
+          }
+        },
+      },
+    });
+  });
+
+  onUnmounted(() => {
+    if (swiperInstance) {
+      swiperInstance.destroy(true, true);
+    }
+  });
+</script>

+ 498 - 0
app/components/SwiperBanner4.vue

@@ -0,0 +1,498 @@
+<template>
+  <section class="swiper--banner--wrapper2" :data-type="type" :data-fit="fit">
+    <div class="swiper--banner--container">
+      <div class="type--connection lincoln" :class="type" v-if="type == 'exterior'">
+        <svg
+          xmlns="http://www.w3.org/2000/svg"
+          width="30"
+          height="30"
+          viewBox="0 0 30 30"
+          fill="none"
+        >
+          <path
+            d="M15.1354 5H20.9482C21.5223 5 22.034 5.35992 22.2251 5.89813L25.1563 12.8077M25.1563 12.8077C26.1042 12.9423 28 13.6962 28 15.6346V16.8462M25.1563 12.8077H15.1354M25.1563 12.8077L27.7292 10.9231M28 16.8462V20.3462M28 16.8462L23.3958 18.5962M28 20.3462V23.8462C27.9097 24.5641 27.3229 26 25.6979 26C23.6674 26 23.396 25.3274 22.0431 22.503M28 20.3462C26.8663 21.4731 24.5199 22.1343 22.0431 22.503M22.0431 22.503C22.0426 22.502 22.0421 22.501 22.0417 22.5M22.0431 22.503C19.5496 22.8742 16.924 22.9489 15.2708 22.9038H14.7292C13.076 22.9489 10.4504 22.8742 7.9569 22.503M19.3333 22.5V19C19.3333 18.7026 19.0908 18.4615 18.7917 18.4615H11.2083C10.9092 18.4615 10.6667 18.7026 10.6667 19V22.5M14.8646 5H9.05184C8.4777 5 7.96596 5.35992 7.77488 5.89813L4.84375 12.8077M4.84375 12.8077C3.89583 12.9423 2 13.6962 2 15.6346V16.8462M4.84375 12.8077H14.8646M4.84375 12.8077L2.27083 10.9231M2 16.8462V20.3462M2 16.8462L6.60417 18.5962M2 20.3462V23.8462C2.09028 24.5641 2.67708 26 4.30208 26C6.33261 26 6.60398 25.3274 7.9569 22.503M2 20.3462C3.13367 21.4731 5.48012 22.1343 7.9569 22.503M7.9569 22.503C7.95738 22.502 7.95785 22.501 7.95833 22.5"
+            stroke="white"
+            stroke-width="2"
+            stroke-linecap="round"
+          /></svg
+        >{{ type }}
+      </div>
+      <div class="type--connection lincoln" :class="type" v-else-if="type == 'interior'">
+        <svg
+          xmlns="http://www.w3.org/2000/svg"
+          width="30"
+          height="30"
+          viewBox="0 0 30 30"
+          fill="none"
+        >
+          <path
+            d="M2.30762 22.3072H13.8931M20.7409 22.3072H26.6922C27.2445 22.3072 27.6922 21.8595 27.6922 21.3072V18.0765M2.30762 3.8457C2.78804 3.8457 4.52124 3.8457 7.61071 3.8457C11.4725 3.8457 13.8931 6.92263 16.8791 9.61493C19.2679 11.7688 21.2214 12.3072 21.8995 12.3072C23.4442 12.3072 25.9288 12.5214 27.0908 14.9995M2.30762 6.92263H7.61071C8.12562 6.92263 9.46439 7.1534 10.7002 8.07647C11.7257 8.84247 13.8858 10.8974 15.3269 12.3072M2.30762 19.6149C4.84768 19.6149 10.1595 19.6149 11.0864 19.6149C12.05 19.6149 12.2121 19.0828 12.6838 18.4611M6.45216 13.8457L7.8038 11.9226M9.15544 9.99955L7.8038 11.9226M7.8038 11.9226L9.92781 13.4611M9.92781 13.4611C10.1853 13.0765 10.9319 12.3072 11.8587 12.3072C12.7856 12.3072 14.557 12.3072 15.3269 12.3072M9.92781 13.4611C9.30991 14.3842 9.67035 15.3842 9.92781 15.7688L12.6838 18.4611M15.3269 12.3072C15.6224 12.5963 15.8877 12.8583 16.1067 13.0765C16.3642 13.3329 16.8791 13.9995 16.8791 14.6149C16.8791 15.3842 16.8791 16.2288 15.7206 16.538C14.562 16.8472 13.7896 17.3072 13.0173 18.0765C12.8873 18.2059 12.7792 18.3353 12.6838 18.4611M27.6922 18.0765C27.6922 16.8049 27.464 15.7955 27.0908 14.9995M27.6922 18.0765H24.6345C23.124 18.0765 21.8995 16.852 21.8995 15.3414C21.8995 15.1526 22.0526 14.9995 22.2414 14.9995H27.0908"
+            stroke="white"
+            stroke-width="2"
+            stroke-linecap="round"
+          />
+          <path
+            d="M17.2653 20.2305C18.6364 20.2305 19.7408 21.3367 19.7408 22.6924C19.7406 24.0479 18.6362 25.1533 17.2653 25.1533C15.8943 25.1533 14.7899 24.0478 14.7897 22.6924C14.7897 21.3368 15.8942 20.2305 17.2653 20.2305Z"
+            stroke="white"
+            stroke-width="2"
+            stroke-linecap="round"
+          /></svg
+        >{{ type }}
+      </div>
+
+      <!-- Pagination with Progress and Fractions -->
+      <div class="swiper--pagination--wrapper">
+        <div class="swiper-pagination" ref="paginationRef"></div>
+        <div class="swiper--progressbar" ref="progressRef">
+          <div class="swiper--progressbar-fill" :style="{ width: progressWidth }"></div>
+        </div>
+        <div class="swiper--fraction" ref="fractionRef">
+          <span class="current">{{ currentSlide + 1 }}</span>
+          <span class="separator">/</span>
+          <span class="total">{{ slides.length }}</span>
+        </div>
+      </div>
+
+      <!-- 70% 영역: 단일 배너 -->
+      <div class="swiper--banner--section">
+        <div class="swiper--container" ref="swiperContainer">
+          <div class="swiper-wrapper">
+            <div v-for="(slide, index) in slides" :key="index" class="swiper-slide">
+              <div class="slide--image" v-if="slide.image">
+                <img
+                  :src="slide.image"
+                  :alt="slide.alt || 'Banner Image'"
+                  loading="lazy"
+                />
+
+                <!-- 확대 버튼 -->
+                <button class="zoom--btn" @click="openLightbox(index)" aria-label="확대">
+                  Image Details
+                  <i>
+                    <svg
+                      xmlns="http://www.w3.org/2000/svg"
+                      width="12"
+                      height="12"
+                      viewBox="0 0 12 12"
+                      fill="none"
+                    >
+                      <path
+                        d="M2.5 6H9.5"
+                        stroke="white"
+                        stroke-width="1.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M6 2.5L9.5 6L6 9.5"
+                        stroke="white"
+                        stroke-width="1.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                    </svg>
+                  </i>
+                </button>
+              </div>
+              <div class="slide--image" v-if="slide.video">
+                <video
+                  autoplay
+                  muted
+                  loop
+                  playsinline
+                  :poster="slide.video"
+                  controlslist="nodownload noplaybackrate"
+                  disablepictureinpicture
+                  preload="metadata"
+                >
+                  <source media="(min-width:320px)" :src="slide.video" type="video/mp4" />
+                </video>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- Lightbox -->
+    <Transition name="lightbox-fade">
+      <div v-if="isLightboxOpen" class="lightbox--overlay" @click.self="closeLightbox">
+        <button class="lightbox--close" @click="closeLightbox" aria-label="닫기">
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="32"
+            height="32"
+            viewBox="0 0 32 32"
+            fill="none"
+          >
+            <path
+              d="M24 8L8 24M8 8L24 24"
+              stroke="white"
+              stroke-width="2"
+              stroke-linecap="round"
+            />
+          </svg>
+        </button>
+
+        <button class="lightbox--prev" @click="prevLightboxSlide" aria-label="이전">
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="40"
+            height="40"
+            viewBox="0 0 40 40"
+            fill="none"
+          >
+            <path
+              d="M25 30L15 20L25 10"
+              stroke="white"
+              stroke-width="3"
+              stroke-linecap="round"
+              stroke-linejoin="round"
+            />
+          </svg>
+        </button>
+
+        <div class="lightbox--content">
+          <div class="lightbox--swiper" ref="lightboxSwiperContainer">
+            <div class="swiper-wrapper">
+              <div
+                v-for="(slide, index) in slides"
+                :key="`lightbox-${index}`"
+                class="swiper-slide"
+              >
+                <img
+                  v-if="slide.image"
+                  :src="slide.image"
+                  :alt="slide.alt || 'Banner Image'"
+                />
+              </div>
+            </div>
+          </div>
+
+          <!-- Lightbox Pagination Info -->
+          <div class="lightbox--info">
+            <span class="lightbox--fraction">
+              {{ lightboxCurrentSlide + 1 }} / {{ slides.length }}
+            </span>
+          </div>
+        </div>
+
+        <button class="lightbox--next" @click="nextLightboxSlide" aria-label="다음">
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="40"
+            height="40"
+            viewBox="0 0 40 40"
+            fill="none"
+          >
+            <path
+              d="M15 30L25 20L15 10"
+              stroke="white"
+              stroke-width="3"
+              stroke-linecap="round"
+              stroke-linejoin="round"
+            />
+          </svg>
+        </button>
+      </div>
+    </Transition>
+  </section>
+</template>
+
+<script setup>
+  import { Swiper } from "swiper";
+  import { Navigation, Pagination, Autoplay, EffectFade } from "swiper/modules";
+  import "swiper/css";
+  import "swiper/css/navigation";
+  import "swiper/css/pagination";
+  import "swiper/css/effect-fade";
+
+  const { getMediaUrl } = useImage();
+
+  // Props 정의
+  const props = defineProps({
+    slides: {
+      type: Array,
+      default: () => [],
+    },
+    height: {
+      type: String,
+      default: "60%",
+    },
+    title: {
+      type: String,
+      default: "",
+    },
+    subtitle: {
+      type: String,
+      default: "",
+    },
+    smalldesc: {
+      type: String,
+      default: "",
+    },
+    cautiondesc: {
+      type: String,
+      default: "",
+    },
+    morelink: {
+      type: String,
+      default: "",
+    },
+    morelinktitle: {
+      type: String,
+      default: "자세히 보기",
+    },
+    morelinktarget: {
+      type: String,
+      default: "_self",
+    },
+    notice: {
+      type: String,
+      default: "",
+    },
+    autoplay: {
+      type: [Boolean, Object],
+      default: () => ({ delay: 5000 }),
+    },
+    loop: {
+      type: Boolean,
+      default: true,
+    },
+    type: {
+      type: String,
+      default: "", // 'horz' , 'vert'
+      validator: (value) => ["exterior", "interior"].includes(value),
+    },
+    fit: {
+      type: String,
+      default: "cover",
+      validator: (value) => ["cover", "contain"].includes(value),
+    },
+  });
+
+  // Refs
+  const swiperContainer = ref(null);
+  const lightboxSwiperContainer = ref(null);
+  const paginationRef = ref(null);
+  const progressRef = ref(null);
+  const fractionRef = ref(null);
+  const prevRef = ref(null);
+  const nextRef = ref(null);
+  const textSlider = ref(null);
+  let swiperInstance = null;
+  let lightboxSwiperInstance = null;
+
+  // 현재 슬라이드 인덱스
+  const currentSlide = ref(0);
+  const lightboxCurrentSlide = ref(0);
+  const progressWidth = ref("0%");
+
+  // 라이트박스 상태
+  const isLightboxOpen = ref(false);
+
+  // 슬라이드별 텍스트 반환 함수들
+  const getSlideTitle = (index) => {
+    return props.slides[index]?.title || props.title;
+  };
+
+  const getSlideSubtitle = (index) => {
+    return props.slides[index]?.subtitle || props.subtitle;
+  };
+
+  const getSlideSmalldesc = (index) => {
+    return props.slides[index]?.smalldesc || props.smalldesc;
+  };
+
+  const getSlideMorelink = (index) => {
+    return props.slides[index]?.morelink || props.morelink;
+  };
+
+  const getSlideMorelinktitle = (index) => {
+    return props.slides[index]?.morelinktitle || props.morelinktitle;
+  };
+
+  const getSlideTarget = (index) => {
+    return props.slides[index]?.morelinktarget || props.morelinktarget;
+  };
+
+  const getSlideCautiondesc = (index) => {
+    return props.slides[index]?.cautiondesc || props.cautiondesc;
+  };
+
+  // Progress bar 업데이트 함수
+  const updateProgress = () => {
+    const progress = ((currentSlide.value + 1) / props.slides.length) * 100;
+    progressWidth.value = `${progress}%`;
+  };
+
+  // 라이트박스 열기
+  const openLightbox = (index) => {
+    isLightboxOpen.value = true;
+    document.body.style.overflow = "hidden";
+    document.querySelector(".g-layout").classList.add("active");
+
+    nextTick(() => {
+      initLightboxSwiper(index);
+    });
+  };
+
+  // 라이트박스 닫기
+  const closeLightbox = () => {
+    isLightboxOpen.value = false;
+    document.body.style.overflow = "";
+    document.querySelector(".g-layout").classList.remove("active");
+
+    if (lightboxSwiperInstance) {
+      lightboxSwiperInstance.destroy(true, true);
+      lightboxSwiperInstance = null;
+    }
+  };
+
+  // 라이트박스 Swiper 초기화
+  const initLightboxSwiper = (initialIndex = 0) => {
+    if (!lightboxSwiperContainer.value) return;
+
+    lightboxSwiperInstance = new Swiper(lightboxSwiperContainer.value, {
+      modules: [Navigation],
+      slidesPerView: 1,
+      spaceBetween: 0,
+      initialSlide: initialIndex,
+      loop: false,
+      keyboard: {
+        enabled: true,
+      },
+      on: {
+        slideChange: function () {
+          lightboxCurrentSlide.value = this.activeIndex;
+
+          // 메인 스와이퍼도 동일한 인덱스로 이동
+          if (swiperInstance && !swiperInstance.destroyed) {
+            swiperInstance.slideToLoop(this.activeIndex, 0);
+          }
+        },
+        init: function () {
+          lightboxCurrentSlide.value = this.activeIndex;
+        },
+      },
+    });
+  };
+
+  // 라이트박스 이전 슬라이드
+  const prevLightboxSlide = () => {
+    if (lightboxSwiperInstance) {
+      lightboxSwiperInstance.slidePrev();
+    }
+  };
+
+  // 라이트박스 다음 슬라이드
+  const nextLightboxSlide = () => {
+    if (lightboxSwiperInstance) {
+      lightboxSwiperInstance.slideNext();
+    }
+  };
+
+  // ESC 키로 라이트박스 닫기
+  const handleKeydown = (e) => {
+    if (e.key === "Escape" && isLightboxOpen.value) {
+      closeLightbox();
+    }
+  };
+
+  onMounted(() => {
+    // 키보드 이벤트 리스너 추가
+    window.addEventListener("keydown", handleKeydown);
+    // Swiper 인스턴스 초기화
+    swiperInstance = new Swiper(swiperContainer.value, {
+      modules: [Navigation, Pagination, Autoplay],
+      slidesPerView: 1,
+      spaceBetween: 0,
+      loop: props.loop,
+      autoplay: props.autoplay
+        ? {
+            delay:
+              typeof props.autoplay === "object" ? props.autoplay.delay || 5000 : 5000,
+            disableOnInteraction: false,
+            pauseOnMouseEnter: true,
+          }
+        : false,
+      navigation: {
+        nextEl: nextRef.value,
+        prevEl: prevRef.value,
+      },
+      pagination: {
+        el: paginationRef.value,
+        clickable: true,
+        type: "bullets",
+        // renderBullet: function (index, className) {
+        //   return '<span class="' + className + '">' + (index + 1) + "</span>";
+        // },
+      },
+      effect: "fade",
+      fadeEffect: {
+        crossFade: true,
+      },
+      speed: 800,
+      // 슬라이드 변경 이벤트
+      on: {
+        slideChange: function () {
+          if (this && this.realIndex !== undefined) {
+            currentSlide.value = this.realIndex;
+          }
+          // Progress bar 업데이트
+          updateProgress();
+
+          // 모든 비디오 일시정지
+          const allVideos = this.el.querySelectorAll("video");
+          allVideos.forEach((video) => {
+            video.pause();
+            video.currentTime = 0;
+          });
+          // 현재 슬라이드의 비디오 재생
+          const currentSlideEl = this.slides[this.activeIndex];
+          if (currentSlideEl) {
+            const video = currentSlideEl.querySelector("video");
+            if (video) {
+              video.play().catch((err) => console.log("Video play failed:", err));
+            }
+          }
+        },
+        init: function () {
+          if (this && this.realIndex !== undefined) {
+            currentSlide.value = this.realIndex;
+          }
+
+          // 초기 Progress bar 업데이트 - nextTick으로 지연
+          nextTick(() => {
+            updateProgress();
+          });
+
+          // 초기 슬라이드의 비디오 재생
+          const currentSlideEl = this.slides[this.activeIndex];
+          if (currentSlideEl) {
+            const video = currentSlideEl.querySelector("video");
+            if (video) {
+              video.play().catch((err) => console.log("Video play failed:", err));
+            }
+          }
+        },
+      },
+    });
+  });
+
+  onUnmounted(() => {
+    // 키보드 이벤트 리스너 제거
+    window.removeEventListener("keydown", handleKeydown);
+
+    // Swiper 인스턴스 정리
+    if (swiperInstance) {
+      swiperInstance.destroy(true, true);
+    }
+    if (lightboxSwiperInstance) {
+      lightboxSwiperInstance.destroy(true, true);
+    }
+
+    // body overflow 복원
+    document.body.style.overflow = "";
+  });
+</script>

+ 169 - 0
app/components/admin/AccountLockedModal.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="admin--modal-overlay">
+    <div class="admin--modal account--locked-modal">
+      <div class="admin--modal-header">
+        <h4>계정 잠금</h4>
+      </div>
+
+      <div class="admin--modal-body">
+        <div class="admin--error-box">
+          <svg
+            width="64"
+            height="64"
+            viewBox="0 0 24 24"
+            fill="none"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <circle cx="12" cy="12" r="10" stroke="#f44336" stroke-width="2" />
+            <path
+              d="M12 8v4M12 16h.01"
+              stroke="#f44336"
+              stroke-width="2"
+              stroke-linecap="round"
+            />
+          </svg>
+          <h3>계정이 잠겼습니다</h3>
+          <p class="main--message">비밀번호를 5회 틀렸습니다.</p>
+          <p class="sub--message">슈퍼 관리자에게 문의하여주세요.</p>
+        </div>
+      </div>
+
+      <div class="admin--modal-footer">
+        <button
+          type="button"
+          @click="closeModal"
+          class="admin--btn-small admin--btn-small-primary"
+        >
+          확인
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  const emit = defineEmits(["close"]);
+
+  const closeModal = () => {
+    emit("close");
+  };
+</script>
+
+<style>
+  .admin--modal-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.5);
+    z-index: 9999;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .admin--modal {
+    background: #ffffff;
+    padding: 0;
+    border-radius: 8px;
+    min-width: 450px;
+    max-width: 550px;
+    width: 90%;
+    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+    max-height: 90vh;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .account--locked-modal {
+    border: 2px solid #f44336;
+  }
+
+  .admin--modal-header {
+    padding: 20px;
+    border-bottom: 1px solid #e0e0e0;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    flex-shrink: 0;
+    background: #fafafa;
+  }
+
+  .admin--modal-header h4 {
+    margin: 0;
+    font-size: 18px;
+    font-weight: 600;
+    color: #f44336;
+  }
+
+  .admin--modal-body {
+    padding: 32px 24px;
+    overflow-y: auto;
+    flex: 1;
+    min-height: 0;
+  }
+
+  .admin--error-box {
+    text-align: center;
+    padding: 20px;
+  }
+
+  .admin--error-box svg {
+    margin: 0 auto 20px;
+    display: block;
+  }
+
+  .admin--error-box h3 {
+    margin: 0 0 16px 0;
+    font-size: 24px;
+    font-weight: 600;
+    color: #f44336;
+  }
+
+  .admin--error-box .main--message {
+    margin: 12px 0;
+    color: #1a1a1a;
+    font-size: 16px;
+    font-weight: 500;
+    line-height: 1.6;
+  }
+
+  .admin--error-box .sub--message {
+    margin: 8px 0;
+    color: #333333;
+    font-size: 14px;
+    line-height: 1.6;
+  }
+
+  .admin--modal-footer {
+    padding: 20px 24px;
+    border-top: 1px solid #e0e0e0;
+    display: flex;
+    gap: 10px;
+    justify-content: center;
+    flex-shrink: 0;
+  }
+
+  .admin--btn-small {
+    padding: 10px 24px;
+    border: none;
+    border-radius: 4px;
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    font-family: "FORDKOREAType", sans-serif;
+    white-space: nowrap;
+    min-width: 100px;
+  }
+
+  .admin--btn-small-primary {
+    background: var(--admin-accent-primary, #217346);
+    color: #ffffff;
+  }
+
+  .admin--btn-small-primary:hover:not(:disabled) {
+    background: var(--admin-accent-hover, #1a5c37);
+  }
+</style>

+ 78 - 0
app/components/admin/AdminAlertModal.vue

@@ -0,0 +1,78 @@
+<template>
+  <Transition name="modal-fade">
+    <div class="admin--modal-overlay" @click.self="handleClose">
+      <div class="admin--modal admin--modal-sm admin--alert-modal">
+        <div class="admin--modal-header">
+          <h4>{{ title }}</h4>
+          <button
+            v-if="!hideClose"
+            @click="handleClose"
+            class="admin--modal-close"
+          >&times;</button>
+        </div>
+
+        <div class="admin--modal-body">
+          <div class="admin--alert-content">
+            <p>{{ message }}</p>
+          </div>
+        </div>
+
+        <div class="admin--modal-footer">
+          <button
+            v-if="type === 'confirm'"
+            @click="handleCancel"
+            class="admin--btn-secondary"
+          >
+            취소
+          </button>
+          <button
+            @click="handleConfirm"
+            class="admin--btn-primary"
+          >
+            확인
+          </button>
+        </div>
+      </div>
+    </div>
+  </Transition>
+</template>
+
+<script setup>
+const props = defineProps({
+  title: {
+    type: String,
+    default: '알림'
+  },
+  message: {
+    type: String,
+    required: true
+  },
+  type: {
+    type: String,
+    default: 'alert', // 'alert' or 'confirm'
+    validator: (value) => ['alert', 'confirm'].includes(value)
+  },
+  hideClose: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emit = defineEmits(['confirm', 'cancel', 'close'])
+
+const handleConfirm = () => {
+  emit('confirm')
+  emit('close')
+}
+
+const handleCancel = () => {
+  emit('cancel')
+  emit('close')
+}
+
+const handleClose = () => {
+  if (!props.hideClose) {
+    emit('close')
+  }
+}
+</script>

+ 25 - 0
app/components/admin/AdminLoadingOverlay.vue

@@ -0,0 +1,25 @@
+<template>
+  <Transition name="loading-fade">
+    <div v-if="isGlobalLoading" class="admin--loading-overlay">
+      <div class="admin--spinner"></div>
+    </div>
+  </Transition>
+</template>
+
+<script setup>
+import { useLoading } from '~/composables/useLoading'
+
+const { isGlobalLoading } = useLoading()
+</script>
+
+<style scoped>
+.loading-fade-enter-active,
+.loading-fade-leave-active {
+  transition: opacity 0.2s ease;
+}
+
+.loading-fade-enter-from,
+.loading-fade-leave-to {
+  opacity: 0;
+}
+</style>

+ 479 - 0
app/components/admin/AdminModal.vue

@@ -0,0 +1,479 @@
+<template>
+  <div class="admin--modal-overlay" @click.self="close">
+    <div class="admin--modal">
+      <div class="admin--modal-header">
+        <h4>{{ isEdit ? '관리자 수정' : '관리자 추가' }}</h4>
+        <button @click="close" class="admin--modal-close">&times;</button>
+      </div>
+
+      <form @submit.prevent="save" class="admin--modal-form">
+        <div class="admin--modal-body">
+          <div class="admin--form-row">
+            <div class="admin--form-group">
+              <label class="admin--form-label">아이디 *</label>
+              <div v-if="!isEdit" class="admin--input-with-button">
+                <input
+                  v-model="formData.username"
+                  type="text"
+                  class="admin--form-input"
+                  required
+                  placeholder="영문, 숫자 조합"
+                  @input="handleUsernameInput"
+                />
+                <button
+                  type="button"
+                  class="admin--btn-check"
+                  @click="checkDuplicateUsername"
+                  :disabled="!formData.username || isCheckingUsername"
+                >
+                  {{ isCheckingUsername ? '확인 중...' : '중복 체크' }}
+                </button>
+              </div>
+              <input
+                v-else
+                v-model="formData.username"
+                type="text"
+                class="admin--form-input"
+                required
+                :disabled="true"
+                placeholder="영문, 숫자 조합"
+              />
+              <p v-if="!isEdit && usernameCheckResult === 'available'" class="admin--form-help admin--text-success">
+                사용 가능한 아이디입니다.
+              </p>
+              <p v-if="!isEdit && usernameCheckResult === 'duplicate'" class="admin--form-help admin--text-error">
+                이미 사용 중인 아이디입니다.
+              </p>
+            </div>
+
+            <div class="admin--form-group">
+              <label class="admin--form-label">이름 *</label>
+              <input
+                v-model="formData.name"
+                type="text"
+                class="admin--form-input"
+                required
+                placeholder="관리자 이름"
+              />
+            </div>
+          </div>
+
+          <div class="admin--form-row">
+            <div class="admin--form-group">
+              <label class="admin--form-label">이메일 *</label>
+              <input
+                v-model="formData.email"
+                type="email"
+                class="admin--form-input"
+                required
+                placeholder="example@email.com"
+              />
+            </div>
+
+            <div class="admin--form-group">
+              <label class="admin--form-label">부서명</label>
+              <input
+                v-model="formData.department"
+                type="text"
+                class="admin--form-input"
+                placeholder="예: 개발팀, 영업팀"
+              />
+            </div>
+          </div>
+
+          <div class="admin--form-row">
+            <div class="admin--form-group">
+              <label class="admin--form-label">역할 *</label>
+              <select v-model="formData.role" class="admin--form-select" required>
+                <option value="admin">일반 관리자</option>
+                <option value="super_admin">슈퍼 관리자</option>
+              </select>
+            </div>
+
+            <div class="admin--form-group">
+              <label class="admin--form-label">상태 *</label>
+              <select v-model="formData.status" class="admin--form-select" required>
+                <option value="active">활성</option>
+                <option value="inactive">비활성</option>
+              </select>
+            </div>
+          </div>
+
+          <div class="admin--form-row" v-if="!isEdit">
+            <div class="admin--form-group">
+              <label class="admin--form-label">비밀번호 *</label>
+              <input
+                v-model="formData.password"
+                type="password"
+                class="admin--form-input"
+                :required="!isEdit"
+                placeholder="최소 8자 이상"
+                minlength="8"
+              />
+            </div>
+
+            <div class="admin--form-group">
+              <label class="admin--form-label">비밀번호 확인 *</label>
+              <input
+                v-model="formData.password_confirm"
+                type="password"
+                class="admin--form-input"
+                :required="!isEdit"
+                placeholder="비밀번호 재입력"
+                minlength="8"
+              />
+            </div>
+          </div>
+        </div>
+
+        <div class="admin--modal-footer">
+          <button type="button" @click="close" class="admin--btn-small admin--btn-small-secondary">
+            취소
+          </button>
+          <button type="submit" class="admin--btn-small admin--btn-small-primary" :disabled="saving">
+            {{ saving ? '저장 중...' : '저장' }}
+          </button>
+        </div>
+      </form>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, watch } from 'vue'
+
+const props = defineProps({
+  admin: {
+    type: Object,
+    default: null
+  }
+})
+
+const emit = defineEmits(['close', 'saved'])
+
+const { get, post, put } = useApi()
+
+const isEdit = computed(() => !!props.admin)
+const saving = ref(false)
+const isCheckingUsername = ref(false)
+const usernameCheckResult = ref('') // 'available', 'duplicate', ''
+
+const formData = ref({
+  username: '',
+  name: '',
+  email: '',
+  department: '',
+  password: '',
+  password_confirm: '',
+  role: 'admin',
+  status: 'active'
+})
+
+// props.admin이 변경되면 formData 업데이트
+watch(() => props.admin, (newAdmin) => {
+  if (newAdmin) {
+    formData.value = {
+      username: newAdmin.username || '',
+      name: newAdmin.name || '',
+      email: newAdmin.email || '',
+      department: newAdmin.department || '',
+      password: '',
+      password_confirm: '',
+      role: newAdmin.role || 'admin',
+      status: newAdmin.status || 'active'
+    }
+  }
+}, { immediate: true })
+
+// 아이디 입력 시 중복 체크 결과 초기화
+const handleUsernameInput = () => {
+  usernameCheckResult.value = ''
+}
+
+// 아이디 중복 체크
+const checkDuplicateUsername = async () => {
+  if (!formData.value.username) {
+    alert('아이디를 입력하세요.')
+    return
+  }
+
+  isCheckingUsername.value = true
+  usernameCheckResult.value = ''
+
+  try {
+    const { data, error } = await get('/admin/check-username', {
+      params: {
+        username: formData.value.username
+      }
+    })
+
+    console.log('[AdminModal] 중복 체크 응답:', { data, error })
+
+    if (error) {
+      alert('중복 체크에 실패했습니다.')
+      return
+    }
+
+    if (data?.success) {
+      if (data?.data?.available) {
+        usernameCheckResult.value = 'available'
+      } else {
+        usernameCheckResult.value = 'duplicate'
+      }
+    }
+  } catch (error) {
+    console.error('중복 체크 오류:', error)
+    alert('중복 체크 중 오류가 발생했습니다.')
+  } finally {
+    isCheckingUsername.value = false
+  }
+}
+
+const close = () => {
+  emit('close')
+}
+
+const save = async () => {
+  // 신규 생성 시 유효성 검사
+  if (!isEdit.value) {
+    // 아이디 중복 체크 확인
+    if (usernameCheckResult.value !== 'available') {
+      alert('아이디 중복 체크를 해주세요.')
+      return
+    }
+
+    // 비밀번호 확인
+    if (formData.value.password !== formData.value.password_confirm) {
+      alert('비밀번호가 일치하지 않습니다.')
+      return
+    }
+    if (formData.value.password.length < 8) {
+      alert('비밀번호는 최소 8자 이상이어야 합니다.')
+      return
+    }
+  }
+
+  saving.value = true
+
+  try {
+    let result
+
+    if (isEdit.value) {
+      // 수정
+      const updateData = {
+        name: formData.value.name,
+        email: formData.value.email,
+        department: formData.value.department,
+        role: formData.value.role,
+        status: formData.value.status
+      }
+      result = await put(`/admin/${props.admin.id}`, updateData)
+    } else {
+      // 생성
+      const createData = {
+        username: formData.value.username,
+        name: formData.value.name,
+        email: formData.value.email,
+        department: formData.value.department,
+        password: formData.value.password,
+        role: formData.value.role,
+        status: formData.value.status
+      }
+      result = await post('/admin', createData)
+    }
+
+    const { data, error } = result
+
+    if (error) {
+      console.error('[AdminModal] 저장 실패:', error)
+      const errorMessage = error.response?.data?.message || '저장에 실패했습니다.'
+      emit('saved', errorMessage)
+      return
+    }
+
+    if (data?.success) {
+      emit('saved', data.message || '저장되었습니다.')
+    }
+  } finally {
+    saving.value = false
+  }
+}
+</script>
+
+<style scoped>
+.admin--modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.admin--modal {
+  background: #ffffff;
+  padding: 0;
+  border-radius: 8px;
+  min-width: 700px;
+  max-width: 800px;
+  width: 90%;
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+  max-height: 90vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.admin--modal-form {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  min-height: 0;
+}
+
+.admin--modal-header {
+  padding: 20px;
+  border-bottom: 1px solid #e0e0e0;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  flex-shrink: 0;
+}
+
+.admin--modal-header h4 {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.admin--modal-close {
+  background: none;
+  border: none;
+  font-size: 28px;
+  cursor: pointer;
+  color: #999;
+  line-height: 1;
+  transition: color 0.2s;
+  padding: 0;
+  width: 30px;
+  height: 30px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.admin--modal-close:hover {
+  color: #333;
+}
+
+.admin--modal-body {
+  padding: 24px;
+  overflow-y: auto;
+  flex: 1;
+  min-height: 0;
+}
+
+.admin--form-row {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 16px;
+  margin-bottom: 20px;
+}
+
+.admin--form-row:last-child {
+  margin-bottom: 0;
+}
+
+.admin--form-group {
+  display: flex;
+  flex-direction: column;
+}
+
+.admin--form-label {
+  display: block;
+  margin-bottom: 8px;
+  font-size: 14px;
+  font-weight: 500;
+  color: #333333;
+}
+
+.admin--form-input,
+.admin--form-select {
+  width: 100%;
+  padding: 10px 14px;
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+  background: #ffffff;
+  color: #333333;
+  font-size: 14px;
+  transition: border-color 0.2s;
+}
+
+.admin--form-input:focus,
+.admin--form-select:focus {
+  outline: none;
+  border-color: var(--admin-accent-primary, #217346);
+}
+
+.admin--form-input:disabled {
+  background: #f5f5f5;
+  color: #999;
+  cursor: not-allowed;
+}
+
+.admin--form-input::placeholder {
+  color: #999;
+}
+
+.admin--modal-footer {
+  padding: 20px 24px;
+  border-top: 1px solid #e0e0e0;
+  display: flex;
+  gap: 10px;
+  justify-content: flex-end;
+  flex-shrink: 0;
+}
+
+.admin--btn-small {
+  padding: 6px 14px;
+  border: none;
+  border-radius: 4px;
+  font-size: 13px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  font-family: 'FORDKOREAType', sans-serif;
+  white-space: nowrap;
+}
+
+.admin--btn-small:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.admin--btn-small-secondary {
+  background: #f5f5f5;
+  color: #333333;
+  border: 1px solid #e0e0e0;
+}
+
+.admin--btn-small-secondary:hover:not(:disabled) {
+  background: #eeeeee;
+  border-color: var(--admin-accent-primary, #217346);
+  color: #333333;
+}
+
+.admin--btn-small-primary {
+  background: var(--admin-accent-primary, #217346);
+  color: #ffffff;
+}
+
+.admin--btn-small-primary:hover:not(:disabled) {
+  background: var(--admin-accent-hover, #1a5c37);
+}
+
+</style>

+ 197 - 0
app/components/admin/DatePicker.vue

@@ -0,0 +1,197 @@
+<template>
+  <div class="admin--datepicker">
+    <input
+      ref="dateInput"
+      type="text"
+      :value="modelValue"
+      :placeholder="placeholder"
+      :class="['admin--form-input', { 'is-required': required }]"
+      :required="required"
+      readonly
+    >
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
+import flatpickr from 'flatpickr'
+import { Korean } from 'flatpickr/dist/l10n/ko'
+import 'flatpickr/dist/flatpickr.min.css'
+import 'flatpickr/dist/themes/dark.css'
+
+const props = defineProps({
+  modelValue: {
+    type: String,
+    default: ''
+  },
+  placeholder: {
+    type: String,
+    default: '날짜 선택'
+  },
+  required: {
+    type: Boolean,
+    default: false
+  },
+  mode: {
+    type: String,
+    default: 'single', // single, range
+    validator: (value) => ['single', 'range'].includes(value)
+  },
+  minDate: {
+    type: String,
+    default: null
+  },
+  maxDate: {
+    type: String,
+    default: null
+  }
+})
+
+const emit = defineEmits(['update:modelValue'])
+
+const dateInput = ref(null)
+let flatpickrInstance = null
+
+onMounted(() => {
+  flatpickrInstance = flatpickr(dateInput.value, {
+    locale: Korean,
+    dateFormat: 'Y-m-d',
+    mode: props.mode,
+    minDate: props.minDate,
+    maxDate: props.maxDate,
+    defaultDate: props.modelValue || null,
+    onChange: (_selectedDates, dateStr) => {
+      emit('update:modelValue', dateStr)
+    },
+    // 다크 테마 설정
+    theme: 'dark',
+    // 월/년 선택 드롭다운 활성화
+    monthSelectorType: 'dropdown',
+    // 시간 선택 비활성화
+    enableTime: false,
+    // 주말 강조
+    onDayCreate: (_dObj, _dStr, _fp, dayElem) => {
+      const day = dayElem.dateObj.getDay()
+      if (day === 0) {
+        dayElem.classList.add('weekend-sunday')
+      } else if (day === 6) {
+        dayElem.classList.add('weekend-saturday')
+      }
+    }
+  })
+})
+
+// modelValue 변경 감지
+watch(() => props.modelValue, (newValue) => {
+  if (flatpickrInstance && newValue !== flatpickrInstance.input.value) {
+    flatpickrInstance.setDate(newValue || null, false)
+  }
+})
+
+// minDate, maxDate 변경 감지
+watch([() => props.minDate, () => props.maxDate], ([newMinDate, newMaxDate]) => {
+  if (flatpickrInstance) {
+    flatpickrInstance.set('minDate', newMinDate)
+    flatpickrInstance.set('maxDate', newMaxDate)
+  }
+})
+
+onBeforeUnmount(() => {
+  if (flatpickrInstance) {
+    flatpickrInstance.destroy()
+  }
+})
+</script>
+
+<style scoped>
+.admin--datepicker {
+  position: relative;
+}
+
+.admin--datepicker .admin--form-input {
+  cursor: pointer;
+  background: var(--admin-bg-tertiary, #252525);
+  color: var(--admin-text-primary, #ffffff) !important;
+  border: 1px solid var(--admin-border-color, #333333);
+}
+
+.admin--datepicker .admin--form-input:read-only {
+  background: var(--admin-bg-tertiary, #252525);
+  color: var(--admin-text-primary, #ffffff) !important;
+}
+
+.admin--datepicker .admin--form-input::placeholder {
+  color: var(--admin-text-muted, #666666);
+}
+
+.admin--datepicker .admin--form-input:focus {
+  outline: none;
+  border-color: var(--admin-accent-primary, #bb0a30);
+  box-shadow: 0 0 0 3px rgba(187, 10, 48, 0.1);
+}
+
+/* 다크 테마 커스터마이징 */
+:deep(.flatpickr-calendar.dark) {
+  background: #2c3e50;
+  border-color: #34495e;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+}
+
+:deep(.flatpickr-calendar.dark .flatpickr-months) {
+  background: #34495e;
+}
+
+:deep(.flatpickr-calendar.dark .flatpickr-current-month .flatpickr-monthDropdown-months) {
+  background: #2c3e50;
+  color: #ecf0f1;
+}
+
+:deep(.flatpickr-calendar.dark .flatpickr-day.selected) {
+  background: #3498db;
+  border-color: #2980b9;
+}
+
+:deep(.flatpickr-calendar.dark .flatpickr-day.selected:hover) {
+  background: #2980b9;
+}
+
+:deep(.flatpickr-calendar.dark .flatpickr-day:hover) {
+  background: #34495e;
+}
+
+:deep(.flatpickr-calendar.dark .flatpickr-day.today) {
+  border-color: #3498db;
+}
+
+:deep(.flatpickr-calendar.dark .flatpickr-day.today:hover) {
+  background: #34495e;
+  border-color: #3498db;
+}
+
+/* 주말 색상 */
+:deep(.flatpickr-day.weekend-sunday) {
+  color: #e74c3c !important;
+}
+
+:deep(.flatpickr-day.weekend-saturday) {
+  color: #3498db !important;
+}
+
+:deep(.flatpickr-day.weekend-sunday.selected),
+:deep(.flatpickr-day.weekend-saturday.selected) {
+  color: #fff !important;
+}
+
+/* Range 모드 스타일 */
+:deep(.flatpickr-day.inRange) {
+  background: rgba(52, 152, 219, 0.2);
+  border-color: transparent;
+  box-shadow: -5px 0 0 rgba(52, 152, 219, 0.2), 5px 0 0 rgba(52, 152, 219, 0.2);
+}
+
+:deep(.flatpickr-day.startRange),
+:deep(.flatpickr-day.endRange) {
+  background: #3498db;
+  color: #fff;
+}
+</style>

+ 337 - 0
app/components/admin/PasswordChangeRecommendModal.vue

@@ -0,0 +1,337 @@
+<template>
+  <div class="admin--modal-overlay">
+    <div class="admin--modal password--recommend-modal">
+      <div class="admin--modal-header">
+        <h4>비밀번호 변경 권장</h4>
+      </div>
+
+      <div class="admin--modal-body">
+        <div class="admin--warning-box">
+          <svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <path d="M12 2L2 20h20L12 2zm0 5.5l6.5 11h-13L12 7.5z" fill="#ff9800"/>
+            <path d="M11 10h2v5h-2v-5zm0 6h2v2h-2v-2z" fill="#fff"/>
+          </svg>
+          <h3>비밀번호 변경이 필요합니다</h3>
+          <p>마지막 비밀번호 변경 후 6개월이 경과하였습니다.</p>
+          <p>보안을 위해 비밀번호를 변경해주세요.</p>
+        </div>
+
+        <form @submit.prevent="changePassword" v-if="showPasswordForm">
+          <div class="admin--form-group">
+            <label class="admin--form-label">현재 비밀번호 *</label>
+            <input
+              v-model="formData.current_password"
+              type="password"
+              class="admin--form-input"
+              required
+              placeholder="현재 비밀번호 입력"
+            />
+          </div>
+
+          <div class="admin--form-group">
+            <label class="admin--form-label">새 비밀번호 *</label>
+            <input
+              v-model="formData.new_password"
+              type="password"
+              class="admin--form-input"
+              required
+              placeholder="최소 8자 이상"
+              minlength="8"
+            />
+          </div>
+
+          <div class="admin--form-group">
+            <label class="admin--form-label">새 비밀번호 확인 *</label>
+            <input
+              v-model="formData.new_password_confirm"
+              type="password"
+              class="admin--form-input"
+              required
+              placeholder="새 비밀번호 재입력"
+              minlength="8"
+            />
+          </div>
+
+          <div v-if="errorMessage" class="admin--error-message">
+            {{ errorMessage }}
+          </div>
+        </form>
+      </div>
+
+      <div class="admin--modal-footer">
+        <button
+          type="button"
+          @click="skipAndContinue"
+          class="admin--btn-small admin--btn-small-secondary"
+        >
+          나중에 변경
+        </button>
+        <button
+          v-if="!showPasswordForm"
+          type="button"
+          @click="showPasswordForm = true"
+          class="admin--btn-small admin--btn-small-primary"
+        >
+          지금 변경
+        </button>
+        <button
+          v-else
+          type="button"
+          @click="changePassword"
+          class="admin--btn-small admin--btn-small-primary"
+          :disabled="saving"
+        >
+          {{ saving ? '변경 중...' : '변경' }}
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+
+const props = defineProps({
+  adminId: {
+    type: Number,
+    required: true
+  }
+})
+
+const emit = defineEmits(['close', 'changed'])
+
+const { post } = useApi()
+
+const showPasswordForm = ref(false)
+const saving = ref(false)
+const errorMessage = ref('')
+
+const formData = ref({
+  current_password: '',
+  new_password: '',
+  new_password_confirm: ''
+})
+
+const skipAndContinue = () => {
+  emit('close')
+}
+
+const changePassword = async () => {
+  errorMessage.value = ''
+
+  // 비밀번호 확인
+  if (formData.value.new_password !== formData.value.new_password_confirm) {
+    errorMessage.value = '새 비밀번호가 일치하지 않습니다.'
+    return
+  }
+
+  if (formData.value.new_password.length < 8) {
+    errorMessage.value = '비밀번호는 최소 8자 이상이어야 합니다.'
+    return
+  }
+
+  if (!formData.value.current_password) {
+    errorMessage.value = '현재 비밀번호를 입력해주세요.'
+    return
+  }
+
+  saving.value = true
+
+  try {
+    const requestData = {
+      current_password: formData.value.current_password,
+      new_password: formData.value.new_password
+    }
+
+    const { data, error } = await post(`/admin/${props.adminId}/password`, requestData)
+
+    if (error) {
+      console.error('[PasswordChangeRecommendModal] 변경 실패:', error)
+      errorMessage.value = error.response?.data?.message || '비밀번호 변경에 실패했습니다.'
+      return
+    }
+
+    if (data?.success) {
+      emit('changed', '비밀번호가 변경되었습니다.')
+    }
+  } finally {
+    saving.value = false
+  }
+}
+</script>
+
+<style>
+.admin--modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.admin--modal {
+  background: #ffffff;
+  padding: 0;
+  border-radius: 8px;
+  min-width: 500px;
+  max-width: 600px;
+  width: 90%;
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+  max-height: 90vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.password--recommend-modal {
+  border: 2px solid #ff9800;
+}
+
+.admin--modal-header {
+  padding: 20px;
+  border-bottom: 1px solid #e0e0e0;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  flex-shrink: 0;
+  background: #fafafa;
+}
+
+.admin--modal-header h4 {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.admin--modal-body {
+  padding: 24px;
+  overflow-y: auto;
+  flex: 1;
+  min-height: 0;
+}
+
+.admin--warning-box {
+  text-align: center;
+  padding: 20px;
+  margin-bottom: 24px;
+}
+
+.admin--warning-box svg {
+  margin: 0 auto 16px;
+  display: block;
+}
+
+.admin--warning-box h3 {
+  margin: 0 0 12px 0;
+  font-size: 20px;
+  font-weight: 600;
+  color: #ff9800;
+}
+
+.admin--warning-box p {
+  margin: 6px 0;
+  color: #333333;
+  font-size: 14px;
+  line-height: 1.6;
+}
+
+.admin--form-group {
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 20px;
+}
+
+.admin--form-group:last-child {
+  margin-bottom: 0;
+}
+
+.admin--form-label {
+  display: block;
+  margin-bottom: 8px;
+  font-size: 14px;
+  font-weight: 500;
+  color: #333333;
+}
+
+.admin--form-input {
+  width: 100%;
+  padding: 10px 14px;
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+  background: #ffffff;
+  color: #333333;
+  font-size: 14px;
+  transition: border-color 0.2s;
+}
+
+.admin--form-input:focus {
+  outline: none;
+  border-color: var(--admin-accent-primary, #217346);
+}
+
+.admin--form-input::placeholder {
+  color: #999;
+}
+
+.admin--error-message {
+  margin-top: 12px;
+  padding: 12px;
+  background: rgba(244, 67, 54, 0.1);
+  border: 1px solid rgba(244, 67, 54, 0.3);
+  border-radius: 4px;
+  color: #f44336;
+  font-size: 13px;
+}
+
+.admin--modal-footer {
+  padding: 20px 24px;
+  border-top: 1px solid #e0e0e0;
+  display: flex;
+  gap: 10px;
+  justify-content: flex-end;
+  flex-shrink: 0;
+}
+
+.admin--btn-small {
+  padding: 8px 16px;
+  border: none;
+  border-radius: 4px;
+  font-size: 13px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  font-family: 'FORDKOREAType', sans-serif;
+  white-space: nowrap;
+}
+
+.admin--btn-small:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.admin--btn-small-secondary {
+  background: #f5f5f5;
+  color: #333333;
+  border: 1px solid #e0e0e0;
+}
+
+.admin--btn-small-secondary:hover:not(:disabled) {
+  background: #eeeeee;
+  border-color: var(--admin-accent-primary, #217346);
+  color: #333333;
+}
+
+.admin--btn-small-primary {
+  background: var(--admin-accent-primary, #217346);
+  color: #ffffff;
+}
+
+.admin--btn-small-primary:hover:not(:disabled) {
+  background: var(--admin-accent-hover, #1a5c37);
+}
+</style>

+ 336 - 0
app/components/admin/PasswordModal.vue

@@ -0,0 +1,336 @@
+<template>
+  <div class="admin--modal-overlay" @click.self="close">
+    <div class="admin--modal">
+      <div class="admin--modal-header">
+        <h4>비밀번호 변경</h4>
+        <button @click="close" class="admin--modal-close">&times;</button>
+      </div>
+
+      <form @submit.prevent="save" class="admin--modal-form">
+        <div class="admin--modal-body">
+          <div class="admin--info-box">
+            <p><strong>관리자:</strong> {{ admin.name }} ({{ admin.username }})</p>
+          </div>
+
+          <div class="admin--form-group" v-if="isCurrentUser">
+            <label class="admin--form-label">현재 비밀번호</label>
+            <input
+              v-model="formData.current_password"
+              type="password"
+              class="admin--form-input"
+              placeholder="현재 비밀번호 입력"
+            />
+            <small class="admin--help-text">본인 계정의 경우 현재 비밀번호를 입력해주세요.</small>
+          </div>
+
+          <div class="admin--form-group">
+            <label class="admin--form-label">새 비밀번호 *</label>
+            <input
+              v-model="formData.new_password"
+              type="password"
+              class="admin--form-input"
+              required
+              placeholder="최소 8자 이상"
+              minlength="8"
+            />
+          </div>
+
+          <div class="admin--form-group">
+            <label class="admin--form-label">새 비밀번호 확인 *</label>
+            <input
+              v-model="formData.new_password_confirm"
+              type="password"
+              class="admin--form-input"
+              required
+              placeholder="새 비밀번호 재입력"
+              minlength="8"
+            />
+          </div>
+        </div>
+
+        <div class="admin--modal-footer">
+          <button type="button" @click="close" class="admin--btn-small admin--btn-small-secondary">
+            취소
+          </button>
+          <button type="submit" class="admin--btn-small admin--btn-small-primary" :disabled="saving">
+            {{ saving ? '변경 중...' : '변경' }}
+          </button>
+        </div>
+      </form>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue'
+
+const props = defineProps({
+  admin: {
+    type: Object,
+    required: true
+  }
+})
+
+const emit = defineEmits(['close', 'saved'])
+
+const { post } = useApi()
+
+const saving = ref(false)
+
+// 현재 로그인한 관리자 ID
+const currentAdminId = computed(() => {
+  if (typeof window === 'undefined') return null
+  const user = localStorage.getItem('admin_user')
+  if (!user) return null
+  try {
+    return JSON.parse(user).id
+  } catch {
+    return null
+  }
+})
+
+const isCurrentUser = computed(() => {
+  return currentAdminId.value === props.admin.id
+})
+
+const formData = ref({
+  current_password: '',
+  new_password: '',
+  new_password_confirm: ''
+})
+
+const close = () => {
+  emit('close')
+}
+
+const save = async () => {
+  // 비밀번호 확인
+  if (formData.value.new_password !== formData.value.new_password_confirm) {
+    emit('saved', '새 비밀번호가 일치하지 않습니다.')
+    return
+  }
+
+  if (formData.value.new_password.length < 8) {
+    emit('saved', '비밀번호는 최소 8자 이상이어야 합니다.')
+    return
+  }
+
+  saving.value = true
+
+  try {
+    const requestData = {
+      new_password: formData.value.new_password
+    }
+
+    // 본인 계정인 경우 현재 비밀번호 포함
+    if (isCurrentUser.value && formData.value.current_password) {
+      requestData.current_password = formData.value.current_password
+    }
+
+    const { data, error } = await post(`/admin/${props.admin.id}/password`, requestData)
+
+    if (error) {
+      console.error('[PasswordModal] 변경 실패:', error)
+      const errorMessage = error.response?.data?.message || '비밀번호 변경에 실패했습니다.'
+      emit('saved', errorMessage)
+      return
+    }
+
+    if (data?.success) {
+      emit('saved', '비밀번호가 변경되었습니다.')
+    }
+  } finally {
+    saving.value = false
+  }
+}
+</script>
+
+<style scoped>
+.admin--modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.admin--modal {
+  background: #ffffff;
+  padding: 0;
+  border-radius: 8px;
+  min-width: 500px;
+  max-width: 600px;
+  width: 90%;
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+  max-height: 90vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.admin--modal-form {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  min-height: 0;
+}
+
+.admin--modal-header {
+  padding: 20px;
+  border-bottom: 1px solid #e0e0e0;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  flex-shrink: 0;
+}
+
+.admin--modal-header h4 {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.admin--modal-close {
+  background: none;
+  border: none;
+  font-size: 28px;
+  cursor: pointer;
+  color: #999;
+  line-height: 1;
+  transition: color 0.2s;
+  padding: 0;
+  width: 30px;
+  height: 30px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.admin--modal-close:hover {
+  color: #333;
+}
+
+.admin--modal-body {
+  padding: 24px;
+  overflow-y: auto;
+  flex: 1;
+  min-height: 0;
+}
+
+.admin--info-box {
+  background: #f5f5f5;
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+  padding: 12px 16px;
+  margin-bottom: 20px;
+}
+
+.admin--info-box p {
+  margin: 0;
+  color: #333333;
+  font-size: 14px;
+}
+
+.admin--info-box strong {
+  color: #1a1a1a;
+  font-weight: 600;
+}
+
+.admin--form-group {
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 20px;
+}
+
+.admin--form-group:last-child {
+  margin-bottom: 0;
+}
+
+.admin--form-label {
+  display: block;
+  margin-bottom: 8px;
+  font-size: 14px;
+  font-weight: 500;
+  color: #333333;
+}
+
+.admin--form-input {
+  width: 100%;
+  padding: 10px 14px;
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+  background: #ffffff;
+  color: #333333;
+  font-size: 14px;
+  transition: border-color 0.2s;
+}
+
+.admin--form-input:focus {
+  outline: none;
+  border-color: var(--admin-accent-primary, #217346);
+}
+
+.admin--form-input::placeholder {
+  color: #999;
+}
+
+.admin--help-text {
+  display: block;
+  margin-top: 6px;
+  font-size: 12px;
+  color: #666;
+  line-height: 1.4;
+}
+
+.admin--modal-footer {
+  padding: 20px 24px;
+  border-top: 1px solid #e0e0e0;
+  display: flex;
+  gap: 10px;
+  justify-content: flex-end;
+  flex-shrink: 0;
+}
+
+.admin--btn-small {
+  padding: 6px 14px;
+  border: none;
+  border-radius: 4px;
+  font-size: 13px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  font-family: 'FORDKOREAType', sans-serif;
+  white-space: nowrap;
+}
+
+.admin--btn-small:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.admin--btn-small-secondary {
+  background: #f5f5f5;
+  color: #333333;
+  border: 1px solid #e0e0e0;
+}
+
+.admin--btn-small-secondary:hover:not(:disabled) {
+  background: #eeeeee;
+  border-color: var(--admin-accent-primary, #217346);
+  color: #333333;
+}
+
+.admin--btn-small-primary {
+  background: var(--admin-accent-primary, #217346);
+  color: #ffffff;
+}
+
+.admin--btn-small-primary:hover:not(:disabled) {
+  background: var(--admin-accent-hover, #1a5c37);
+}
+</style>

+ 203 - 0
app/components/admin/SunEditor.vue

@@ -0,0 +1,203 @@
+<template>
+  <div class="admin--suneditor-wrapper">
+    <div ref="editorElement" class="admin--suneditor"></div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
+import suneditor from 'suneditor'
+import 'suneditor/dist/css/suneditor.min.css'
+import plugins from 'suneditor/src/plugins'
+
+const props = defineProps({
+  modelValue: {
+    type: String,
+    default: ''
+  },
+  height: {
+    type: String,
+    default: '400px'
+  },
+  placeholder: {
+    type: String,
+    default: '내용을 입력하세요...'
+  }
+})
+
+const emit = defineEmits(['update:modelValue'])
+
+const editorElement = ref(null)
+let editorInstance = null
+
+const { upload } = useApi()
+const { getImageUrl } = useImage()
+
+onMounted(() => {
+  if (editorElement.value) {
+    editorInstance = suneditor.create(editorElement.value, {
+      plugins: plugins,
+      height: props.height,
+      buttonList: [
+        ['undo', 'redo'],
+        ['font', 'fontSize', 'formatBlock'],
+        ['bold', 'underline', 'italic', 'strike', 'subscript', 'superscript'],
+        ['fontColor', 'hiliteColor'],
+        ['removeFormat'],
+        ['outdent', 'indent'],
+        ['align', 'horizontalRule', 'list', 'table'],
+        ['link', 'image', 'video'],
+        ['fullScreen', 'showBlocks', 'codeView'],
+        ['preview', 'print']
+      ],
+      font: ['Arial', 'Courier New', 'Georgia', 'Tahoma', 'Trebuchet MS', 'Verdana', 'FORDKOREAType'],
+      formats: ['p', 'div', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
+      colorList: [
+        '#ff0000', '#ff5e00', '#ffe400', '#abf200', '#00d8ff', '#0055ff', '#6600ff', '#ff00dd',
+        '#000000', '#ffffff', '#888888', '#cccccc'
+      ],
+      imageResizing: true,
+      imageHeightShow: true,
+      imageWidth: '100%',
+      placeholder: props.placeholder,
+      callBackSave: function (contents) {
+        emit('update:modelValue', contents)
+      }
+    })
+
+    // 커스텀 이미지 업로드 핸들러
+    editorInstance.onImageUploadBefore = function (files, info, core) {
+      console.log('[SunEditor] onImageUploadBefore 호출, files:', files, 'info:', info)
+
+      // 비동기 파일 업로드 처리
+      ;(async () => {
+        for (const file of files) {
+          const formData = new FormData()
+          formData.append('file', file)
+
+          try {
+            const { data: uploadData, error } = await upload('/upload/event-file', formData)
+
+            console.log('[SunEditor] 업로드 응답:', uploadData)
+
+            if (uploadData?.success && uploadData?.data?.url) {
+              // 전체 URL 생성
+              const fullUrl = getImageUrl(uploadData.data.url)
+
+              console.log('[SunEditor] 원본 URL:', uploadData.data.url)
+              console.log('[SunEditor] 전체 URL:', fullUrl)
+
+              // 에디터에 직접 이미지 태그 삽입
+              const imgTag = `<img src="${fullUrl}" alt="${file.name}" />`
+              editorInstance.insertHTML(imgTag)
+
+              console.log('[SunEditor] 이미지 삽입 완료:', imgTag)
+            } else {
+              console.error('[SunEditor] 이미지 업로드 응답 오류:', uploadData, error)
+            }
+          } catch (error) {
+            console.error('[SunEditor] 이미지 업로드 실패:', error)
+          }
+        }
+      })()
+
+      // 기본 동작 막기
+      return false
+    }
+
+    // 초기값 설정
+    if (props.modelValue) {
+      editorInstance.setContents(props.modelValue)
+    }
+
+    // 내용 변경 이벤트
+    editorInstance.onChange = (contents) => {
+      emit('update:modelValue', contents)
+    }
+  }
+})
+
+// modelValue 변경 감지
+watch(() => props.modelValue, (newValue) => {
+  if (editorInstance && editorInstance.getContents() !== newValue) {
+    editorInstance.setContents(newValue || '')
+  }
+})
+
+onBeforeUnmount(() => {
+  if (editorInstance) {
+    editorInstance.destroy()
+  }
+})
+</script>
+
+<style scoped>
+.admin--suneditor-wrapper {
+  width: 100%;
+}
+
+.admin--suneditor-wrapper :deep(.sun-editor) {
+  background: var(--admin-bg-tertiary);
+  border: 1px solid var(--admin-border-color);
+  border-radius: 6px;
+}
+
+.admin--suneditor-wrapper :deep(.se-toolbar) {
+  background: var(--admin-bg-secondary);
+  border-bottom: 1px solid var(--admin-border-color);
+}
+
+.admin--suneditor-wrapper :deep(.se-btn) {
+  color: var(--admin-text-secondary);
+}
+
+.admin--suneditor-wrapper :deep(.se-btn:hover) {
+  background: var(--admin-bg-tertiary);
+  color: var(--admin-text-primary);
+}
+
+.admin--suneditor-wrapper :deep(.se-wrapper) {
+  background: var(--admin-bg-tertiary);
+  color: var(--admin-text-primary);
+}
+
+.admin--suneditor-wrapper :deep(.se-wrapper-inner) {
+  background: #ffffff;
+  color: #000000;
+}
+
+.admin--suneditor-wrapper :deep(.se-wrapper-inner.se-wrapper-wysiwyg) {
+  background: #ffffff;
+  color: #000000 !important;
+}
+
+/* 에디터 내부 모든 텍스트 요소 - 기본 검정색 */
+.admin--suneditor-wrapper :deep(.se-wrapper-inner p),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner div),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner span),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner h1),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner h2),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner h3),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner h4),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner h5),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner h6),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner li),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner ul),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner ol),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner blockquote),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner pre),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner strong),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner em),
+.admin--suneditor-wrapper :deep(.se-wrapper-inner code) {
+  color: #000000;
+}
+
+/* 링크는 파란색으로 */
+.admin--suneditor-wrapper :deep(.se-wrapper-inner a) {
+  color: #0066cc;
+}
+
+.admin--suneditor-wrapper :deep(.se-placeholder) {
+  color: var(--admin-text-muted) !important;
+}
+</style>

+ 179 - 0
app/components/block/mainSwiper.vue

@@ -0,0 +1,179 @@
+<template>
+  <div class="main--swiper--wrap">
+    <swiper
+      :modules="[EffectFade]"
+      :slides-per-view="1"
+      :space-between="0"
+      :speed="300"
+      effect="fade"
+      :fadeEffect="{ crossFade: true }"
+      @swiper="onSwiper"
+      @slideChange="onSlideChange"
+      class="main--swiper"
+    >
+      <swiper-slide v-for="(slide, index) in slides" :key="index">
+        <div class="slide--content">
+          <div class="img--wrap">
+            <img v-if="slide.image" :src="slide.image" :alt="slide.title" />
+            <video
+                v-if="slide.video"
+                autoplay
+                muted
+                loop
+                playsinline
+                :poster="slide.video"
+                controlslist="nodownload noplaybackrate"
+                disablepictureinpicture
+                preload="metadata"
+              >
+                <source :src="slide.video" type="video/mp4" />
+              </video>
+          </div>
+          <div class="title--wrap">
+            <h2>{{ slide.title }}</h2>
+            <p v-html="slide.subTitle"></p>
+            <div class="btn--wrap" v-if="slide.btnHidden != 'hidden'">
+              <NuxtLink class="btn--white" to="/ford/network">시승 신청</NuxtLink>
+              <NuxtLink class="btn--blue" :to="slide.href">제품 정보 보기</NuxtLink>
+            </div>
+          </div>
+        </div>
+      </swiper-slide>
+    </swiper>
+
+    <div class="swiper--pagination--custom">
+      <div
+        v-for="(slide, index) in slides"
+        :key="index"
+        class="pagination--item"
+        :class="{ active: currentIndex === index }"
+        @click="goToSlide(index)"
+      >
+        <h2 v-html="slide.pagingTitle"></h2>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onUnmounted } from 'vue';
+import { Swiper, SwiperSlide } from 'swiper/vue';
+import { EffectFade } from 'swiper/modules';
+import 'swiper/css';
+import 'swiper/css/effect-fade';
+
+const isMobile = ref(false);
+
+const checkMobile = () => {
+  isMobile.value = window.innerWidth <= 768;
+};
+
+const isAfterLaunch = useTimeSwitch('2026-04-15T08:00:00+09:00');
+
+
+onMounted(() => {
+  checkMobile();
+  window.addEventListener('resize', checkMobile);
+});
+
+onUnmounted(() => {
+  window.removeEventListener('resize', checkMobile);
+});
+
+const slides = 
+isAfterLaunch.value == false ?
+computed(() => [  
+  {
+    title: "",
+    subTitle: "",
+    pagingTitle: 'EXPLORER',
+    image: isMobile.value ? '/img/main--visual11--mo.jpg' : '/img/main--visual11.jpg',
+    href: "/ford/vehicle/explorer",
+    btnHidden : "hidden"
+  },
+  {
+    title: "FORD EXPEDITION",
+    subTitle: "최대 8명이 탑승할 수 있는 편안한 좌석은 물론, <br>FORD CO-PILOT360TM 시스템이 안전하고 편안한 주행을 지원합니다.",
+    pagingTitle: 'EXPEDITION',
+    image: isMobile.value ? '/img/main--visual2--mo.png' : '/img/main--visual2.png',
+    href: "/ford/vehicle/expedition",
+  },
+  {
+    title: "FORD BRONCO",
+    subTitle: "광활한 대자연을 열망하고 모험을 추구하는 내면의 열정을 실현시켜줄 진정한 버디입니다.",
+    pagingTitle: 'BRONCO',
+    image: isMobile.value ? '/img/main--visual3--mo.png' : '/img/main--visual3.png',
+    href: "/ford/vehicle/bronco",
+  },
+  {
+    title: "FORD RANGER",
+    subTitle: "지금까지 출시된 포드 레인저 중 가장 스마트하고 다재다능한 레인저를 만나보십시오.",
+    pagingTitle: 'RANGER',
+    image: isMobile.value ? '/img/main--visual4--mo.png' : '/img/main--visual4.png',
+    href: "/ford/vehicle/ranger",
+  },
+  {
+    title: "FORD MUSTANG",
+    subTitle: "머스탱을 소유할 최고의 타이밍",
+    pagingTitle: 'MUSTANG',
+    image: isMobile.value ? '/img/main--visual5--mo.png' : '/img/main--visual5.png',
+    href: "/ford/vehicle/mustang",
+  }
+]) : 
+computed(() => [ 
+  { 
+    title: "",
+    subTitle: "",
+    href: "",
+    pagingTitle: "EXPLORER",
+    video: "https://audi.interscope.co.kr/video/flak/flak_26my_web.mp4",
+    btnHidden : "hidden"
+  },  
+  {
+    title: "FORD EXPEDITION",
+    subTitle: "최대 8명이 탑승할 수 있는 편안한 좌석은 물론, <br>FORD CO-PILOT360TM 시스템이 안전하고 편안한 주행을 지원합니다.",
+    pagingTitle: 'EXPEDITION',
+    image: isMobile.value ? '/img/main--visual2--mo.png' : '/img/main--visual2.png',
+    href: "/ford/vehicle/expedition",
+  },
+  {
+    title: "FORD BRONCO",
+    subTitle: "광활한 대자연을 열망하고 모험을 추구하는 내면의 열정을 실현시켜줄 진정한 버디입니다.",
+    pagingTitle: 'BRONCO',
+    image: isMobile.value ? '/img/main--visual3--mo.png' : '/img/main--visual3.png',
+    href: "/ford/vehicle/bronco",
+  },
+  {
+    title: "FORD RANGER",
+    subTitle: "지금까지 출시된 포드 레인저 중 가장 스마트하고 다재다능한 레인저를 만나보십시오.",
+    pagingTitle: 'RANGER',
+    image: isMobile.value ? '/img/main--visual4--mo.png' : '/img/main--visual4.png',
+    href: "/ford/vehicle/ranger",
+  },
+  {
+    title: "FORD MUSTANG",
+    subTitle: "머스탱을 소유할 최고의 타이밍",
+    pagingTitle: 'MUSTANG',
+    image: isMobile.value ? '/img/main--visual5--mo.png' : '/img/main--visual5.png',
+    href: "/ford/vehicle/mustang",
+  }
+]);
+
+const swiperInstance = ref(null);
+const currentIndex = ref(0);
+
+const onSwiper = (swiper) => {
+  swiperInstance.value = swiper;
+};
+
+const onSlideChange = (swiper) => {
+  currentIndex.value = swiper.activeIndex;
+};
+
+const goToSlide = (index) => {
+  if (swiperInstance.value) {
+    swiperInstance.value.slideTo(index);
+  }
+};
+</script>
+

+ 127 - 0
app/components/block/mainSwiperLincoln.vue

@@ -0,0 +1,127 @@
+<template>
+  <div class="main--swiper--wrap">
+    <swiper
+      :modules="[EffectFade]"
+      :slides-per-view="1"
+      :space-between="0"
+      :speed="300"
+      effect="fade"
+      :fadeEffect="{ crossFade: true }"
+      @swiper="onSwiper"
+      @slideChange="onSlideChange"
+      class="main--swiper"
+    >
+      <swiper-slide v-for="(slide, index) in slides" :key="index">
+        <div class="slide--content">
+          <div class="img--wrap">
+            <video v-if="slide.type === 'video'" muted loop autoplay playsinline>
+              <source :src="slide.video" type="video/mp4">
+            </video>
+            <img v-else :src="slide.image" :alt="slide.title" />
+          </div>
+          <div class="title--wrap" v-if="slide.title">
+            <h2>{{ slide.title }}</h2>
+            <p v-html="slide.subTitle"></p>
+            <div class="btn--wrap">
+              <NuxtLink class="btn--white" to="/lincoln/network">시승 신청</NuxtLink>
+              <NuxtLink class="btn--orange" :to="slide.href">제품 정보 보기</NuxtLink>
+            </div>
+          </div>
+        </div>
+      </swiper-slide>
+    </swiper>
+
+    <div class="swiper--pagination--custom">
+      <div
+        v-for="(slide, index) in slides"
+        :key="index"
+        class="pagination--item"
+        :class="{ active: currentIndex === index }"
+        @click="goToSlide(index)"
+      >
+        <h2 v-html="slide.pagingTitle"></h2>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref, computed, onMounted, onUnmounted } from "vue";
+  import { Swiper, SwiperSlide } from "swiper/vue";
+  import { EffectFade } from "swiper/modules";
+  import "swiper/css";
+  import "swiper/css/effect-fade";
+
+  const isMobile = ref(false);
+
+  const checkMobile = () => {
+    isMobile.value = window.innerWidth <= 768;
+  };
+
+  onMounted(() => {
+    checkMobile();
+    window.addEventListener("resize", checkMobile);
+  });
+
+  onUnmounted(() => {
+    window.removeEventListener("resize", checkMobile);
+  });
+
+  const slides = computed(() => [
+    // {
+    //   type: "video",
+    //   title: "",
+    //   subTitle: "",
+    //   href: "",
+    //   pagingTitle: "NAUTILUS",
+    //   video: "/img/main--lincoln--visual.mp4",
+    // },
+    {
+      title: "",
+      subTitle: "",
+      href: "",
+      pagingTitle: "NAUTILUS",
+      image: isMobile.value
+        ? "/img/main--nautilus--visual3--mo.jpg"
+        : "/img/main--nautilus--visual3.jpg",
+    },
+    {
+      title: "THE NEW LINCOLN NAVIGATOR",
+      subTitle: "EXPERIENCE OF A PACKAGE ADVANCED TECHNOLOGIES",
+      href: "/lincoln/vehicle/navigator",
+      pagingTitle: "NAVIGATOR",
+      image: isMobile.value
+        ? "/img/main--lincoln--visual1--mo.png"
+        : "/img/main--lincoln--visual1.png",
+    },
+    {
+      title: "THE NEW LINCOLN AVIATOR",
+      subTitle: "Way To The Shipped Exit",
+      href: "/lincoln/vehicle/aviator",
+      pagingTitle: "AVIATOR",
+      image: isMobile.value
+        ? "/img/main--lincoln--visual2--mo.png"
+        : "/img/main--lincoln--visual2.png",
+    },
+    
+  ]);
+
+  const swiperInstance = ref(null);
+  const currentIndex = ref(0);
+
+  const onSwiper = (swiper) => {
+    swiperInstance.value = swiper;
+  };
+
+  const onSlideChange = (swiper) => {
+    currentIndex.value = swiper.activeIndex;
+  };
+
+  const goToSlide = (index) => {
+    if (swiperInstance.value) {
+      swiperInstance.value.slideTo(index);
+    }
+  };
+
+ 
+</script>

+ 96 - 0
app/components/breadCrumbs.vue

@@ -0,0 +1,96 @@
+<template>
+  <div class="breadcrumbs--wrap">
+    <div class="container">
+      <div class="menu--wrap">
+        <p class="main--menu">{{ mainMenu }}</p>
+        <span class="bar"></span>
+        <div class="sub--menu--wrap" ref="menuWrap">
+          <p class="sub--menu" @click="toggleNav" :style="{ width: width + 'px' }">
+            <span class="ellipsis1">
+              {{ currentSubMenu }}
+            </span>
+            <i class="ico"></i>
+          </p>
+
+          <div class="nav--wrap" :class="{ active: isNavOpen }">
+            <NuxtLink
+              v-for="(item, index) in subMenuItems"
+              :key="index"
+              :to="item.to"
+              class="sub--menu2"
+              :class="{ active: isActiveRoute(item.to) }"
+              @click="isNavOpen = false"
+            >
+              {{ item.label }}
+            </NuxtLink>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref, onMounted, onUnmounted } from "vue";
+
+  const route = useRoute();
+
+  // Props 정의
+  const props = defineProps({
+    // 가로 넓이
+    width: {
+      type: String,
+      default: "180",
+    },
+    // 메인 메뉴 타이틀 (예: NETWORK, VEHICLES, OWNER 등)
+    mainMenu: {
+      type: String,
+      default: "NETWORK",
+    },
+    // 현재 선택된 서브메뉴 타이틀 (예: 전시장 및 서비스센터)
+    currentSubMenu: {
+      type: String,
+      default: "메뉴를 선택하세요",
+    },
+    // 서브메뉴 아이템 리스트
+    subMenuItems: {
+      type: Array,
+      default: () => [],
+      // 예시 구조: [{ label: '전시장 찾기', to: '/ford/network/showroom', active: true }, ...]
+    },
+  });
+
+  const isNavOpen = ref(false);
+  const menuWrap = ref(null);
+
+  const toggleNav = () => {
+    isNavOpen.value = !isNavOpen.value;
+  };
+
+  // 현재 route와 비교하여 active 여부 판단
+  const isActiveRoute = (to) => {
+    if (!to) return false;
+    // 현재 전체 경로 (쿼리 포함)
+    const currentFullPath = route.fullPath;
+    // to가 쿼리를 포함하는 경우 전체 비교
+    if (to.includes("?")) {
+      return currentFullPath === to || currentFullPath.startsWith(to + "&");
+    }
+    // 쿼리 없는 경우 path만 비교하고 쿼리가 없어야 함
+    return route.path === to && Object.keys(route.query).length === 0;
+  };
+
+  const handleClickOutside = (event) => {
+    if (menuWrap.value && !menuWrap.value.contains(event.target)) {
+      isNavOpen.value = false;
+    }
+  };
+
+  onMounted(() => {
+    document.addEventListener("click", handleClickOutside);
+  });
+
+  onUnmounted(() => {
+    document.removeEventListener("click", handleClickOutside);
+  });
+</script>

Fichier diff supprimé car celui-ci est trop grand
+ 185 - 0
app/components/event.vue


+ 420 - 0
app/components/footer.vue

@@ -0,0 +1,420 @@
+<template>
+  <footer>
+    <div class="footer--wrap" v-if="!isIndexPage">
+      <div class="footer--btn--wrap" :class="{ active: isScrolled80 }">
+        <div
+          class="quick--wrap"
+          :class="{ active: isQuickMenuActive, lincoln: isLincolnPage }"
+        >
+          <ul class="inner--wrap">
+            <li v-if="!isLincolnPage">
+              <NuxtLink :to="linkName + '/vehicle/explorer'">explorer</NuxtLink>
+            </li>
+            <li v-if="!isLincolnPage">
+              <NuxtLink :to="linkName + '/vehicle/expedition'">expedition</NuxtLink>
+            </li>
+            <li v-if="!isLincolnPage">
+              <NuxtLink :to="linkName + '/vehicle/bronco'">bronco</NuxtLink>
+            </li>
+            <li v-if="!isLincolnPage">
+              <NuxtLink :to="linkName + '/vehicle/mustang'">mustang</NuxtLink>
+            </li>
+            <li v-if="!isLincolnPage">
+              <NuxtLink :to="linkName + '/vehicle/ranger'">RANGER</NuxtLink>
+            </li>
+             <li v-if="isLincolnPage">
+              <NuxtLink to="/lincoln/vehicle/nautilus">NAUTILUS</NuxtLink>
+            </li>
+            <li v-if="isLincolnPage">
+              <NuxtLink to="/lincoln/vehicle/navigator">NAVIGATOR</NuxtLink>
+            </li>
+            <li v-if="isLincolnPage">
+              <NuxtLink to="/lincoln/vehicle/aviator">AVIATOR</NuxtLink>
+            </li>
+           
+            <li><NuxtLink :to="linkName + '/network'">전시장</NuxtLink></li>
+            <li><NuxtLink :to="linkName + '/network'">서비스 센터</NuxtLink></li>
+          </ul>
+        </div>
+        <button
+          class="quick--menu"
+          :class="{ lincoln: isLincolnPage, active: isQuickMenuActive }"
+          @click.stop="toggleQuickMenu"
+        >
+          <i class="ico"></i>
+          QUICK
+        </button>
+        <button class="scroll--to--top" @click="scrollToTop" aria-label="맨 위로">
+          <i class="ico"></i>
+          TOP
+        </button>
+      </div>
+      <div class="footer--site--map">
+        <ul :class="{ lincoln: isLincolnPage }">
+          <li v-if="isLincolnPage == false">
+            <h2 @click="toggleMenu(0)">VEHICLE</h2>
+            <ul :class="{ active: activeMenu === 0 }">
+              <li><NuxtLink to="/ford/vehicle/explorer">explorer</NuxtLink></li>
+              <li><NuxtLink to="/ford/vehicle/expedition">expedition</NuxtLink></li>
+              <li><NuxtLink to="/ford/vehicle/bronco">bronco</NuxtLink></li>
+              <li><NuxtLink to="/ford/vehicle/mustang">mustang</NuxtLink></li>
+              <li><NuxtLink to="/ford/vehicle/ranger">RANGER</NuxtLink></li>
+            </ul>
+          </li>
+          <li v-else>
+            <h2 @click="toggleMenu(0)">VEHICLE</h2>
+            <ul :class="{ active: activeMenu === 0 }">
+              <li><NuxtLink to='/lincoln/vehicle/nautilus'>NAUTILUS</NuxtLink></li>
+              <li><NuxtLink to="/lincoln/vehicle/navigator">NAVIGATOR</NuxtLink></li>
+              <li><NuxtLink to="/lincoln/vehicle/aviator">AVIATOR</NuxtLink></li>
+            </ul>
+          </li>
+
+          <li>
+            <h2 @click="toggleMenu(1)">NETWORK</h2>
+            <ul :class="{ active: activeMenu === 1 }">
+              <li>
+                <NuxtLink :to="linkName + '/network'">전시장 및 서비스센터</NuxtLink>
+              </li>
+              <!-- <li>
+                <NuxtLink :to="linkName + '/network'"
+                  >서비스 센터 찾기</NuxtLink
+                >
+              </li> -->
+              <li>
+                <NuxtLink :to="linkName + '/owner/contact'"
+                  >{{ isLincolnPage == true ? "링컨" : "포드" }} 고객 센터</NuxtLink
+                >
+              </li>
+            </ul>
+          </li>
+
+          <li>
+            <h2 @click="toggleMenu(2)">SHOPPING</h2>
+            <ul :class="{ active: activeMenu === 2 }">
+              <li>
+                <NuxtLink :to="linkName + '/shopping/brochure'"
+                  >브로슈어 다운로드</NuxtLink
+                >
+              </li>
+              <li>
+                <NuxtLink :to="linkName + '/shopping/tire'"
+                  >타이어 에너지 소비 효율</NuxtLink
+                >
+              </li>
+              <!-- <li><NuxtLink :to="linkName + '/shopping/apply'">신청하기</NuxtLink></li> -->
+            </ul>
+          </li>
+          <li>
+            <h2 @click="toggleMenu(3)">OWNER</h2>
+            <ul :class="{ active: activeMenu === 3 }">
+              <h3>서비스</h3>
+              <li class="inner--style">
+                <NuxtLink :to="linkName + '/owner'">공인 서비스</NuxtLink>
+              </li>
+              <li class="inner--style" v-if="isLincolnPage == true">
+                <NuxtLink :to="linkName + '/owner/pickup'"
+                  >픽업 & 딜리버리 서비스</NuxtLink
+                >
+              </li>
+              <li class="inner--style">
+                <NuxtLink :to="linkName + '/owner/consumableParts'"
+                  >소모성 부품 무상 교환 서비스</NuxtLink
+                >
+              </li>
+              <li class="inner--style">
+                <NuxtLink :to="linkName + '/owner/tfService'"
+                  >24시간 긴급 출동 서비스</NuxtLink
+                >
+              </li>
+              <li class="inner--style">
+                <NuxtLink :to="linkName + '/owner/genuine'">순정 부품</NuxtLink>
+              </li>
+              <li class="inner--style">
+                <NuxtLink :to="linkName + '/owner/recall'">리콜 안내</NuxtLink>
+              </li>
+              <li class="inner--style">
+                <NuxtLink :to="linkName + '/owner/navigation'">내비게이션</NuxtLink>
+              </li>
+              <!-- <li class="inner--style" v-if="isLincolnPage == true">
+                <NuxtLink :to="linkName + '/owner/warranty'">보증 및 서비스</NuxtLink>
+              </li> -->
+              <!-- <li class="inner--style" v-if="isLincolnPage == false">
+                <NuxtLink :to="linkName + '/owner/accident'"
+                  >사고 수리를 위한 포드 서비스 부품</NuxtLink
+                >
+              </li> -->
+            </ul>
+
+            <ul :class="{ active: activeMenu === 3 }">              
+                <h3 class="mb--40">
+                  <NuxtLink :to="linkName + '/owner/event'"
+                    >프로모션</NuxtLink>
+                </h3>
+              
+            </ul>
+
+            <!-- <ul :class="{ active: activeMenu === 3 }" v-if="isLincolnPage == false">
+              <h3>보증 및 서비스</h3>
+              <li class="inner--style"><NuxtLink to="/">온라인 서비스 예약</NuxtLink></li>
+              <li class="inner--style">
+                <NuxtLink :to="linkName + '/owner/warranty'">보증 및 서비스</NuxtLink>
+              </li>
+            </ul> -->
+            <!-- <ul :class="{ active: activeMenu === 3 }" v-if="isLincolnPage == true">
+              <h3>부품 및 차량 관리</h3>
+              <li class="inner--style">
+                <NuxtLink :to="linkName + '/owner/genuine'">순정 부품</NuxtLink>
+              </li>
+              <li class="inner--style">
+                <NuxtLink :to="linkName + '/owner/navigation'">내비게이션</NuxtLink>
+              </li>
+            </ul> -->
+
+            <!-- <ul :class="{ active: activeMenu === 3 }">
+              <h3>Contact Us</h3>
+              <li class="inner--style">
+                <NuxtLink :to="linkName + '/owner/contact'">포드 고객 센터</NuxtLink>
+              </li>
+            </ul> -->
+          </li>
+
+          <li v-if="isLincolnPage == false">
+            <h2 @click="toggleMenu(4)">ABOUT FORD</h2>
+            <!-- <ul :class="{ active: activeMenu === 4 }">
+              <h3>About Ford</h3>
+            </ul> -->
+            <ul :class="{ active: activeMenu === 4 }">
+              <h3>Global Sites</h3>
+              <li class="inner--style">
+                <NuxtLink :to="linkName + '/about/company'">Company</NuxtLink>
+              </li>
+              <li class="inner--style">
+                <NuxtLink :to="linkName + '/about/ourPurpose'">Our Purpose</NuxtLink>
+              </li>
+              <li class="inner--style">
+                <NuxtLink
+                  to="https://corporate.ford.com/careers/inclusive-hiring/diversity/?intcmp=corporate-seconNav-diversity"
+                  target="_blank"
+                  >Corporate</NuxtLink
+                >
+              </li>
+              <li class="inner--style">
+                <NuxtLink to="https://corporate.ford.com/about/history/" target="_blank"
+                  >Heritage</NuxtLink
+                >
+              </li>
+              <li class="inner--style">
+                <NuxtLink
+                  to="https://corporate.ford.com//social-impact/sustainability/"
+                  target="_blank"
+                  >Sustanability</NuxtLink
+                >
+              </li>
+            </ul>
+          </li>
+          <li v-if="isLincolnPage">
+            <h2 @click="toggleMenu(4)">ABOUT LINCOLN</h2>
+            <!-- <ul :class="{ active: activeMenu === 4 }">
+              <h3>About Ford</h3>
+            </ul> -->
+            <ul :class="{ active: activeMenu === 4 }">
+              <NuxtLink to="https://www.lincoln.com" target="_blank"
+                ><h3 class="">Global Sites</h3></NuxtLink
+              >
+            </ul>
+          </li>
+          <li>
+            <h2
+              v-if="linkName == '/ford'"
+              @click="navigateTo('/lincoln')"
+              style="cursor: pointer"
+            >
+              LINCOLN
+            </h2>
+            <h2 v-else @click="navigateTo('/ford')" style="cursor: pointer">FORD</h2>
+          </li>
+        </ul>
+      </div>
+    </div>
+    <div class="footer--info--wrapper">
+      <div class="footer--info--wrap">
+        <div class="copy--wrap">
+          <!-- <ul class="link--list">
+            <li><NuxtLink to="/">사이트 맵</NuxtLink></li>
+            <li><NuxtLink to="/">개인정보처리방침</NuxtLink></li>
+            <li><NuxtLink to="/" target="_blank">글로벌 사이트</NuxtLink></li>
+          </ul> -->
+          <div>
+            회사명 : 에프엘오토코리아 유한회사&nbsp;&nbsp;&nbsp;사업소재지 : 서울특별시 성동구 자동차시장1길 104-13&nbsp;&nbsp;&nbsp;대표이사 : 이윤동<br/>
+            이메일 : customercenter@flak.co.kr&nbsp;&nbsp;&nbsp;대표전화번호 : 1600-6003<br/>
+            통신판매업신고번호 : 2026-서울성동-0319<br />
+            등록번호 : 211-86-57145
+          </div>
+          <br/>
+          <p>Copyright © 2025 FL Auto Korea Company. All Rights Reserved.</p>
+
+          <div class="sns--wrap">
+            <NuxtLink
+              v-if="isLincolnPage == false"
+              to="https://www.facebook.com/fordkorea"
+              target="_blank"
+            >
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                width="8"
+                height="15"
+                viewBox="0 0 8 15"
+                fill="none"
+              >
+                <path
+                  d="M5.33333 5V3.51172C5.33333 2.83984 5.49167 2.5 6.60417 2.5H8V0H5.67083C2.81667 0 1.875 1.22656 1.875 3.33203V5H0V7.5H1.875V15H5.33333V7.5H7.68333L8 5H5.33333Z"
+                  fill="black"
+                />
+              </svg>
+            </NuxtLink>
+            <NuxtLink v-else to="https://www.facebook.com/lincolnkorea" target="_blank">
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                width="8"
+                height="15"
+                viewBox="0 0 8 15"
+                fill="none"
+              >
+                <path
+                  d="M5.33333 5V3.51172C5.33333 2.83984 5.49167 2.5 6.60417 2.5H8V0H5.67083C2.81667 0 1.875 1.22656 1.875 3.33203V5H0V7.5H1.875V15H5.33333V7.5H7.68333L8 5H5.33333Z"
+                  fill="black"
+                />
+              </svg>
+            </NuxtLink>
+            <NuxtLink
+              v-if="isLincolnPage == false"
+              to="https://www.instagram.com/fordkorea"
+              target="_blank"
+            >
+              <img src="/img/ico--insta.png" />
+            </NuxtLink>
+            <NuxtLink
+              v-else
+              to="https://www.instagram.com/lincolnmotorcompanykr"
+              target="_blank"
+            >
+              <img src="/img/ico--insta.png" />
+            </NuxtLink>
+
+            <NuxtLink
+              v-if="isLincolnPage == false"
+              to="https://www.youtube.com/@fordkorea1/featured"
+              target="_blank"
+            >
+              <!-- <img src="/img/ico--youtube.png" /> -->
+            </NuxtLink>
+            <NuxtLink v-else to="https://www.youtube.com/@LincolnKorea" target="_blank">
+              <!-- <img src="/img/ico--youtube.png" /> -->
+            </NuxtLink>
+            <NuxtLink
+              v-if="isLincolnPage == false"
+              to="https://pf.kakao.com/_PGPWz"
+              target="_blank"
+            >
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                width="24"
+                height="24"
+                viewBox="0 0 24 24"
+                fill="none"
+              >
+                <path
+                  d="M12.0043 2C6.48085 2 2 5.91412 2 10.7454C2 15.5767 3.58298 16.0152 5.74468 17.568H5.75319V21.7597C5.75319 21.9568 5.99149 22.0694 6.15745 21.9528L9.94894 19.2897L10.0298 19.3219C10.6638 19.4345 11.3234 19.4908 11.9957 19.4908C17.5191 19.4908 22 15.5727 22 10.7414C22 5.9101 17.5277 2 12.0043 2ZM8.48511 13.0102C9.51489 13.0102 10.3957 12.4269 10.6596 11.5299H12.183C11.8128 13.2637 10.383 14.3981 8.48511 14.3981C6.58723 14.3981 4.52766 12.8413 4.52766 10.677C4.52766 8.51281 6.2766 6.95601 8.48511 6.95601C10.6936 6.95601 11.8383 8.11455 12.1957 9.87651H10.6766C10.4468 8.9191 9.53617 8.31971 8.48511 8.31971C7 8.31971 6.01277 9.32137 6.01277 10.677C6.01277 12.0327 7.13617 13.0142 8.48511 13.0142V13.0102ZM18.5191 14.2532H17.1234V11.2764C17.1234 10.5926 16.7021 10.2225 16.0511 10.2225C15.4 10.2225 14.8596 10.6408 14.8596 11.4896V14.2492H13.4638V6.9721H14.8596V9.71158C15.1957 9.23287 15.7362 8.99553 16.4723 8.99553C17.2085 8.99553 17.5532 9.18058 17.9319 9.55872C18.3234 9.93685 18.5149 10.4477 18.5149 11.1115V14.2532H18.5191Z"
+                  fill="#161616"
+                />
+              </svg>
+            </NuxtLink>
+            <NuxtLink v-else to="https://pf.kakao.com/_xoybzG" target="_blank">
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                width="24"
+                height="24"
+                viewBox="0 0 24 24"
+                fill="none"
+              >
+                <path
+                  d="M12.0043 2C6.48085 2 2 5.91412 2 10.7454C2 15.5767 3.58298 16.0152 5.74468 17.568H5.75319V21.7597C5.75319 21.9568 5.99149 22.0694 6.15745 21.9528L9.94894 19.2897L10.0298 19.3219C10.6638 19.4345 11.3234 19.4908 11.9957 19.4908C17.5191 19.4908 22 15.5727 22 10.7414C22 5.9101 17.5277 2 12.0043 2ZM8.48511 13.0102C9.51489 13.0102 10.3957 12.4269 10.6596 11.5299H12.183C11.8128 13.2637 10.383 14.3981 8.48511 14.3981C6.58723 14.3981 4.52766 12.8413 4.52766 10.677C4.52766 8.51281 6.2766 6.95601 8.48511 6.95601C10.6936 6.95601 11.8383 8.11455 12.1957 9.87651H10.6766C10.4468 8.9191 9.53617 8.31971 8.48511 8.31971C7 8.31971 6.01277 9.32137 6.01277 10.677C6.01277 12.0327 7.13617 13.0142 8.48511 13.0142V13.0102ZM18.5191 14.2532H17.1234V11.2764C17.1234 10.5926 16.7021 10.2225 16.0511 10.2225C15.4 10.2225 14.8596 10.6408 14.8596 11.4896V14.2492H13.4638V6.9721H14.8596V9.71158C15.1957 9.23287 15.7362 8.99553 16.4723 8.99553C17.2085 8.99553 17.5532 9.18058 17.9319 9.55872C18.3234 9.93685 18.5149 10.4477 18.5149 11.1115V14.2532H18.5191Z"
+                  fill="#161616"
+                />
+              </svg>
+            </NuxtLink>
+          </div>
+        </div>
+      </div>
+    </div>
+  </footer>
+</template>
+
+<script setup>
+  import { ref, onMounted, onUnmounted, computed } from "vue";
+  import { useRoute } from "vue-router";
+
+  const config = useRuntimeConfig();
+  const route = useRoute();
+  const activeMenu = ref(null);
+  const isScrolled80 = ref(false);
+  const isQuickMenuActive = ref(false);
+
+  // URL에 'lincoln'이 포함되어 있는지 확인
+  const isLincolnPage = computed(() => {
+    return route.path.includes("lincoln");
+  });
+
+  // 인덱스 페이지인지 확인
+  const isIndexPage = computed(() => {
+    return route.path === "/";
+  });
+
+  // 브랜드명을 동적으로 반환
+  const linkName = computed(() => {
+    return route.path.includes("lincoln") ? "/lincoln" : "/ford";
+  });
+
+  const toggleMenu = (index) => {
+    if (activeMenu.value === index) {
+      activeMenu.value = null;
+    } else {
+      activeMenu.value = index;
+    }
+  };
+
+  const toggleQuickMenu = () => {
+    isQuickMenuActive.value = !isQuickMenuActive.value;
+  };
+
+  const closeQuickMenu = () => {
+    isQuickMenuActive.value = false;
+  };
+
+  const scrollToTop = () => {
+    window.scrollTo({
+      top: 0,
+      behavior: "smooth",
+    });
+  };
+
+  const handleScroll = () => {
+    const scrollTop = window.scrollY || document.documentElement.scrollTop;
+    const scrollHeight =
+      document.documentElement.scrollHeight - document.documentElement.clientHeight;
+    const scrollPercent = (scrollTop / scrollHeight) * 100;
+
+    isScrolled80.value = scrollPercent !== 0;
+  };
+
+  onMounted(() => {
+    window.addEventListener("scroll", handleScroll);
+    document.addEventListener("click", closeQuickMenu);
+  });
+
+  onUnmounted(() => {
+    window.removeEventListener("scroll", handleScroll);
+    document.removeEventListener("click", closeQuickMenu);
+  });
+</script>

Fichier diff supprimé car celui-ci est trop grand
+ 23 - 0
app/components/header.vue


+ 3 - 0
app/components/sitemap.vue

@@ -0,0 +1,3 @@
+<template>
+  <div>사이투맵</div>
+</template>

+ 173 - 0
app/composables/useApi.js

@@ -0,0 +1,173 @@
+import axios from 'axios'
+import { useLoading } from './useLoading'
+
+export const useApi = () => {
+  const config = useRuntimeConfig()
+  const API_BASE_URL = config.public.apiBase + '/api'  
+
+  const { showLoading, hideLoading } = useLoading()
+  let pendingRequests = 0
+
+  // Axios 인스턴스 생성
+  const apiClient = axios.create({
+    baseURL: API_BASE_URL,
+    timeout: 30000,
+    headers: {
+      'Content-Type': 'application/json'
+    }
+  })
+
+  // Request Interceptor (토큰 자동 추가 + 로딩 시작)
+  apiClient.interceptors.request.use(
+    (config) => {
+      // 로딩 카운터 증가
+      pendingRequests++
+      if (pendingRequests === 1) {
+        showLoading()
+      }
+
+      if (typeof window !== 'undefined' && typeof localStorage !== 'undefined') {
+        const token = localStorage.getItem('admin_token')
+        if (token) {
+          config.headers.Authorization = `Bearer ${token}`
+          console.log('[useApi] Request with token:', config.url, token.substring(0, 20) + '...')
+        } else {
+          console.log('[useApi] Request without token:', config.url)
+        }
+      }
+      return config
+    },
+    (error) => {
+      // 에러 발생시에도 카운터 감소
+      pendingRequests--
+      if (pendingRequests === 0) {
+        hideLoading()
+      }
+      return Promise.reject(error)
+    }
+  )
+
+  // Response Interceptor (에러 처리 + 로딩 종료)
+  apiClient.interceptors.response.use(
+    (response) => {
+      // 로딩 카운터 감소
+      pendingRequests--
+      if (pendingRequests === 0) {
+        hideLoading()
+      }
+
+      console.log('[useApi] Response:', response.config.url, response.status)
+      return response
+    },
+    (error) => {
+      // 에러 시에도 카운터 감소
+      pendingRequests--
+      if (pendingRequests === 0) {
+        hideLoading()
+      }
+
+      const status = error.response?.status
+      const url = error.config?.url
+
+      console.log('[useApi] Error:', {
+        url,
+        status,
+        message: error.message,
+        hasToken: typeof window !== 'undefined' && typeof localStorage !== 'undefined' ? !!localStorage.getItem('admin_token') : false
+      })
+
+      // 401 에러만 토큰 삭제 및 로그아웃 처리
+      if (status === 401) {
+        console.log('[useApi] 401 Unauthorized - 토큰 삭제 및 로그아웃')
+        if (typeof window !== 'undefined') {
+          localStorage.removeItem('admin_token')
+          localStorage.removeItem('admin_user')
+          window.location.href = '/site-manager'
+        }
+      } else {
+        console.log('[useApi] 401이 아닌 에러 - 토큰 유지')
+      }
+
+      return Promise.reject(error)
+    }
+  )
+
+  // GET 요청
+  const get = async (url, config = {}) => {
+    try {
+      const response = await apiClient.get(url, config)
+      // 전체 응답 반환: { success, data, message }
+      return { data: response.data, error: null }
+    } catch (error) {
+      return { data: null, error: error.response?.data || error.message }
+    }
+  }
+
+  // POST 요청
+  const post = async (url, data = {}) => {
+    try {
+      const response = await apiClient.post(url, data)
+      // 전체 응답 반환: { success, data, message }
+      return { data: response.data, error: null }
+    } catch (error) {
+      return { data: null, error: error.response?.data || error.message }
+    }
+  }
+
+  // PUT 요청
+  const put = async (url, data = {}) => {
+    try {
+      const response = await apiClient.put(url, data)
+      // 전체 응답 반환: { success, data, message }
+      return { data: response.data, error: null }
+    } catch (error) {
+      return { data: null, error: error.response?.data || error.message }
+    }
+  }
+
+  // PATCH 요청
+  const patch = async (url, data = {}) => {
+    try {
+      const response = await apiClient.patch(url, data)
+      // 전체 응답 반환: { success, data, message }
+      return { data: response.data, error: null }
+    } catch (error) {
+      return { data: null, error: error.response?.data || error.message }
+    }
+  }
+
+  // DELETE 요청
+  const del = async (url) => {
+    try {
+      const response = await apiClient.delete(url)
+      // 전체 응답 반환: { success, data, message }
+      return { data: response.data, error: null }
+    } catch (error) {
+      return { data: null, error: error.response?.data || error.message }
+    }
+  }
+
+  // 파일 업로드
+  const upload = async (url, formData) => {
+    try {
+      const response = await apiClient.post(url, formData, {
+        headers: {
+          'Content-Type': 'multipart/form-data'
+        }
+      })
+      // 전체 응답 반환: { success, data, message }
+      return { data: response.data, error: null }
+    } catch (error) {
+      return { data: null, error: error.response?.data || error.message }
+    }
+  }
+
+  return {
+    get,
+    post,
+    put,
+    patch,
+    del,
+    upload
+  }
+}

+ 6 - 0
app/composables/useCompany.js

@@ -0,0 +1,6 @@
+export const useCompany = () => {
+  const config = useRuntimeConfig()
+  const apiBase = computed(() => config.public.apiBase)
+
+  return { apiBase }
+}

+ 47 - 0
app/composables/useImage.js

@@ -0,0 +1,47 @@
+/**
+ * 이미지 URL 헬퍼 composable
+ */
+export const useImage = () => {
+  const config = useRuntimeConfig()
+
+  /**
+   * 이미지 상대 경로를 절대 URL로 변환
+   * @param {string} path - 이미지 상대 경로 (예: "/uploads/images/abc.jpg")
+   * @returns {string} - 전체 URL (예: "http://gojinFORDKOREA.mycafe24.com/uploads/images/abc.jpg")
+   */
+  const getImageUrl = (path) => {
+    if (!path) return ''
+
+    // 이미 전체 URL인 경우 그대로 반환
+    if (path.startsWith('http://') || path.startsWith('https://')) {
+      return path
+    }
+
+    // 상대 경로인 경우 imageBase 붙이기
+    const imageBase = config.public.imageBase
+    return `${imageBase}${path}`
+  }
+
+  /**
+   * 미디어 파일(이미지, 비디오) 상대 경로를 절대 URL로 변환
+   * @param {string} path - 미디어 파일 상대 경로 (예: "/images/abc.jpg")
+   * @returns {string} - 전체 URL (예: "http://FORDKOREA.interscope.co.kr/images/abc.jpg")
+   */
+  const getMediaUrl = (path) => {
+    if (!path) return ''
+
+    // 이미 전체 URL인 경우 그대로 반환
+    if (path.startsWith('http://') || path.startsWith('https://')) {
+      return path
+    }
+
+    // 상대 경로인 경우 mediaBase 붙이기
+    const mediaBase = config.public.mediaBase
+    return `${mediaBase}${path}`
+  }
+
+  return {
+    getImageUrl,
+    getMediaUrl
+  }
+}

+ 38 - 0
app/composables/useLoading.js

@@ -0,0 +1,38 @@
+import { ref } from 'vue'
+
+const isGlobalLoading = ref(false)
+let loadingStartTime = null
+
+export const useLoading = () => {
+  const showLoading = () => {
+    isGlobalLoading.value = true
+    loadingStartTime = Date.now()
+  }
+
+  const hideLoading = () => {
+    if (!loadingStartTime) {
+      isGlobalLoading.value = false
+      return
+    }
+
+    // 최소 300ms 동안 로딩을 표시
+    const elapsedTime = Date.now() - loadingStartTime
+    const minLoadingTime = 300
+
+    if (elapsedTime < minLoadingTime) {
+      setTimeout(() => {
+        isGlobalLoading.value = false
+        loadingStartTime = null
+      }, minLoadingTime - elapsedTime)
+    } else {
+      isGlobalLoading.value = false
+      loadingStartTime = null
+    }
+  }
+
+  return {
+    isGlobalLoading,
+    showLoading,
+    hideLoading
+  }
+}

+ 120 - 0
app/composables/useSalesData.js

@@ -0,0 +1,120 @@
+/**
+ * 영업사원 관련 데이터 관리 Composable
+ * 팀 리스트, 직책 리스트 및 관련 유틸리티 함수 제공
+ */
+export const useSalesData = () => {
+  const config = useRuntimeConfig()
+
+  /**
+   * 회사별 영업팀 리스트 반환
+   */
+  const getTeamsList = () => {
+    switch (config.public.company) {
+      case "g":
+        return [
+          { id: 0, name: "마스터팀" },
+          { id: 1, name: "1팀" },
+          { id: 2, name: "2팀" },
+          { id: 3, name: "3팀" },
+          { id: 4, name: "4팀" },
+          { id: 5, name: "5팀" },
+          { id: 6, name: "6팀" },
+          { id: 7, name: "7팀" },
+          { id: 8, name: "8팀" },
+          { id: 9, name: "9팀" },
+          { id: 10, name: "10팀" },
+        ]
+      case "w":
+        return [
+          { id: 0, name: "특판팀" },
+          { id: 1, name: "1팀" },
+          { id: 2, name: "2팀" },
+          { id: 3, name: "3팀" },
+          { id: 4, name: "4팀" },
+          { id: 5, name: "5팀" },
+          { id: 6, name: "6팀" },
+          { id: 7, name: "7팀" },
+          { id: 8, name: "8팀" },
+          { id: 9, name: "9팀" },
+          { id: 10, name: "10팀" },
+        ]
+      default:
+        return [
+          { id: 0, name: "마스터팀" },
+          { id: 1, name: "1팀" },
+          { id: 2, name: "2팀" },
+          { id: 3, name: "3팀" },
+          { id: 4, name: "4팀" },
+          { id: 5, name: "5팀" },
+          { id: 6, name: "6팀" },
+          { id: 7, name: "7팀" },
+          { id: 8, name: "8팀" },
+          { id: 9, name: "9팀" },
+          { id: 10, name: "10팀" },
+        ]
+    }
+  }
+
+  /**
+   * 회사별 직책 리스트 반환
+   */
+  const getPositionsList = () => {
+    switch (config.public.company) {
+      case "g":
+        return [
+          { value: 10, label: "팀장" },
+          { value: 15, label: "마스터" },
+          { value: 20, label: "차장" },
+          { value: 30, label: "과장" },
+          { value: 40, label: "대리" },
+          { value: 60, label: "사원" },
+        ]
+      case "w":
+        return [
+          { value: 0, label: "인턴" },
+          { value: 1, label: "사원" },
+          { value: 2, label: "주임" },
+          { value: 3, label: "대리" },
+          { value: 4, label: "과장" },
+          { value: 5, label: "차장" },
+          { value: 6, label: "부장" },
+          { value: 7, label: "팀장" },
+          { value: 8, label: "지점장" },
+          { value: 9, label: "Expert" },
+          { value: 10, label: "부지점장" },
+        ]
+      default:
+        return []
+    }
+  }
+
+  const teams = ref(getTeamsList())
+  const positions = ref(getPositionsList())
+
+  /**
+   * 팀 ID로 팀 이름 조회
+   * @param {number|string} teamId - 팀 ID
+   * @returns {string} 팀 이름
+   */
+  const getTeamName = (teamId) => {
+    const team = teams.value.find(t => t.id == teamId)
+    return team ? team.name : "-"
+  }
+
+  /**
+   * 직책 코드로 직책 이름 조회
+   * @param {number|string} positionCode - 직책 코드
+   * @returns {string} 직책 이름
+   */
+  const getPositionName = (positionCode) => {
+    const position = positions.value.find(p => p.value == positionCode)
+    return position ? position.label : "-"
+  }
+
+  return {
+    teams,
+    positions,
+    getTeamName,
+    getPositionName,
+  }
+}

+ 19 - 0
app/composables/useTimeSwitch.js

@@ -0,0 +1,19 @@
+// 시간 조건
+// 각 페이지에서 const isAfterLaunch = useTimeSwitch('2026-04-15T08:00:00+09:00'); 선언 후 사용
+
+export function useTimeSwitch(targetTime) {
+  const target = new Date(targetTime);
+  const now = ref(new Date());
+
+  let timer;
+  onMounted(() => {
+    if (now.value >= target) return;
+    timer = setInterval(() => {
+      now.value = new Date();
+      if (now.value >= target) clearInterval(timer);
+    }, 1000);
+  });
+  onBeforeUnmount(() => clearInterval(timer));
+
+  return computed(() => now.value >= target);
+}

+ 422 - 0
app/layouts/admin.vue

@@ -0,0 +1,422 @@
+<script setup>
+  import { ref, computed } from "vue";
+  import { useRoute, useRouter } from "vue-router";
+  import AdminAlertModal from "~/components/admin/AdminAlertModal.vue";
+  import AdminModal from "~/components/admin/AdminModal.vue";
+
+  const route = useRoute();
+  const router = useRouter();
+
+  // 메뉴 열림 상태 관리
+  const openMenus = ref(["basic", "branch", "staff", "service", "board", "system"]);
+
+  // GNB 메뉴 구조
+  const menuItems = ref([
+    {
+      id: "basic",
+      title: "기본정보관리",
+      children: [
+        {
+          title: "사이트 정보",
+          path: "/site-manager/basic/site-info",
+          pattern: /^\/site-manager\/basic\/site-info/,
+        },
+        {
+          title: "팝업관리",
+          path: "/site-manager/basic/popup",
+          pattern: /^\/site-manager\/basic\/popup/,
+        },
+      ],
+    },
+    {
+      id: "branch",
+      title: "지점관리",
+      children: [
+        {
+          title: "딜러사 관리",
+          path: "/site-manager/branch/list",
+          pattern: /^\/site-manager\/branch\/(list|create|edit)/,
+        },
+        {
+          title: "전시장목록",
+          path: "/site-manager/showroom/list",
+          pattern: /^\/site-manager\/showroom\/(list|create|edit)/,
+        },
+        {
+          title: "서비스센터목록",
+          path: "/site-manager/service-center/list",
+          pattern: /^\/site-manager\/service-center\/(list|create|edit)/,
+        },
+      ],
+    },
+    {
+      id: "board",
+      title: "게시판관리",
+      children: [
+        {
+          title: "이벤트",
+          path: "/site-manager/board/event",
+          pattern: /^\/site-manager\/board\/event/,
+        },
+        // { title: "뉴스", path: "/site-manager/board/news", pattern: /^\/site-manager\/board\/news/ },
+        // { title: "IR", path: "/site-manager/board/ir", pattern: /^\/site-manager\/board\/ir/ },
+      ],
+    },
+    {
+      id: "system",
+      title: "시스템관리",
+      children: [
+        { title: "관리자관리", path: "/site-manager/admins", pattern: /^\/site-manager\/admins/ },
+      ],
+    },
+  ]);
+
+  // 메뉴 토글
+  const toggleMenu = (menuId) => {
+    const index = openMenus.value.indexOf(menuId);
+    if (index > -1) {
+      openMenus.value.splice(index, 1);
+    } else {
+      openMenus.value.push(menuId);
+    }
+  };
+
+  // 현재 활성 라우트 체크
+  const isActiveRoute = (path) => {
+    return route.path === path;
+  };
+
+  // 현재 경로에 맞는 메뉴 찾기
+  const findCurrentMenu = () => {
+    const currentPath = route.path;
+
+    // 대시보드인 경우
+    if (currentPath === "/site-manager/dashboard" || currentPath === "/site-manager") {
+      return { menu: null, child: null };
+    }
+
+    for (const menu of menuItems.value) {
+      for (const child of menu.children) {
+        // pattern이 있으면 정규식으로 매칭, 없으면 정확히 일치하는지 확인
+        if (child.pattern && child.pattern.test(currentPath)) {
+          return { menu, child };
+        } else if (currentPath === child.path) {
+          return { menu, child };
+        }
+      }
+    }
+
+    return { menu: null, child: null };
+  };
+
+  // 페이지 타이틀 계산
+  const pageTitle = computed(() => {
+    const currentPath = route.path;
+
+    // 대시보드
+    if (currentPath === "/site-manager/dashboard" || currentPath === "/site-manager") {
+      return "대시보드";
+    }
+
+    const { child } = findCurrentMenu();
+
+    if (child) {
+      // 상세 페이지 타이틀 처리
+      if (currentPath.includes("/create")) {
+        return `${child.title} 등록`;
+      } else if (currentPath.includes("/edit/")) {
+        return `${child.title} 수정`;
+      } else if (currentPath.includes("/print/")) {
+        return `${child.title} 인쇄`;
+      } else if (currentPath.includes("/print-a2")) {
+        return `${child.title} 인쇄 (A2)`;
+      }
+      return child.title;
+    }
+
+    return "대시보드";
+  });
+
+  // Breadcrumb 계산
+  const breadcrumbs = computed(() => {
+    const currentPath = route.path;
+    const crumbs = [{ title: "Home", path: "/site-manager/dashboard" }];
+
+    // 대시보드인 경우 Home만 표시
+    if (currentPath === "/site-manager/dashboard" || currentPath === "/site-manager") {
+      return crumbs;
+    }
+
+    const { menu, child } = findCurrentMenu();
+
+    if (menu && child) {
+      // 메뉴 그룹 추가
+      crumbs.push({ title: menu.title, path: null });
+
+      // 현재 페이지 타이틀 추가
+      if (currentPath.includes("/create")) {
+        crumbs.push({ title: child.title, path: child.path });
+        crumbs.push({ title: "등록", path: null });
+      } else if (currentPath.includes("/edit/")) {
+        crumbs.push({ title: child.title, path: child.path });
+        crumbs.push({ title: "수정", path: null });
+      } else if (currentPath.includes("/print/")) {
+        crumbs.push({ title: child.title, path: child.path });
+        crumbs.push({ title: "인쇄", path: null });
+      } else if (currentPath.includes("/print-a2")) {
+        crumbs.push({ title: child.title, path: child.path });
+        crumbs.push({ title: "인쇄 (A2)", path: null });
+      } else {
+        crumbs.push({ title: child.title, path: null });
+      }
+    }
+
+    return crumbs;
+  });
+
+  // 로그아웃 모달
+  const showLogoutModal = ref(false);
+
+  const handleLogout = () => {
+    console.log("[Logout] 로그아웃 버튼 클릭");
+    showLogoutModal.value = true;
+    console.log("[Logout] showLogoutModal:", showLogoutModal.value);
+  };
+
+  const confirmLogout = () => {
+    console.log("[Logout] 로그아웃 확인");
+    localStorage.removeItem("admin_token");
+    localStorage.removeItem("admin_user");
+    router.push("/site-manager");
+  };
+
+  const closeLogoutModal = () => {
+    console.log("[Logout] 모달 닫기");
+    showLogoutModal.value = false;
+  };
+
+  // 정보수정 모달
+  const showProfileModal = ref(false);
+  const currentAdmin = ref(null);
+
+  const { get } = useApi();
+
+  // 알림 모달
+  const alertModal = ref({
+    show: false,
+    title: "알림",
+    message: "",
+    type: "alert",
+    onConfirm: null,
+  });
+
+  // 알림 모달 표시
+  const showAlert = (message, title = "알림") => {
+    alertModal.value = {
+      show: true,
+      title,
+      message,
+      type: "alert",
+      onConfirm: null,
+    };
+  };
+
+  // 알림 모달 닫기
+  const closeAlertModal = () => {
+    alertModal.value.show = false;
+  };
+
+  // 알림 모달 확인
+  const handleAlertConfirm = () => {
+    if (alertModal.value.onConfirm) {
+      alertModal.value.onConfirm();
+    }
+    closeAlertModal();
+  };
+
+  // 알림 모달 취소
+  const handleAlertCancel = () => {
+    closeAlertModal();
+  };
+
+  // 현재 로그인한 관리자 ID 가져오기
+  const getCurrentAdminId = () => {
+    if (typeof window === "undefined") return null;
+    const user = localStorage.getItem("admin_user");
+    if (!user) return null;
+    try {
+      return JSON.parse(user).id;
+    } catch {
+      return null;
+    }
+  };
+
+  // 대시보드로 이동
+  const goToDashboard = () => {
+    router.push("/site-manager/dashboard");
+  };
+
+  // 정보수정 버튼 클릭
+  const goToProfile = async () => {
+    const adminId = getCurrentAdminId();
+    if (!adminId) {
+      showAlert("로그인 정보를 찾을 수 없습니다.", "오류");
+      return;
+    }
+
+    // 현재 관리자 정보 조회
+    const { data, error } = await get(`/admin/${adminId}`);
+
+    if (error) {
+      showAlert("관리자 정보를 불러올 수 없습니다.", "오류");
+      console.error("[Profile] 관리자 정보 조회 실패:", error);
+      return;
+    }
+
+    if (data?.success && data?.data) {
+      currentAdmin.value = data.data;
+      showProfileModal.value = true;
+    }
+  };
+
+  // 정보수정 모달 닫기
+  const closeProfileModal = () => {
+    showProfileModal.value = false;
+    currentAdmin.value = null;
+  };
+
+  // 정보수정 완료
+  const handleProfileSaved = (message) => {
+    closeProfileModal();
+    if (message) {
+      showAlert(message, "성공");
+
+      // 로컬스토리지의 사용자 정보도 업데이트
+      if (currentAdmin.value) {
+        localStorage.setItem("admin_user", JSON.stringify(currentAdmin.value));
+      }
+    }
+  };
+</script>
+
+<template>
+  <div class="admin--layout">
+    <!-- Header -->
+    <header class="admin--header">
+      <div class="admin--header-left">
+        <div class="admin--logo" @click="goToDashboard" style="cursor: pointer">
+          <h1>파이럿존</h1>
+          <span class="admin--logo-sub">ADMIN</span>
+        </div>
+      </div>
+      <div class="admin--header-right">
+        <button class="admin--header-btn" @click="goToProfile">정보수정</button>
+        <button
+          type="button"
+          class="admin--header-btn admin--header-btn-logout"
+          @click.prevent="handleLogout"
+        >
+          로그아웃
+        </button>
+      </div>
+    </header>
+
+    <!-- Main Content Area -->
+    <div class="admin--content-wrapper">
+      <!-- Sidebar GNB -->
+      <aside class="admin--sidebar">
+        <nav class="admin--gnb">
+          <div v-for="menu in menuItems" :key="menu.id" class="admin--gnb-group">
+            <div class="admin--gnb-title" @click="toggleMenu(menu.id)">
+              {{ menu.title }}
+              <span
+                class="admin--gnb-arrow"
+                :class="{ 'is-open': openMenus.includes(menu.id) }"
+              >
+                ▼
+              </span>
+            </div>
+            <transition name="admin--submenu">
+              <ul v-show="openMenus.includes(menu.id)" class="admin--gnb-submenu">
+                <li
+                  v-for="submenu in menu.children"
+                  :key="submenu.path"
+                  class="admin--gnb-item"
+                  :class="{ 'is-active': isActiveRoute(submenu.path) }"
+                >
+                  <NuxtLink :to="submenu.path" class="admin--gnb-link">
+                    {{ submenu.title }}
+                  </NuxtLink>
+                </li>
+              </ul>
+            </transition>
+          </div>
+        </nav>
+      </aside>
+
+      <!-- Content Area -->
+      <main class="admin--main">
+        <!-- Breadcrumb & Title -->
+        <div class="admin--page-header">
+          <h2 class="admin--page-title">{{ pageTitle }}</h2>
+          <div class="admin--breadcrumb">
+            <span v-for="(crumb, index) in breadcrumbs" :key="index">
+              <NuxtLink v-if="crumb.path" :to="crumb.path" class="admin--breadcrumb-link">
+                {{ crumb.title }}
+              </NuxtLink>
+              <span v-else class="admin--breadcrumb-current">{{ crumb.title }}</span>
+              <span
+                v-if="index < breadcrumbs.length - 1"
+                class="admin--breadcrumb-separator"
+                >/</span
+              >
+            </span>
+          </div>
+        </div>
+
+        <!-- Page Content -->
+        <div class="admin--page-content">
+          <slot />
+        </div>
+
+        <!-- Admin Footer -->
+        <footer class="admin--footer">
+          <p>
+            &copy; {{ new Date().getFullYear() }} FORDKOREA 포드코리아. All rights
+            reserved.
+          </p>
+        </footer>
+      </main>
+    </div>
+
+    <!-- 로그아웃 확인 모달 -->
+    <AdminAlertModal
+      v-if="showLogoutModal"
+      title="로그아웃"
+      message="로그아웃 하시겠습니까?"
+      type="confirm"
+      @confirm="confirmLogout"
+      @cancel="closeLogoutModal"
+      @close="closeLogoutModal"
+    />
+
+    <!-- 관리자 정보수정 모달 -->
+    <AdminModal
+      v-if="showProfileModal"
+      :admin="currentAdmin"
+      @close="closeProfileModal"
+      @saved="handleProfileSaved"
+    />
+
+    <!-- 알림 모달 -->
+    <AdminAlertModal
+      v-if="alertModal.show"
+      :title="alertModal.title"
+      :message="alertModal.message"
+      :type="alertModal.type"
+      @confirm="handleAlertConfirm"
+      @cancel="handleAlertCancel"
+      @close="closeAlertModal"
+    />
+  </div>
+</template>

+ 14 - 0
app/layouts/default.vue

@@ -0,0 +1,14 @@
+<template>
+  <div class="g-layout">
+    <slot />
+  </div>
+</template>
+
+<script setup>
+  import { watch } from 'vue';
+
+  const route = useRoute();
+</script>
+
+<style scoped>
+</style>

+ 38 - 0
app/middleware/auth.js

@@ -0,0 +1,38 @@
+export default defineNuxtRouteMiddleware((to) => {
+  // SSR에서는 middleware 실행 안 함 (클라이언트에서만)
+  if (import.meta.server) {
+    return
+  }
+
+  // Admin 페이지가 아니면 middleware 실행 안 함
+  if (!to.path.startsWith('/site-manager')) {
+    return
+  }
+
+  const token = localStorage.getItem('admin_token')
+
+  console.log('[Auth Middleware]', {
+    path: to.path,
+    hasToken: !!token,
+    token: token ? token.substring(0, 20) + '...' : null
+  })
+
+  // 로그인 페이지는 예외 처리
+  if (to.path === '/site-manager' || to.path === '/site-manager/') {
+    // 이미 로그인된 경우 대시보드로
+    if (token) {
+      console.log('[Auth] 이미 로그인됨, dashboard로 이동')
+      return navigateTo('/site-manager/dashboard')
+    }
+    console.log('[Auth] 로그인 페이지 접근 허용')
+    return
+  }
+
+  // Admin 페이지 접근 시 토큰 체크
+  if (!token) {
+    console.log('[Auth] 토큰 없음, 로그인 페이지로 이동')
+    return navigateTo('/site-manager')
+  }
+
+  console.log('[Auth] 토큰 확인됨, 페이지 접근 허용')
+})

+ 459 - 0
app/pages/ford/about/company.vue

@@ -0,0 +1,459 @@
+<template>
+  <div>
+    <breadCrumbs
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="about--wrap">
+      <div class="container">
+        <div class="title--visual">
+          <h2>FORD STORY</h2>
+          <div class="sub--title">
+            포드 모터 컴퍼니는 고객 여러분께 승용차, 트럭, SUV 를 비롯해 다양한 제품과
+            서비스를 세계 여러 나라에 제공하고 있습니다.<br />
+            새로운 100년을 맞이하면서 저희는 폭넓은 잠재 고객층에게 더욱 가까이 다가가기
+            위해 노력하고 있습니다.<br />
+            포드의 모든 자동차 브랜드들은 포드 모터 컴퍼니 그룹 내에서 저마다 독특한
+            개성과 차별성을 자랑하고 있습니다.
+          </div>
+        </div>
+        <div class="visual--banner--01 mt--70">
+          <img src="/img/about/about--banner01.jpg" alt="FORD STORY" />
+        </div>
+
+        <div class="title--visual">
+          <h2>FORD BRAND</h2>
+          <div class="sub--title">
+            포드 모터 컴퍼니는 고객 여러분께 승용차, 트럭, SUV 를 비롯해 다양한 제품과
+            서비스를 세계 여러 나라에 제공하고 있습니다.<br />
+            새로운 100년을 맞이하면서 저희는 폭넓은 잠재 고객층에게 더욱 가까이 다가가기
+            위해 노력하고 있습니다.<br />
+            포드의 모든 자동차 브랜드들은 포드 모터 컴퍼니 그룹 내에서 저마다 독특한
+            개성과 차별성을 자랑하고 있습니다.
+          </div>
+        </div>
+        <div class="visual--banner--02 mt--70">
+          <div class="thumb--wrap">
+            <div class="thumb--item">
+              <div class="thumb">
+                <img src="/img/about/about--banner02.png" alt="FORD" />
+              </div>
+              <div class="desc">
+                <h2 class="t--title">FORD</h2>
+                <div class="captions">
+                  포드 모터 컴퍼니의 최초의 브랜드인 ‘포드’는 전 세계인의 다양한 라이프
+                  스타일에 맞는 개성 있는 디자인과 합리적인 가격대의 자동차를 제공합니다.
+                  그러한 약속은 세상에 선보인 첫 자동차인 모델 T부터 최근 사랑받고 있는
+                  미국의 Mustang과 유럽의 Mondeo, 남미의 EcoSport에 이르 기까지
+                  한결같습니다. 세계에서 가장 인기 있는 승용차, 트럭, SUV 가운데에는 늘
+                  포드 차들이 있습 니다.
+                </div>
+              </div>
+            </div>
+            <div class="thumb--item">
+              <div class="thumb">
+                <img src="/img/about/about--banner03.png" alt="FORD" />
+              </div>
+              <div class="desc">
+                <h2 class="t--title">FORD HISTORY</h2>
+                <div class="captions">
+                  포드 모터 컴퍼니는 미국 미시건 주의 한 마차 상점에서 포드 배지를 단
+                  자동차로부터 시작되었습니 다. 많은 패밀리 자동차 브랜드들의 창립자인
+                  포드의 진수를 알고 싶으시면, 헨리 포드의 첫 자동차 실 험에서부터 모델 A,
+                  모델 T 그리고 그 이후의 차들에 이르는 포드 모터 컴퍼니 초창기 역사를
+                  살펴보 시기 바랍니다. 포드의 모델 개발에 있어 ‘가격 대비 가치 (Value for
+                  Money)’ 는 언제나 최우 선 과제였지만, 결코 품질과 타협하지 않았고 더
+                  나은 품질을 추구하려는 노력은 계속되었습니다. 포드는 유럽인들의 현실적
+                  요구 조건인 좀 더 작은  사이즈의 모델을 제공하거나 계속 변화되는 환경 에
+                  부합하기 위해 더 똑똑한 차를 제공하는 등 고객이 원하는 그 이상을
+                  실현하기 위해 끊임없이 노력 하고 있습니다.
+                </div>
+              </div>
+            </div>
+
+            <div class="thumb--item">
+              <div class="thumb">
+                <img src="/img/about/about--banner04.png" alt="FORD" />
+              </div>
+              <div class="desc">
+                <h2 class="t--title">LINCOLN</h2>
+                <div class="captions">
+                  1921년 이래 링컨은 미국 최고의 럭셔리 자동차 브랜드의 아이콘으로 그
+                  자리를 지켜 왔습니다. 오늘날에도 링컨은 가장 순수한 미국의 정신을
+                  담아내고 있습니다.<br />
+                  다이나믹한 미국 디자인의 정수로서, 링컨은 우아함과 균형미, 풍부함과
+                  절제의 조화를 아우르며 럭셔리를 대담하게 표현합니다.
+                </div>
+              </div>
+            </div>
+
+            <div class="thumb--item">
+              <div class="thumb">
+                <img src="/img/about/about--banner05.png" alt="FORD" />
+              </div>
+              <div class="desc">
+                <h2 class="t--title">LINCOLN HISTORY</h2>
+                <div class="captions">
+                  포드 모터 컴퍼니는 1922년, 8백만 달러에 링컨 모터 컴퍼니를
+                  인수하였습니다. 링컨은 포드 브랜드 그룹에 합류한 최초의 “외부
+                  브랜드”였으며, 포드 모터 컴퍼니가 럭셔리 자동차 분야로 진입하는 시발
+                  점이 되었습니다. <br />
+                  링컨의 자동차 역사의 대부분은 “최고급의 향유”를 제공한다는 브랜드 목표를
+                  만족시키기 위해 매년 업그레이드된 컨티넨탈이나 새로운 소수의 럭셔리카
+                  라인으로 구성되어 있습니다.
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="our--vision pt--30 mt--120">
+        <div class="container">
+          <div class="title--visual">
+            <h2>OUR VISION</h2>
+            <div class="sub--title">
+              글로벌 기업인 포드의 임직원들은 전 세계 자동차 산업의 선두에 서서 사람들의
+              삶을 더욱 풍요롭게 하고자 함께 노력하고 있습니다.
+            </div>
+          </div>
+
+          <div class="vision--wrap">
+            <ul>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <g clip-path="url(#clip0_188_6220)">
+                      <path
+                        d="M45 26.2865L24.992 50.0005L5 26.2865L13.568 15.9375H36.416L45 26.2865Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="bevel"
+                      />
+                      <path
+                        d="M5 26.2852H45"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="bevel"
+                      />
+                      <path
+                        d="M31.7411 26.2865L24.992 50.0005L18.2429 26.2865L21.1468 15.9375H28.8532L31.7411 26.2865Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="bevel"
+                      />
+                      <path
+                        d="M24.992 0V6.69013"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="bevel"
+                      />
+                      <path
+                        d="M5.38297 7.79492L10.3291 12.5255"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="bevel"
+                      />
+                      <path
+                        d="M44.6011 7.79492L39.6709 12.5255"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="bevel"
+                      />
+                    </g>
+                    <defs>
+                      <clipPath id="clip0_188_6220">
+                        <rect width="50" height="50" fill="white" />
+                      </clipPath>
+                    </defs>
+                  </svg>
+                </div>
+                <div class="desc">
+                  <h2>품질 QUALITY</h2>
+                  <div class="captions">
+                    포드는 항상 품질을 최우선으로 하고 있습니다. 설계, 제조에서부터 세일즈
+                    및 서비스에 이르기까지 언제나 고객을 더욱 만족시키기 위해 노력합니다.
+                  </div>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <g clip-path="url(#clip0_188_6229)">
+                      <path
+                        d="M21.4849 16.5112L11.1846 2.02148"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-miterlimit="10"
+                      />
+                      <path
+                        d="M26.9813 6.72439C27.4431 3.91918 25.5035 1.2772 22.6491 0.823359C19.7947 0.369517 17.1063 2.27567 16.6445 5.08088C16.1827 7.88609 18.1223 10.5281 20.9767 10.9819C23.8312 11.4358 26.5195 9.52959 26.9813 6.72439Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-miterlimit="10"
+                      />
+                      <path
+                        d="M21.4849 16.5098H22.1401C24.8519 16.5098 27.0509 18.6709 27.0509 21.336V34.7563H16.574V21.336C16.574 18.6709 18.7731 16.5098 21.4849 16.5098Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-miterlimit="10"
+                      />
+                      <path
+                        d="M16.574 34.7598H38.1203L42 49.9998"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-miterlimit="10"
+                      />
+                      <path
+                        d="M27.0509 23.0137V23.7807L19.0181 34.76L16.574 38.0353"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-miterlimit="10"
+                        stroke-linecap="square"
+                      />
+                      <path
+                        d="M13.8737 38.0385V16.0015C13.8737 14.2099 12.3981 12.7598 10.5751 12.7598H8V43.8165H34.2065V41.3362C34.2065 39.5166 32.7024 38.0385 30.8509 38.0385H13.8737Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-miterlimit="10"
+                      />
+                    </g>
+                    <defs>
+                      <clipPath id="clip0_188_6229">
+                        <rect width="50" height="50" fill="white" />
+                      </clipPath>
+                    </defs>
+                  </svg>
+                </div>
+                <div class="desc">
+                  <h2>안전 SAFETY</h2>
+                  <div class="captions">
+                    저희는 포드 자동차를 타는 고객과 가족의 안전을 위해 혁신 기술의 개발
+                    및 구현에 오랜 기간 힘써왔습니다.
+                  </div>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <path
+                      d="M14.2186 20.1543V48.7787"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M23.4052 32.1382C23.4052 37.2606 19.2832 41.4166 14.2026 41.4166C9.122 41.4166 5 37.2606 5 32.1382C5 27.0158 9.13798 11.9707 14.2186 11.9707C19.2992 11.9707 23.4212 27.0319 23.4212 32.1543L23.4052 32.1382Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M36.618 20.1543V48.7787"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M27.4314 32.1387C27.4314 37.2611 31.5534 41.4171 36.634 41.4171C41.7146 41.4171 45.8366 37.2611 45.8366 32.1387C45.8366 27.0163 41.7146 11.9551 36.634 11.9551C31.5534 11.9551 27.4314 27.0163 27.4314 32.1387Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M7.76404 48.7773H43.0727"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M37.0974 27.6443C37.0974 34.1521 31.873 39.4195 25.4184 39.4195C18.9638 39.4195 13.7394 34.1521 13.7394 27.6443C13.7394 21.1366 18.9638 2 25.4184 2C31.873 2 37.0974 21.1366 37.0974 27.6443Z"
+                      fill="#F3F3F3"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M25.4183 12.4062V48.7787"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M29.7001 16.5938L25.4184 20.9108L21.1366 16.5938"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M30.8664 24.6309L25.4183 30.1238L19.9702 24.6309"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                  </svg>
+                </div>
+                <div class="desc">
+                  <h2>친환경 GREEN</h2>
+                  <div class="captions">
+                    지속가능성에 대한 전략(Sustainability strategy)은 탄소 배출을 감소한
+                    것에서 멈추지 않습니다. 사내에서 직원들의 물 사용에서부터 근로자
+                    인권에 이르기까지 비즈니스의 모든 부분에서 지속가능성
+                    (Sustainability)은 필수적인 부분으로 자리 잡았습니다.
+                  </div>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <path
+                      d="M20.449 27.485L13.4156 29.5801L17.0003 41.4435L24.0336 39.3484L20.449 27.485Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M25.9142 39.3482L32.9475 41.4434L37.6711 25.8106L30.6378 23.7155L25.9142 39.3482Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M33.0704 2.16566L14.0682 7.62574C11.8247 8.27038 10.5312 10.595 11.1791 12.8179C11.827 15.0408 14.1709 16.3203 16.4144 15.6756L35.4167 10.2155C37.6602 9.5709 38.9536 7.24629 38.3058 5.02338C37.6579 2.80047 35.3139 1.52102 33.0704 2.16566Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M33.0645 11.0563L14.0622 16.5164C11.8187 17.161 10.5252 19.4856 11.1731 21.7085C11.821 23.9314 14.1649 25.2109 16.4084 24.5662L35.4107 19.1062C37.6542 18.4615 38.9477 16.1369 38.2998 13.914C37.6519 11.6911 35.308 10.4116 33.0645 11.0563Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M33.0761 19.9469L14.0738 25.407C11.8303 26.0516 10.5368 28.3762 11.1847 30.5992C11.8326 32.8221 14.1765 34.1015 16.42 33.4569L35.4223 27.9968C37.6658 27.3522 38.9593 25.0275 38.3114 22.8046C37.6635 20.5817 35.3196 19.3023 33.0761 19.9469Z"
+                      fill="#F3F3F3"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M15.1887 39.3574H34.7584V43.0468C34.7584 46.0335 32.3166 48.4755 29.2909 48.4755H20.6562C17.6482 48.4755 15.1887 46.051 15.1887 43.0468V39.3574Z"
+                      fill="#F3F3F3"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                  </svg>
+                </div>
+                <div class="desc">
+                  <h2>스마트 SMART</h2>
+                  <div class="captions">
+                    포드의 중심에는 혁신이 있습니다. 일반 대중 차 시장에서도 포드는 가장
+                    먼저 고강도 알루미늄 적용을 대폭 확대했으며, 2014년에는 자동차 산업
+                    최초로 차량 내 연결성 (In- car connectivity)에 관한 개발자 회의를
+                    개최하였습니다.
+                  </div>
+                </div>
+              </li>
+            </ul>
+          </div>
+        </div>
+      </div>
+      <div class="container">
+        <div class="strong--business">
+          <div class="title--visual">
+            <h2>강력한 비즈니스 (Strong Business)</h2>
+            <div class="sub--title">
+              모든 운전자에게 좋은 차가 최고의 자동차입니다. 포드 비즈니스의 힘은 바로
+              이런 믿음에서 나옵니다.<br />
+              포드는 늘어나는 수요를 맞추기 위해 50년만에 최대 규모로 제조 시설을 확장하고
+              있습니다.<br />
+              미국 내 생산 역량을 확대하고 세계 곳곳에 신규 시설을 건설 중입니다.<br />
+              포드는 지속적인 수익을 창출하며 성장하고 있습니다.<br />
+              지난해에도 전세계 직원들의 연금 제도를 지원했으며, 미국 내 시간제 직원들에게
+              이익분배금을 지급하고, 주주 배당금을 두 배로 올렸습니다.
+            </div>
+          </div>
+          <div class="btn--wrap mt--70">
+            <a class="btn--sky"
+              href="https://www.ford.com/finance/investor-center?gnav=footer-finance"
+              target="_blank"
+              >투자자 (Investors) 자세히 알아보기<svg
+                xmlns="http://www.w3.org/2000/svg"
+                width="16"
+                height="16"
+                viewBox="0 0 16 16"
+                fill="none"
+              >
+                <path
+                  d="M3.33203 8H12.6654"
+                  stroke="white"
+                  stroke-linecap="round"
+                  stroke-linejoin="round"
+                />
+                <path
+                  d="M8 3.33398L12.6667 8.00065L8 12.6673"
+                  stroke="white"
+                  stroke-linecap="round"
+                  stroke-linejoin="round"
+                /></svg
+            ></a>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+
+  const breadcrumbData = {
+    mainMenu: "ABOUT FORD",
+    currentSubMenu: "GLOBAL SITES",
+    subMenuItems: [
+      { label: "Company", to: "/ford/about/company", active: true },
+      { label: "Our Purpose", to: "/ford/about/ourPurpose", active: false },
+    ],
+  };
+</script>

+ 136 - 0
app/pages/ford/about/ourPurpose.vue

@@ -0,0 +1,136 @@
+<template>
+  <div>
+    <breadCrumbs
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="about--wrap">
+      <div class="container">
+        <div class="title--visual">
+          <h2>OUR PURPOSE</h2>
+          <div class="sub--title">
+            “모든 사람이 자유롭게 이동하며, 꿈꾸는 모든 것을 이룰 수 있는 더 나은 세상을
+            만들자.”라는 미션으로 우리는 여기에 모였습니다.
+          </div>
+        </div>
+        <div class="visual--banner--01 mt--70">
+          <img src="/img/about/ourpp--banner01.jpg" alt="our purpose" />
+        </div>
+
+        <div class="title--visual">
+          <h2 class="side--title">우리의 미션</h2>
+          <h3 class="middle--title">
+            “모든 사람이 자유롭게 이동하며, 꿈꾸는 모든 것을 이룰 수 있는 더 나은 세상을
+            만들자.”라는 미션으로 우리는 여기에 모였습니다.
+          </h3>
+          <div class="sub--title">
+            우리는 사람들이 장애물과 한계 없이 더 나은 삶을 누리고, 더 큰 꿈을 향해 전진할
+            수 있는 세상을 만드는 힘을 믿습니다.<br />
+            내가 가고자 하는 그곳까지의 거리를 더 가깝게 만들고, 전 세계 곳곳의 사람들을
+            연결하며, 더 많은 가능성을 발견하고 자유로운 이동이 선사하는<br />
+            스릴과 모험 그리고 자부심을 즐길 수 있게 합니다.<br />포드는 창립 초기부터
+            사람들이 더 멀리, 더 높이 나아갈 수 있는 길을 만들어왔습니다.<br />
+            사람들이 더 많은 기회를 찾을 수 있도록 혁신을 거듭했고, 신뢰를 얻기 위해 매
+            순간 노력해왔습니다.<br />지금까지의 위대한 경험을 발판으로 포드는 새로운
+            미래를 만들어 나가고 있습니다.<br />
+            미래의 모든 사람들이 자유롭게 이동하고 한계 없는 꿈을 꿀 수 있는 더 나은
+            세상으로 변화시키는 일.<br />
+            그것이 포드가 가장 잘 할 수 있는 일입니다.
+          </div>
+        </div>
+      </div>
+
+      <div class="grid--banner--wrapper">
+        <div class="grid--items">
+          <div class="grid--banner">
+            <img src="/img/about/ourpp--grd01.png" />
+          </div>
+          <div class="grid--desc">
+            <h2>더 나은 삶</h2>
+            <div class="captions">
+              포드의 역사는 사람들의 성장, 발전을 막는 장애물들을 극복해 나가고자 하는
+              노력에서 시작 되었습니다. 우리는 누구나 자유롭게 이동하고 꿈을 실현하며
+              최고의 삶을 누릴 자격이 있다고 믿습니다.
+            </div>
+          </div>
+        </div>
+        <div class="grid--items revers dark">
+          <div class="grid--banner">
+            <img src="/img/about/ourpp--grd02.png" />
+          </div>
+          <div class="grid--desc">
+            <h2>결속력이 강한 커뮤니티 형성</h2>
+            <div class="captions">
+              포드는 직원, 노조, 고객, 딜러, 기업, 이웃, 도시, 정부를 포함한 지역 사회와의
+              깊은 연대감을 형성하고 강화하며 이를 중심으로 비즈니스를 영위해 나가고
+              있습니다.
+            </div>
+            <div class="btn--wrap fl--start mt--50">
+              <a
+                href="https://corporate.ford.com//social-impact/community"
+                target="_blank"
+                class="btn--sky"
+                >커뮤니티 활동 보기</a
+              >
+            </div>
+          </div>
+        </div>
+        <div class="grid--items">
+          <div class="grid--banner">
+            <img src="/img/about/ourpp--grd03.png" />
+          </div>
+          <div class="grid--desc">
+            <h2>더 건강한 지구</h2>
+            <div class="captions">
+              포드는 창업자 헨리 포드(Henry Ford) 회장 시절부터 빌 포드(Bill Ford) 회장이
+              이끄는 오늘에 이르기까지, 우리 모두의 지구를 위한 지속 가능한 미래를 만들기
+              위해 거침없는 행보를 이어왔습니다.
+            </div>
+            <div class="btn--wrap fl--start mt--50">
+              <a
+                href="https://corporate.ford.com//social-impact/sustainability"
+                target="_blank"
+                class="btn--sky"
+                >지속 가능성 활동 보기</a
+              >
+            </div>
+          </div>
+        </div>
+        <div class="grid--items revers dark">
+          <div class="grid--banner">
+            <img src="/img/about/ourpp--grd04.png" />
+          </div>
+          <div class="grid--desc">
+            <h2>전동화로 만들어 가는 더 나은 세상</h2>
+            <div class="captions">
+              포드의 ‘지속 가능성 및 재무 통합 보고서’는 포드의 미션과 비즈니스가 어떻게
+              연계되어 있는지 보여줍니다.
+            </div>
+            <div class="btn--wrap fl--start mt--50">
+              <a
+                href="https://corporate.ford.com/microsites/integrated-sustainability-and-financial-report-2021/index.html"
+                target="_blank"
+                class="btn--sky"
+                >통합 보고서 보기</a
+              >
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+
+  const breadcrumbData = {
+    mainMenu: "ABOUT FORD",
+    currentSubMenu: "GLOBAL SITES",
+    subMenuItems: [
+      { label: "Company", to: "/ford/about/company", active: false },
+      { label: "Our Purpose", to: "/ford/about/ourPurpose", active: true },
+    ],
+  };
+</script>

+ 120 - 0
app/pages/ford/index.vue

@@ -0,0 +1,120 @@
+<template>
+  <main>
+    <Popup site="ford" />
+    <BlockMainSwiper />
+    <div class="container">
+      <!-- 2026-04-15 08:00:00 이전 -->
+      <section class="main--section" v-if="!isAfterLaunch">
+        <h2>완벽함을 소유하다</h2>
+        <p>포드에서 전하는 최신 모델 정보와 특별한 이야기를 만나보세요</p>
+        <div class="main--grid--wrap">
+          <NuxtLink to="/ford/vehicle/ranger" class="grid">
+            <h3>RANGER <i class="ico"></i></h3>
+            <p>OUR MOST VERSATILE RANGER EVER</p>
+          </NuxtLink>
+          <div class="grid--group--wrap">
+            <div class="grid--wrap">
+              <NuxtLink to="/ford/vehicle/mustang" class="grid">
+                <h3>mustang<i class="ico"></i></h3>
+                <p>rules of the open road</p>
+              </NuxtLink>
+              <NuxtLink to="/ford/vehicle/explorer" class="grid">
+                <h3>explorer<i class="ico"></i></h3>
+                <p>it's all in the name, 포드 익스플로러</p>
+              </NuxtLink>
+            </div>
+            <div class="grid--wrap">
+              <NuxtLink to="/ford/vehicle/bronco" class="grid">
+                <h3>bronco<i class="ico"></i></h3>
+                <p>bronco outer banks</p>
+              </NuxtLink>
+              <NuxtLink to="/ford/vehicle/expedition" class="grid">
+                <h3>expedition<i class="ico"></i></h3>
+                <p>the road to adventure is often unpaved</p>
+              </NuxtLink>
+            </div>
+          </div>
+        </div>
+        <div class="main--list--wrap">
+          <NuxtLink to="/ford/network" class="list">
+            <i class="ico"></i>
+            <h3>전시장 찾기</h3>
+            <p>가까운 주변의 전시장을 확인해보세요. <i class="ico"></i></p>
+          </NuxtLink>
+          <NuxtLink to="/ford/network" class="list">
+            <i class="ico"></i>
+            <h3>서비스 센터 찾기</h3>
+            <p>가까운 주변의 서비스 센터를 확인해보세요. <i class="ico"></i></p>
+          </NuxtLink>
+          <NuxtLink to="/ford/owner" class="list">
+            <i class="ico"></i>
+            <h3>공인 서비스</h3>
+            <p>
+              포드 전문가에게 맡기시면 차별화된 서비스를 받으실 수 있습니다.<i
+                class="ico"
+              ></i>
+            </p>
+          </NuxtLink>
+        </div>
+      </section>
+      <!-- 2026-04-15 08:00:00 이후 -->
+      <section class="main--section" v-if="isAfterLaunch">
+        <h2>SEE ALL FORD</h2>
+        <p>포드에서 전하는 최신 모델 정보와 특별한 이야기를 만나보세요</p>
+        <div class="main--grid--wrap">
+          <NuxtLink to="/ford/vehicle/explorer" class="grid explorer">
+            <h3>explorer<i class="ico"></i></h3>
+            <p>it's all in the name, 포드 익스플로러</p>
+          </NuxtLink>
+          <div class="grid--group--wrap">
+            <div class="grid--wrap">
+              <NuxtLink to="/ford/vehicle/mustang" class="grid mustang">
+                <h3>mustang<i class="ico"></i></h3>
+                <p>rules of the open road</p>
+              </NuxtLink>
+              <NuxtLink to="/ford/vehicle/bronco" class="grid bronco">
+                <h3>bronco<i class="ico"></i></h3>
+                <p>bronco outer banks</p>
+              </NuxtLink>
+            </div>
+            <div class="grid--wrap">
+              <NuxtLink to="/ford/vehicle/ranger" class="grid ranger">
+                <h3>RANGER <i class="ico"></i></h3>
+                <p>OUR MOST VERSATILE RANGER EVER</p>
+              </NuxtLink>
+              <NuxtLink to="/ford/vehicle/expedition" class="grid expedition">
+                <h3>expedition<i class="ico"></i></h3>
+                <p>the road to adventure is often unpaved</p>
+              </NuxtLink>
+            </div>
+          </div>
+        </div>
+        <div class="main--list--wrap">
+          <NuxtLink to="/ford/network" class="list">
+            <i class="ico"></i>
+            <h3>전시장 찾기</h3>
+            <p>가까운 주변의 전시장을 확인해보세요. <i class="ico"></i></p>
+          </NuxtLink>
+          <NuxtLink to="/ford/network" class="list">
+            <i class="ico"></i>
+            <h3>서비스 센터 찾기</h3>
+            <p>가까운 주변의 서비스 센터를 확인해보세요. <i class="ico"></i></p>
+          </NuxtLink>
+          <NuxtLink to="/ford/owner" class="list">
+            <i class="ico"></i>
+            <h3>공인 서비스</h3>
+            <p>
+              포드 전문가에게 맡기시면 차별화된 서비스를 받으실 수 있습니다.<i
+                class="ico"
+              ></i>
+            </p>
+          </NuxtLink>
+        </div>
+      </section>
+    </div>
+  </main>
+</template>
+
+<script setup>
+  const isAfterLaunch = useTimeSwitch('2026-04-15T08:00:00+09:00');
+</script>

+ 589 - 0
app/pages/ford/network/index.vue

@@ -0,0 +1,589 @@
+<template>
+  <div>
+    <breadCrumbs
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="container">
+      <div class="title--wrap">
+        <h2 class="mb--15">전시장 및 서비스센터</h2>
+        <p>전국의 포드 전시장 및 서비스센터를 한 눈에 찾아보실 수 있습니다.</p>
+      </div>
+      <div class="title--visual mt--50">
+        <h2 class="side--title2 text--justify--left">전시장 안내</h2>
+      </div>
+
+      <div class="table--wrap service--center--ford mt--24">
+        <table>
+          <colgroup></colgroup>
+          <thead>
+            <tr>
+              <th style="min-width: 100px">지역</th>
+              <th>전시장명</th>
+              <th>전화번호</th>
+              <th>주소</th>
+            </tr>
+          </thead>
+          <tbody>
+            <!-- <tr>
+              <td class="text--center" rowspan="5">서울</td>
+              <td>대치 전시장 (선인자동차)</td>
+              <td>02-3442-2300</td>
+              <td class="text--justify--left">서울특별시 강남구 영동대로 333 (대치동)</td>
+            </tr> -->
+            <tr>
+              <td class="text--center" rowspan="2">서울</td>
+              <td>서초 전시장(선인자동차)</td>
+              <td>02-535-3800</td>
+              <td class="text--justify--left">
+                서울특별시 서초구 반포대로 63 (서초동, 진석빌딩 1층)
+              </td>
+            </tr>
+            <tr>
+              <td>강북 전시장(선인자동차)</td>
+              <td>02-2246-2100</td>
+              <td class="text--justify--left">
+                서울특별시 성동구 천호대로 366 (용답동, 미라보타워)
+              </td>
+            </tr>
+            <!-- <tr>
+              <td>강서 전시장(선인자동차)</td>
+              <td>02-2063-6300</td>
+              <td class="text--justify--left">
+                서울특별시 강서구 강서로 412 (등촌동, 영광빌딩 1층)
+              </td>
+            </tr> -->
+            <!-- <tr>
+              <td>송파 전시장(프리미어모터스)</td>
+              <td>02-6928-3000</td>
+              <td class="text--justify--left">경기도 하남시 감일남로 29 (감일동)</td>
+            </tr>
+            <tr>
+              <td>영등포 전시장(프리미어모터스)</td>
+              <td>02-6941-3000</td>
+              <td class="text--justify--left">
+                서울특별시 영등포구 국회대로 672 (영등포동7가)
+              </td>
+            </tr> -->
+
+            <tr>
+              <td class="text--center" rowspan="3">경기</td>
+              <td>일산 전시장(선인자동차)</td>
+              <td>031-913-2200</td>
+              <td class="text--justify--left">
+                경기도 고양시 일산동구 백마로 514 (풍동)
+              </td>
+            </tr>
+            <tr>
+              <td>수원 전시장(선인자동차)</td>
+              <td>031-221-7600</td>
+              <td class="text--justify--left">
+                경기도 용인시 기흥구 중부대로 13 (영덕동)
+              </td>
+            </tr>
+            <tr>
+              <td>분당 전시장(선인자동차)</td>
+              <td>031-714-2004</td>
+              <td class="text--justify--left">
+                경기도 성남시 분당구 새마을로 81 (서현동)
+              </td>
+            </tr>
+            <!-- <tr>
+              <td>부천 전시장(프리미어모터스)</td>
+              <td>032-710-3355</td>
+              <td class="text--justify--left">
+                경기도 부천시 부천로 170 (춘의동, 에스에이치타워 1층)
+              </td>
+            </tr>
+            <tr>
+              <td>평촌 전시장(프리미어모터스)</td>
+              <td>031-425-2212</td>
+              <td class="text--justify--left">
+                경기도 안양시 동안구 시민대로 380 (관양동)
+              </td>
+            </tr>
+            <tr>
+              <td>평택 전시장 (프리미어모터스)</td>
+              <td>031-647-4044</td>
+              <td class="text--justify--left">
+                경기도 안성시 공도읍 서동대로 3930-8 (공도읍)
+              </td>
+            </tr> -->
+            <tr>
+              <td class="text--center">인천</td>
+              <td>인천 전시장(선인자동차)</td>
+              <td>032-656-2300</td>
+              <td class="text--justify--left">
+                인천광역시 남동구 남동대로 750 (구월동, 유영빌딩 1층)
+              </td>
+            </tr>
+            <tr>
+              <td class="text--center" rowspan="3">충청</td>
+              <td>대전 전시장(선인자동차)</td>
+              <td>042-823-2000</td>
+              <td class="text--justify--left">대전광역시 서구 계룡로 650 (용문동)</td>
+            </tr>
+            <tr>
+              <td>천안 전시장(선인자동차)</td>
+              <td>041-562-0007</td>
+              <td class="text--justify--left">
+                충청남도 천안시 동남구 천안대로 566 (구성동)
+              </td>
+            </tr>
+            <tr>
+              <td>청주 전시장(선인자동차)</td>
+              <td>043-287-0007</td>
+              <td class="text--justify--left">
+                충청북도 청주시 서원구 남이면 2순환로 1778 (양촌리)
+              </td>
+            </tr>
+
+            <tr>
+              <td class="text--center">부산</td>
+              <td>부산 수영 전시장(선인자동차)</td>
+              <td>051-758-0075</td>
+              <td class="text--justify--left">부산광역시 수영구 수영로 554 (광안동)</td>
+            </tr>
+            <!-- <tr>
+              <td class="text--center">경남</td>
+              <td>창원 전시장(프리미어모터스)</td>
+              <td>055-715-5000</td>
+              <td class="text--justify--left">경상남도 창원시 마산회원구 무역로 103</td>
+            </tr> -->
+            <tr>
+              <td class="text--center">울산</td>
+              <td>울산 전시장(선인자동차)</td>
+              <td>052-261-3388</td>
+              <td class="text--justify--left">울산광역시 남구 문수로 328 (옥동)</td>
+            </tr>
+            <tr>
+              <td class="text--center">대구</td>
+              <td>대구 전시장(선인자동차)</td>
+              <td>053-766-2000</td>
+              <td class="text--justify--left">대구광역시 수성구 동대구로 26 (지산동)</td>
+            </tr>
+            <!-- <tr>
+              <td class="text--center">경북</td>
+              <td>포항 전시장(프리미어모터스)</td>
+              <td>054-285-8899</td>
+              <td class="text--justify--left">
+                경상북도 포항시 남구 새천년대로 337 (효자동)
+              </td>
+            </tr> -->
+            <!-- <tr>
+              <td class="text--center">광주</td>
+              <td>광주 전시장(이한모터스)</td>
+              <td>062-515-1010</td>
+              <td class="text--justify--left">광주광역시 서구 상무대로 1226 (양동)</td>
+            </tr>
+            <tr>
+              <td class="text--center">전남</td>
+              <td>순천 전시장(이한모터스)</td>
+              <td>061-905-7000</td>
+              <td class="text--justify--left">전라남도 순천시 중앙로 37 (저전동)</td>
+            </tr> -->
+            <tr>
+              <td class="text--center">전북</td>
+              <td>전주 전시장(선인자동차)</td>
+              <td>063-273-0005</td>
+              <td class="text--justify--left">
+                전라북도 전주시 덕진구 장동유통로 29-8 (장동)
+              </td>
+            </tr>
+            <tr>
+              <td class="text--center">강원</td>
+              <td>원주 전시장(더파크모터스)</td>
+              <td>033-762-0040</td>
+              <td class="text--justify--left">강원도 원주시 치악로 1320 (관설동)</td>
+            </tr>
+            <!-- <tr>
+              <td class="text--center">제주</td>
+              <td>제주 전시장(프리미어모터스)</td>
+              <td>064-759-9981~2</td>
+              <td class="text--justify--left">
+                제주특별자치도 제주시 연삼로 132 (오라3동)
+              </td>
+            </tr> -->
+          </tbody>
+        </table>
+      </div>
+
+      <div class="title--visual mt--144">
+        <h2 class="side--title2 text--justify--left">서비스센터 안내</h2>
+      </div>
+
+      <div class="table--wrap service--center--ford mt--24">
+        <table>
+          <colgroup></colgroup>
+          <thead>
+            <tr>
+              <th style="min-width: 100px">지역</th>
+              <th>서비스센터명</th>
+              <th>전화번호</th>
+              <th>주소</th>
+              <th>비고</th>
+              <th>서비스 예약</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr>
+              <td class="text--center" rowspan="5">서울</td>
+              <td>강북 서비스센터(선인자동차)</td>
+              <td>02-2216-1100</td>
+              <td>서울특별시 성동구 성수이로 20길 38 (성수동)</td>
+              <td></td>
+              <td>
+                <NuxtLink to="https://naver.me/FRu4n0nj" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+
+            <tr>
+              <td>강서 서비스센터(선인자동차)</td>
+              <td>02-3661-0011</td>
+              <td>서울특별시 강서구 화곡로 384 (등촌동)</td>
+              <td>판금, 도장 시설 포함</td>
+              <td>
+                <NuxtLink to="https://naver.me/5KqnH10A" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+
+            <tr>
+              <td>서초 서비스센터(선인자동차)</td>
+              <td>02-2057-8100</td>
+              <td>서울특별시 서초구 사임당로 98 (서초동)</td>
+              <td></td>
+              <td>
+                <NuxtLink to="https://naver.me/GSc313EI" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+
+            <tr>
+              <td>중랑 서비스센터(선인자동차)</td>
+              <td>02-2230-2222</td>
+              <td>서울특별시 중랑구 신내역로 3길 56</td>
+              <td>사고 차량 정비만 해당 (예약불가)</td>
+              <td>
+                <!-- <NuxtLink to="" target="_blank"> 서비스 예약 </NuxtLink> -->
+              </td>
+            </tr>
+
+            <tr>
+              <td>강동 지정 서비스센터(선인자동차)</td>
+              <td>02-426-6381</td>
+              <td>서울특별시 강동구 고덕비즈밸리로 51 아이파크 더리버몰 지하2층</td>
+              <td>경정비 작업만 가능</td>
+              <td>
+                <NuxtLink to="https://naver.me/5D8otPP8" target="_blank"> 서비스 예약 </NuxtLink>
+              </td>
+            </tr>
+
+            <tr>
+              <td class="text--center" rowspan="7">경기</td>
+              <td>분당 서비스센터(선인자동차)</td>
+              <td>031-701-7004</td>
+              <td>경기도 성남시 분당구 새마을로 81 (서현동)</td>
+              <td></td>
+              <td>
+                <NuxtLink to="https://naver.me/F6nO0OQz" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+            <tr>
+              <td>일산 서비스센터(선인자동차)</td>
+              <td>031-901-2888</td>
+              <td>경기도 고양시 일산동구 백마로 514 (풍동</td>
+              <td>판금, 도장 시설 포함</td>
+              <td>
+                <NuxtLink to="https://naver.me/FZ8VsbH7" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+            <tr>
+              <td>수원 서비스센터(선인자동차)</td>
+              <td>031-237-6340</td>
+              <td>경기도 용인시 기흥구 중부대로 13 (영덕동)</td>
+              <td></td>
+              <td>
+                <NuxtLink to="https://naver.me/GlG2o2zi" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+            <tr>
+              <td>서수원 서비스센터(선인자동차)</td>
+              <td>031-292-6340</td>
+              <td>경기도 수원시 권선구 서부로 1652-1 (고색동)</td>
+              <td></td>
+              <td>
+                <NuxtLink to="https://naver.me/GMPxLxXt" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+           
+            <tr>
+              <td>용인 서비스센터(선인자동차)</td>
+              <td>031-8005-9570</td>
+              <td>경기 용인시 기흥구 용구대로2193번길 6</td>
+              <td>사고 차량 정비만 해당 (예약불가)</td>
+              <td>
+                <!-- <NuxtLink to="https://naver.me/xF27eUMc" target="_blank">
+                  서비스 예약
+                </NuxtLink> -->
+              </td>
+            </tr>
+
+            <tr>
+              <td>의정부 지정 서비스센터(선인자동차)</td>
+              <td>031-878-2250</td>
+              <td>경기도 의정부시 경의로 85</td>
+              <td>경정비 작업만 가능</td>
+              <td>
+                <NuxtLink to="https://naver.me/GkIieTEU" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+            
+
+             <tr>
+              <td>안양 지정 서비스센터(선인자동차)</td>
+              <td>031-382-2886</td>
+              <td>경기도 안양시 동안구 관악대로 276</td>
+              <td>경정비 작업만 가능</td>
+              <td>
+                <NuxtLink to="https://naver.me/xtgvc7LC" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+            
+
+            <tr>
+              <td class="text--center">인천</td>
+              <td>인천 중부 서비스센터(선인자동차)</td>
+              <td>032-863-6080</td>
+              <td>인천광역시 중구 서해대로 180번길 65 (신흥동)</td>
+              <td>판금, 도장 시설 포함</td>
+              <td>
+                <NuxtLink to="https://naver.me/5yWUdICG" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+
+            <tr>
+              <td class="text--center" rowspan="3">충청</td>
+              <td>대전 서비스센터(선인자동차)</td>
+              <td>042-226-7707</td>
+              <td>대전광역시 중구 유등천동로 624 (용두동)</td>
+              <td>판금, 도장 시설 포함</td>
+              <td>
+                <NuxtLink to="https://naver.me/GkU1v9bG" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+
+            <tr>
+              <td>천안 서비스센터(선인자동차)</td>
+              <td>041-563-0009</td>
+              <td>충청남도 천안시 동남구 천안대로 566 (구성동)</td>
+              <td>판금, 도장 시설 포함</td>
+              <td>
+                <NuxtLink to="https://naver.me/xdjbKf16" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+            <tr>
+              <td>청주 서비스센터(선인자동차)</td>
+              <td>043-288-0007</td>
+              <td>충청북도 청주시 서원구 남이면 2순환로 1778 (양촌리)</td>
+              <td></td>
+              <td>
+                <NuxtLink to="https://naver.me/5ixbrn4Z" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+
+            <tr>
+              <td class="text--center" rowspan="3">부산</td>
+              <td>부산 수영 서비스센터(선인자동차)</td>
+              <td>051-758-0046</td>
+              <td>부산광역시 수영구 수영로 562-9 (광안동)</td>
+              <td></td>
+              <td>
+                <NuxtLink to="https://naver.me/FW0QGQ2V" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+            <tr>
+              <td>부산 광안 서비스센터(선인자동차)</td>
+              <td>051-647-5222</td>
+              <td>부산광역시 수영구 광남로 114</td>
+              <td>사고 차량 정비만 해당 (예약불가)</td>
+              <td>
+                <!-- <NuxtLink to="" target="_blank"> 서비스 예약 </NuxtLink> -->
+              </td>
+            </tr>
+
+            <tr>
+              <td>부산 동래  지정 서비스센터(선인자동차)</td>
+              <td>051-558-5880</td>
+              <td>부산광역시동래구중앙대로1371</td>
+              <td>경정비 작업만 가능 </td>
+              <td>
+                <NuxtLink to="https://naver.me/5suSlqJt" target="_blank"> 서비스 예약 </NuxtLink>
+              </td>
+            </tr>
+            
+
+            <tr>
+              <td class="text--center" rowspan="1">울산</td>
+              <td>울산 서비스센터(선인자동차)</td>
+              <td>052-258-8004</td>
+              <td>울산광역시 북구 진장유통로 42-8</td>
+              <td></td>
+              <td>
+                <NuxtLink to="https://naver.me/G28rJx69" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+            <tr>              
+              <td class="text--center" rowspan="1">창원</td>
+              <td>신마산 지정 서비스센터(선인자동차)</td>
+              <td>055-221-3114</td>
+              <td>창원시 마산합포구 문화동 3길 23</td>
+              <td>경정비 작업만 가능 </td>
+              <td>
+                <NuxtLink to="https://naver.me/xR2ih6hU" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr> 
+
+            <!-- <tr>
+              <td class="text--center">경북</td>
+              <td>포항 서비스센터(프리미어모터스)</td>
+              <td>054-281-7007</td>
+              <td>경상북도 포항시 남구 연일로145번길 55-6</td>
+              <td></td>
+              <td>
+                <NuxtLink to="https://naver.me/FZ8bM66K" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr> -->
+
+            <!-- <tr>
+              <td class="text--center">경남</td>
+              <td>창원 서비스센터(프리미어모터스)</td>
+              <td>055-715-3003</td>
+              <td>경상남도 창원시 마산회원구 무역로 103</td>
+              <td>판금, 도장 시설 포함</td>
+              <td>
+                <NuxtLink to="https://naver.me/xLsdpklS" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr> -->
+
+            <tr>
+              <td class="text--center">대구</td>
+              <td>동대구 서비스센터(선인자동차)</td>
+              <td>053-963-0011</td>
+              <td>대구광역시 동구 안심로55길 22 (동호동)</td>
+              <td>판금, 도장 시설 포함</td>
+              <td>
+                <NuxtLink to="https://naver.me/x6xlmMqj" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+
+            <tr>
+              <td class="text--center">광주</td>
+              <td>광주 서비스센터(선인자동차)</td>
+              <td>062-515-1020</td>
+              <td>광주광역시 북구 서암대로 143번길 20 (신안동)</td>
+              <td>판금, 도장 시설 포함</td>
+              <td>
+                <!-- <NuxtLink to="https://naver.me/xE6O4KRn" target="_blank">
+                  서비스 예약
+                </NuxtLink> -->
+              </td>
+            </tr>
+
+            <tr>
+              <td class="text--center">전북</td>
+              <td>전주 서비스센터(선인자동차)</td>
+              <td>063-252-5400</td>
+              <td>전라북도 전주시 덕진구 기린대로 867 (팔복동2가 830-3)</td>
+              <td>판금, 도장 시설 포함</td>
+              <td>
+                <NuxtLink to="https://naver.me/FVS1pbuK" target="_blank">
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+
+            <tr>
+              <td class="text--center">강원</td>
+              <td>원주 서비스센터(더파크 모터스)</td>
+              <td>033-762-0041</td>
+              <td>강원도 원주시 치악로 1221-10 (관설동)</td>
+              <td>판금, 도장 시설 포함</td>
+              <td>
+                <NuxtLink
+                  to="https://booking.naver.com/booking/6/bizes/1283368"
+                  target="_blank"
+                >
+                  서비스 예약
+                </NuxtLink>
+              </td>
+            </tr>
+
+            <tr>
+              <td class="text--center">제주</td>
+              <td>제주 지정 서비스센터(선인자동차)</td>
+              <td>064-755-1474~5</td>
+              <td>제주특별자치도 제주시 서광로 138 (오라이동)</td>
+              <td></td>
+              <td>
+                <!-- <NuxtLink to="" target="_blank"> 서비스 예약 </NuxtLink> -->
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+
+  // 샘플 데이터
+  const breadcrumbData = {
+    mainMenu: "NETWORK",
+    currentSubMenu: "전시장 및 서비스센터",
+    subMenuItems: [
+      { label: "전시장 및 서비스 센터", to: "/ford/network", active: true },
+      // { label: "서비스 센터 찾기", to: "/ford/network", active: true },
+      { label: "포드 고객센터", to: "/ford/owner/contact", active: true },
+    ],
+  };
+</script>

+ 928 - 0
app/pages/ford/network/location.vue

@@ -0,0 +1,928 @@
+<template>
+  <div>
+    <breadCrumbs
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="container">
+      <div class="showroom--wrap" :class="{ type2: locationType === 2 }">
+        <div class="search--wrap" :class="{ 'at-bottom': isScrolledToBottom }">
+          <div class="form--wrap">
+            <div class="input--wrap mb--12">
+              <input
+                type="text"
+                v-model="searchKeyword"
+                :placeholder="searchPlaceholder"
+                @keyup.enter="searchLocations"
+              />
+              <button class="search--btn" @click="searchLocations"></button>
+            </div>
+            <div class="input--wrap">
+              <select
+                name="location"
+                id="location"
+                v-model="selectedRegion"
+                required
+                @change="searchLocations"
+              >
+                <option value="" disabled selected hidden>시/도 선택</option>
+                <option value="">전체</option>
+                <option value="서울">서울</option>
+                <option value="경기">경기</option>
+                <option value="인천">인천</option>
+                <option value="부산">부산</option>
+                <option value="대구">대구</option>
+                <option value="광주">광주</option>
+                <option value="대전">대전</option>
+                <option value="울산">울산</option>
+                <option value="세종">세종</option>
+                <option value="강원">강원</option>
+                <option value="충북">충북</option>
+                <option value="충남">충남</option>
+                <option value="전북">전북</option>
+                <option value="전남">전남</option>
+                <option value="경북">경북</option>
+                <option value="경남">경남</option>
+                <option value="제주">제주</option>
+              </select>
+              <!-- <button class="location--btn" @click="findNearbyLocations">
+                내 주변<i class="ico"></i>
+              </button> -->
+            </div>
+            <!-- <div class="btn--wrap">
+              <button class="btn--sky" @click="searchLocations">
+                {{ nearbyButtonText }}
+              </button>
+            </div> -->
+          </div>
+          <div class="list--wrap" ref="listWrapRef" @scroll="handleScroll">
+            <div v-if="isLoading" class="loading">데이터를 불러오는 중...</div>
+            <div
+              v-else-if="!filteredLocations || filteredLocations.length === 0"
+              class="empty"
+            >
+              {{ emptyMessage }}
+            </div>
+            <div
+              v-else
+              v-for="location in filteredLocations"
+              :key="location.id"
+              class="list"
+              :class="{ active: selectedLocation?.id === location.id }"
+              @click="selectLocation(location)"
+            >
+              <div class="list--title">
+                <h3>{{ location.name }}</h3>
+                <p>{{ location.branch_name || "" }}</p>
+                <p v-if="location.distance" class="distance">
+                  {{ location.distance.toFixed(1) }}km
+                </p>
+              </div>
+              <div class="list--info">
+                <div class="address">
+                  <i class="ico"></i>
+                  <p>{{ location.address }}</p>
+                </div>
+                <div class="call">
+                  <i class="ico"></i>
+                  <p>{{ location.main_phone }}</p>
+                </div>
+                <div class="time">
+                  <i class="ico"></i>
+                  <p>{{ location.business_hours }}</p>
+                </div>
+              </div>
+              <div class="list--btn" v-if="locationType === 1">
+                <NuxtLink :to="location.quote_link" target="_blank" class="btn--gray"
+                  >견적 요청</NuxtLink
+                >
+                <NuxtLink
+                  :to="location.test_drive_link"
+                  target="_blank"
+                  class="btn--black"
+                  >시승 요청</NuxtLink
+                >
+              </div>
+              <div class="list--btn" v-else>
+                <NuxtLink
+                  :to="location.service_reservation_link"
+                  target="_blank"
+                  class="btn--black"
+                  >서비스 예약</NuxtLink
+                >
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="map--wrap">
+          <div class="title--wrap">
+            <h2>{{ pageTitle }}</h2>
+            <p>{{ pageDescription }}</p>
+          </div>
+          <!-- 지도 영역 -->
+          <div id="map" class="map"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref, computed, onMounted, nextTick, watch } from "vue";
+
+  const route = useRoute();
+  const { get } = useApi();
+
+  // type 파라미터: 1=전시장, 2=서비스센터 (반응형으로 route.query 직접 참조)
+  const locationType = computed(() => {
+    const type = parseInt(route.query.type) || 1;
+    return type === 2 ? 2 : 1;
+  });
+
+  // breadcrumb도 반응형으로
+  const breadcrumbData = computed(() => ({
+    mainMenu: "NETWORK",
+    currentSubMenu: locationType.value === 2 ? "서비스센터 찾기" : "전시장 찾기",
+    subMenuItems: [
+      {
+        label: "전시장 찾기",
+        to: "/ford/network",
+        active: locationType.value === 2 ? false : true,
+      },
+      {
+        label: "서비스센터 찾기",
+        to: "/ford/network",
+        active: locationType.value === 2 ? true : false,
+      },
+      {
+        label: "포드고객센터",
+        to: "/ford/owner/contact",
+        active: false,
+      },
+    ],
+  }));
+
+  // 타입별 텍스트 설정
+  const pageTitle = computed(() =>
+    locationType.value === 1 ? "전시장 찾기" : "서비스센터 찾기"
+  );
+  const pageDescription = computed(() =>
+    locationType.value === 1
+      ? "전국의 포드 전시장을 한 눈에 찾아보실 수 있습니다."
+      : "전국의 포드 서비스센터를 한 눈에 찾아보실 수 있습니다."
+  );
+  const searchPlaceholder = computed(() =>
+    locationType.value === 1
+      ? "전시장명 또는 지역명을 입력해주세요"
+      : "서비스센터명 또는 지역명을 입력해주세요"
+  );
+  const emptyMessage = computed(() =>
+    locationType.value === 1
+      ? "등록된 전시장이 없습니다."
+      : "등록된 서비스센터가 없습니다."
+  );
+  const nearbyButtonText = computed(() =>
+    locationType.value === 1 ? "가까운 전시장 추천 지정" : "가까운 서비스센터 추천 지정"
+  );
+  const locationLabel = computed(() =>
+    locationType.value === 1 ? "전시장" : "서비스센터"
+  );
+
+  // API endpoint (public API - 인증 불필요)
+  const apiEndpoint = computed(() =>
+    locationType.value === 1 ? "/showroom/public" : "/service-center/public"
+  );
+
+  const isLoading = ref(false);
+  const locations = ref([]);
+  const searchKeyword = ref("");
+  const selectedRegion = ref("");
+  const selectedLocation = ref(null);
+  const map = ref(null);
+  const markers = ref([]);
+  const overlays = ref([]); // 카카오맵 CustomOverlay 저장
+  const clusterer = ref([]); // 커스텀 클러스터 오버레이 배열
+  const userLocation = ref(null);
+  const userLocationMarker = ref(null); // 현재 위치 마커
+  const userLocationCircle = ref(null); // 반경 원
+  const nearbyRadius = ref(5); // 주변 검색 반경 (km) - 유동적으로 변경 가능
+  const listWrapRef = ref(null);
+  const isScrolledToBottom = ref(false);
+
+  // 카카오맵 API Key
+  const KAKAO_API_KEY = "5e42d358f25c02d08a2596876459f6ca";
+  //const KAKAO_API_KEY = "47dfc47d66f488da19bdebeabbde1da7";
+  // 두 지점 간의 거리 계산 (Haversine formula)
+  const calculateDistance = (lat1, lon1, lat2, lon2) => {
+    const R = 6371; // 지구 반경 (km)
+    const dLat = ((lat2 - lat1) * Math.PI) / 180;
+    const dLon = ((lon2 - lon1) * Math.PI) / 180;
+    const a =
+      Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+      Math.cos((lat1 * Math.PI) / 180) *
+        Math.cos((lat2 * Math.PI) / 180) *
+        Math.sin(dLon / 2) *
+        Math.sin(dLon / 2);
+    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+    return R * c;
+  };
+
+  // 카카오맵 스크립트 로드
+  const loadKakaoMapsScript = () => {
+    return new Promise((resolve, reject) => {
+      if (window.kakao && window.kakao.maps) {
+        console.log("[KakaoMap] Already loaded");
+        resolve();
+        return;
+      }
+
+      const script = document.createElement("script");
+      script.src = `https://dapi.kakao.com/v2/maps/sdk.js?appkey=${KAKAO_API_KEY}&autoload=false`;
+      script.async = true;
+      script.onload = () => {
+        console.log("[KakaoMap] Script loaded, initializing...");
+        window.kakao.maps.load(() => {
+          console.log("[KakaoMap] Maps API ready");
+          resolve();
+        });
+      };
+      script.onerror = (e) => {
+        console.error("[KakaoMap] Script load error:", e);
+        reject(new Error("카카오맵 API 로드 실패"));
+      };
+      document.head.appendChild(script);
+    });
+  };
+
+  // 지도 초기화
+  const initMap = () => {
+    console.log("[KakaoMap] initMap called");
+    if (typeof window === "undefined" || !window.kakao || !window.kakao.maps) {
+      console.error("[KakaoMap] Kakao maps not available");
+      return;
+    }
+
+    const container = document.getElementById("map");
+    if (!container) {
+      console.error("[KakaoMap] Map container not found");
+      return;
+    }
+
+    console.log("[KakaoMap] Creating map...", container);
+
+    // 서울 중심으로 지도 생성
+    const options = {
+      center: new window.kakao.maps.LatLng(37.5665, 126.978),
+      level: 8, // 카카오맵 줌 레벨 (숫자가 작을수록 확대)
+    };
+
+    map.value = new window.kakao.maps.Map(container, options);
+    console.log("[KakaoMap] Map created successfully");
+  };
+
+  // 모든 마커 및 오버레이 제거
+  const clearAllMarkers = () => {
+    // 오버레이 먼저 제거
+    overlays.value.forEach((overlay) => overlay.setMap(null));
+    overlays.value = [];
+
+    // 클러스터 오버레이 제거
+    if (clusterer.value && clusterer.value.length) {
+      clusterer.value.forEach((c) => c.setMap(null));
+    }
+    clusterer.value = [];
+
+    // 마커 제거
+    markers.value.forEach((marker) => marker.setMap(null));
+    markers.value = [];
+
+    // 현재 위치 마커 제거
+    if (userLocationMarker.value) {
+      userLocationMarker.value.setMap(null);
+      userLocationMarker.value = null;
+    }
+
+    // 반경 원 제거
+    if (userLocationCircle.value) {
+      userLocationCircle.value.setMap(null);
+      userLocationCircle.value = null;
+    }
+  };
+
+  // 툴팁 콘텐츠 생성
+  const createInfoWindowContent = (location) => {
+    // 버튼 영역 생성
+    let buttonsHtml = "";
+    if (locationType.value === 1) {
+      // 전시장: 견적요청, 시승요청
+      buttonsHtml = `
+        <div style="display: flex; gap: 8px; margin-top: 12px;">
+          ${
+            location.quote_link
+              ? `<a href="${location.quote_link}" target="_blank" style="flex: 1; padding: 8px 12px; background: #6b7280; color: white; text-decoration: none; text-align: center; border-radius: 4px; font-size: 12px;">견적 요청</a>`
+              : ""
+          }
+          ${
+            location.test_drive_link
+              ? `<a href="${location.test_drive_link}" target="_blank" style="flex: 1; padding: 8px 12px; background: #1a1a1a; color: white; text-decoration: none; text-align: center; border-radius: 4px; font-size: 12px;">시승 요청</a>`
+              : ""
+          }
+        </div>
+      `;
+    } else {
+      // 서비스센터: 서비스예약
+      buttonsHtml = location.service_reservation_link
+        ? `
+        <div style="margin-top: 12px;">
+          <a href="${location.service_reservation_link}" target="_blank" style="display: block; padding: 8px 12px; background: #1a1a1a; color: white; text-decoration: none; text-align: center; border-radius: 4px; font-size: 12px;">서비스 예약</a>
+        </div>
+      `
+        : "";
+    }
+
+    // 전시장일 때만 로고 표시 (닫기 버튼 포함)
+    const logoHtml =
+      locationType.value === 1
+        ? `
+      <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #eee;">
+        <img src="/img/logo--gate1.svg" alt="Ford" style="height: 24px; width: auto;" />
+        <button class="close-btn" style="background: none; border: none; cursor: pointer; font-size: 18px; color: #999; padding: 0; line-height: 1;">&times;</button>
+      </div>
+    `
+        : `
+      <div style="display: flex; justify-content: flex-end; margin-bottom: 8px;">
+        <button class="close-btn" style="background: none; border: none; cursor: pointer; font-size: 18px; color: #999; padding: 0; line-height: 1;">&times;</button>
+      </div>
+    `;
+
+    return `
+      <div class="kakao-infowindow" style="padding: 12px; min-width: 220px; max-width: 280px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
+        ${logoHtml}
+        <div>
+          <h3 style="margin: 0 0 4px 0; font-size: 15px; font-weight: bold; color: #1a1a1a;">${
+            location.name
+          }</h3>
+          <p style="margin: 0 0 10px 0; font-size: 12px; color: #666;">${
+            location.branch_name || ""
+          }</p>
+        </div>
+        <div style="font-size: 13px; color: #333; line-height: 1.6;">
+          <div style="display: flex; align-items: flex-start; margin-bottom: 6px;">
+            <span style="margin-right: 8px;">📍</span>
+            <span>${location.address}</span>
+          </div>
+          <div style="display: flex; align-items: center; margin-bottom: 6px;">
+            <span style="margin-right: 8px;">📞</span>
+            <a href="tel:${
+              location.main_phone
+            }" style="color: #333; text-decoration: none;">${location.main_phone}</a>
+          </div>
+          ${
+            location.business_hours
+              ? `
+          <div style="display: flex; align-items: flex-start;">
+            <span style="margin-right: 8px;">🕐</span>
+            <span style="white-space: pre-line;">${location.business_hours}</span>
+          </div>
+          `
+              : ""
+          }
+        </div>
+        ${buttonsHtml}
+      </div>
+      <div style="position: absolute; bottom: -10px; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 10px solid transparent; border-right: 10px solid transparent; border-top: 10px solid white;"></div>
+    `;
+  };
+
+  // 픽셀 거리 계산 (두 지점 간)
+  const getPixelDistance = (pos1, pos2) => {
+    const proj = map.value.getProjection();
+    const point1 = proj.pointFromCoords(pos1);
+    const point2 = proj.pointFromCoords(pos2);
+    return Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
+  };
+
+  // 클러스터 오버레이 생성
+  const createClusterOverlay = (positions, count) => {
+    // 클러스터 중심점 계산
+    let sumLat = 0,
+      sumLng = 0;
+    positions.forEach((pos) => {
+      sumLat += pos.getLat();
+      sumLng += pos.getLng();
+    });
+    const centerLat = sumLat / positions.length;
+    const centerLng = sumLng / positions.length;
+    const centerPosition = new window.kakao.maps.LatLng(centerLat, centerLng);
+
+    // 클러스터 표시 콘텐츠
+    const content = document.createElement("div");
+    content.innerHTML = `
+      <div style="
+        width: 40px;
+        height: 40px;
+        background: #00095B;
+        border: 3px solid white;
+        border-radius: 50%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        color: white;
+        font-weight: bold;
+        font-size: 14px;
+        box-shadow: 0 2px 6px rgba(0,0,0,0.3);
+        cursor: pointer;
+      ">${count}</div>
+    `;
+
+    // 클러스터 클릭 시 확대
+    content.addEventListener("click", () => {
+      map.value.setCenter(centerPosition);
+      const currentLevel = map.value.getLevel();
+      map.value.setLevel(currentLevel - 2);
+    });
+
+    const overlay = new window.kakao.maps.CustomOverlay({
+      content: content,
+      position: centerPosition,
+      xAnchor: 0.5,
+      yAnchor: 0.5,
+      zIndex: 2,
+    });
+
+    return overlay;
+  };
+
+  // 마커 클러스터링 업데이트
+  const updateClustering = () => {
+    if (!map.value || !window.kakao) return;
+
+    const level = map.value.getLevel();
+    const clusterThreshold = 60; // 클러스터링 픽셀 거리
+
+    // 기존 클러스터 제거
+    if (clusterer.value) {
+      clusterer.value.forEach((c) => c.setMap(null));
+    }
+    clusterer.value = [];
+
+    // 줌 레벨이 낮으면 (확대된 상태) 클러스터링 안함
+    if (level <= 6) {
+      markers.value.forEach((marker) => marker.setMap(map.value));
+      return;
+    }
+
+    // 마커 그룹핑
+    const assigned = new Set();
+    const clusters = [];
+
+    markers.value.forEach((marker, i) => {
+      if (assigned.has(i)) return;
+
+      const group = [marker];
+      const positions = [marker.getPosition()];
+      assigned.add(i);
+
+      markers.value.forEach((otherMarker, j) => {
+        if (i === j || assigned.has(j)) return;
+
+        const distance = getPixelDistance(
+          marker.getPosition(),
+          otherMarker.getPosition()
+        );
+        if (distance < clusterThreshold) {
+          group.push(otherMarker);
+          positions.push(otherMarker.getPosition());
+          assigned.add(j);
+        }
+      });
+
+      clusters.push({ markers: group, positions: positions });
+    });
+
+    // 클러스터 또는 개별 마커 표시
+    clusters.forEach((cluster) => {
+      if (cluster.markers.length > 1) {
+        // 클러스터로 표시
+        cluster.markers.forEach((m) => m.setMap(null));
+        const clusterOverlay = createClusterOverlay(
+          cluster.positions,
+          cluster.markers.length
+        );
+        clusterOverlay.setMap(map.value);
+        clusterer.value.push(clusterOverlay);
+      } else {
+        // 개별 마커 표시
+        cluster.markers[0].setMap(map.value);
+      }
+    });
+  };
+
+  // 마커 생성
+  const createMarkers = () => {
+    if (!map.value || !window.kakao) return;
+
+    // 기존 오버레이 제거
+    overlays.value.forEach((overlay) => overlay.setMap(null));
+    overlays.value = [];
+
+    // 기존 클러스터 제거
+    if (clusterer.value) {
+      clusterer.value.forEach((c) => c.setMap(null));
+    }
+    clusterer.value = [];
+
+    // 기존 마커 제거
+    markers.value.forEach((marker) => marker.setMap(null));
+    markers.value = [];
+
+    // 마커 생성 (CustomOverlay로 로고 마커 적용)
+    filteredLocations.value.forEach((location) => {
+      if (location.latitude && location.longitude) {
+        const position = new window.kakao.maps.LatLng(
+          parseFloat(location.latitude),
+          parseFloat(location.longitude)
+        );
+
+        // 커스텀 마커 HTML (타원형 안에 로고)
+        const markerWrapper = document.createElement("div");
+        markerWrapper.innerHTML = `
+          <div class="ford-marker" style="
+            width: 90px;
+            height: 40px;
+            // background: #00095B;
+            // border: 2px solid white;
+            border-radius: 20px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            cursor: pointer;
+            box-shadow: 0 2px 6px rgba(0,0,0,0.3);
+          ">
+            <img src="/img/logo--gate1.svg" alt="Ford" style="width: 70px; height: auto; display: block;" />
+          </div>
+        `;
+
+        // 정보창 오버레이 생성
+        const overlayContent = document.createElement("div");
+        overlayContent.innerHTML = createInfoWindowContent(location);
+        overlayContent.style.position = "relative";
+        overlayContent.style.bottom = "45px";
+
+        const overlay = new window.kakao.maps.CustomOverlay({
+          content: overlayContent,
+          position: position,
+          xAnchor: 0.5,
+          yAnchor: 1,
+          zIndex: 3,
+        });
+
+        // 닫기 버튼 이벤트
+        const closeBtn = overlayContent.querySelector(".close-btn");
+        if (closeBtn) {
+          closeBtn.addEventListener("click", (e) => {
+            e.stopPropagation();
+            overlay.setMap(null);
+          });
+        }
+
+        // 마커 클릭 이벤트 핸들러
+        markerWrapper.onclick = () => {
+          // 다른 오버레이 닫기
+          overlays.value.forEach((ov) => ov.setMap(null));
+          // 현재 오버레이 열기
+          overlay.setMap(map.value);
+          selectLocation(location);
+        };
+
+        // 마커 생성 (CustomOverlay)
+        const marker = new window.kakao.maps.CustomOverlay({
+          position: position,
+          content: markerWrapper,
+          yAnchor: 0.5,
+          xAnchor: 0.5,
+        });
+
+        marker.overlay = overlay;
+        markers.value.push(marker);
+        overlays.value.push(overlay);
+      }
+    });
+
+    // 클러스터링 적용
+    updateClustering();
+
+    // 줌 변경 시 클러스터링 업데이트 (기존 리스너 제거 후 추가)
+    window.kakao.maps.event.removeListener(map.value, "zoom_changed", updateClustering);
+    window.kakao.maps.event.addListener(map.value, "zoom_changed", updateClustering);
+  };
+
+  // 목록 불러오기
+  const loadLocations = async () => {
+    isLoading.value = true;
+
+    // 기존 마커 모두 제거
+    clearAllMarkers();
+
+    // 지도 새로 초기화 (서울 중심)
+    if (window.kakao && window.kakao.maps) {
+      const container = document.getElementById("map");
+      if (container) {
+        const options = {
+          center: new window.kakao.maps.LatLng(37.5665, 126.978),
+          level: 8,
+        };
+        map.value = new window.kakao.maps.Map(container, options);
+      }
+    }
+
+    const { data } = await get(apiEndpoint.value);
+
+    if (data?.success && data?.data) {
+      // public API는 배열을 직접 반환
+      locations.value = Array.isArray(data.data) ? data.data : data.data.items || [];
+
+      // 마커 생성
+      await nextTick();
+      createMarkers();
+    }
+
+    isLoading.value = false;
+  };
+
+  // 필터링된 목록
+  const filteredLocations = computed(() => {
+    let result = locations.value;
+
+    // 내 주변 필터 (우선순위 최상위)
+    if (userLocation.value) {
+      result = result.filter((location) => {
+        if (!location.latitude || !location.longitude) return false;
+        const distance = calculateDistance(
+          userLocation.value.lat,
+          userLocation.value.lng,
+          parseFloat(location.latitude),
+          parseFloat(location.longitude)
+        );
+        return distance <= nearbyRadius.value;
+      });
+
+      // 거리순으로 정렬
+      result = result
+        .map((location) => {
+          const distance = calculateDistance(
+            userLocation.value.lat,
+            userLocation.value.lng,
+            parseFloat(location.latitude),
+            parseFloat(location.longitude)
+          );
+          return { ...location, distance };
+        })
+        .sort((a, b) => a.distance - b.distance);
+    }
+
+    // 지역 필터
+    if (selectedRegion.value) {
+      result = result.filter((location) =>
+        location.address?.includes(selectedRegion.value)
+      );
+    }
+
+    // 검색어 필터
+    if (searchKeyword.value) {
+      const keyword = searchKeyword.value.toLowerCase();
+      result = result.filter(
+        (location) =>
+          location.name?.toLowerCase().includes(keyword) ||
+          location.address?.toLowerCase().includes(keyword) ||
+          location.branch_name?.toLowerCase().includes(keyword)
+      );
+    }
+
+    return result;
+  });
+
+  // 선택
+  const selectLocation = (location) => {
+    selectedLocation.value = location;
+
+    if (map.value && location.latitude && location.longitude && window.kakao) {
+      // 지도 중심 이동 및 줌인
+      const position = new window.kakao.maps.LatLng(
+        parseFloat(location.latitude),
+        parseFloat(location.longitude)
+      );
+      map.value.setCenter(position);
+      map.value.setLevel(3); // 확대
+
+      // 해당 마커의 오버레이 열기
+      const targetLat = parseFloat(location.latitude);
+      const targetLng = parseFloat(location.longitude);
+      const marker = markers.value.find((m) => {
+        const pos = m.getPosition();
+        // 부동소수점 비교를 위해 근사값 비교
+        return (
+          Math.abs(pos.getLat() - targetLat) < 0.0001 &&
+          Math.abs(pos.getLng() - targetLng) < 0.0001
+        );
+      });
+      if (marker && marker.overlay) {
+        // 다른 오버레이 닫기
+        overlays.value.forEach((ov) => ov.setMap(null));
+        marker.overlay.setMap(map.value);
+      }
+    }
+  };
+
+  // 검색
+  const searchLocations = () => {
+    // 내 주변 필터 해제
+    userLocation.value = null;
+
+    // computed에서 자동으로 필터링되므로 별도 처리 불필요
+    // 검색 후 마커 업데이트
+    nextTick(() => {
+      createMarkers();
+    });
+  };
+
+  // 내 주변 찾기
+  const findNearbyLocations = () => {
+    if (!navigator.geolocation) {
+      alert("이 브라우저는 위치 정보를 지원하지 않습니다.");
+      return;
+    }
+
+    isLoading.value = true;
+
+    navigator.geolocation.getCurrentPosition(
+      (position) => {
+        userLocation.value = {
+          lat: position.coords.latitude,
+          lng: position.coords.longitude,
+        };
+
+        // 지도 중심을 현재 위치로 이동
+        if (map.value && window.kakao) {
+          const currentPosition = new window.kakao.maps.LatLng(
+            userLocation.value.lat,
+            userLocation.value.lng
+          );
+          map.value.setCenter(currentPosition);
+          map.value.setLevel(5);
+
+          // 기존 현재 위치 마커/원 제거
+          if (userLocationMarker.value) {
+            userLocationMarker.value.setMap(null);
+          }
+          if (userLocationCircle.value) {
+            userLocationCircle.value.setMap(null);
+          }
+
+          // 현재 위치 마커 추가 (파란색 원)
+          const markerContent = `
+            <div style="width: 20px; height: 20px; background: #4285F4; border: 3px solid white; border-radius: 50%; box-shadow: 0 2px 6px rgba(0,0,0,0.3);"></div>
+          `;
+          userLocationMarker.value = new window.kakao.maps.CustomOverlay({
+            content: markerContent,
+            position: currentPosition,
+            xAnchor: 0.5,
+            yAnchor: 0.5,
+          });
+          userLocationMarker.value.setMap(map.value);
+
+          // 반경 원 표시
+          userLocationCircle.value = new window.kakao.maps.Circle({
+            center: currentPosition,
+            radius: nearbyRadius.value * 1000, // km를 m로 변환
+            strokeWeight: 2,
+            strokeColor: "#4285F4",
+            strokeOpacity: 0.8,
+            strokeStyle: "solid",
+            fillColor: "#4285F4",
+            fillOpacity: 0.15,
+          });
+          userLocationCircle.value.setMap(map.value);
+        }
+
+        // 마커 업데이트
+        nextTick(() => {
+          createMarkers();
+        });
+
+        isLoading.value = false;
+
+        const nearbyCount = filteredLocations.value.length;
+        if (nearbyCount === 0) {
+          alert(`주변 ${nearbyRadius.value}km 이내에 ${locationLabel.value}이 없습니다.`);
+        } else {
+          alert(
+            `주변 ${nearbyRadius.value}km 이내에 ${nearbyCount}개의 ${locationLabel.value}을 찾았습니다.`
+          );
+        }
+      },
+      (error) => {
+        isLoading.value = false;
+
+        let errorMessage = "위치 정보를 가져올 수 없습니다.";
+        if (error.code === 1) {
+          errorMessage =
+            "위치 정보 접근이 거부되었습니다. 브라우저 설정에서 위치 정보 권한을 허용해주세요.";
+        } else if (error.code === 2) {
+          errorMessage = "위치 정보를 사용할 수 없습니다.";
+        } else if (error.code === 3) {
+          errorMessage = "위치 정보 요청 시간이 초과되었습니다.";
+        }
+
+        alert(errorMessage);
+      },
+      {
+        enableHighAccuracy: true,
+        timeout: 10000,
+        maximumAge: 0,
+      }
+    );
+  };
+
+  // 스크롤 이벤트 핸들러
+  const handleScroll = () => {
+    const el = listWrapRef.value;
+    if (el) {
+      const threshold = 10;
+      isScrolledToBottom.value =
+        el.scrollTop + el.clientHeight >= el.scrollHeight - threshold;
+    }
+  };
+
+  // 검색어나 지역 변경시 내 주변 필터 해제
+  watch([searchKeyword, selectedRegion], () => {
+    if (userLocation.value) {
+      userLocation.value = null;
+    }
+  });
+
+  // type이 변경되면 데이터 다시 로드 및 지도 리셋
+  watch(
+    () => route.query.type,
+    (newType, oldType) => {
+      // 실제로 타입이 변경된 경우에만 실행
+      if (newType === oldType) return;
+
+      // 데이터 초기화
+      locations.value = [];
+      selectedLocation.value = null;
+      searchKeyword.value = "";
+      selectedRegion.value = "";
+      userLocation.value = null;
+
+      // 모든 마커 제거
+      clearAllMarkers();
+
+      // 지도 리셋 (서울 중심으로)
+      if (map.value && window.kakao) {
+        const center = new window.kakao.maps.LatLng(37.5665, 126.978);
+        map.value.setCenter(center);
+        map.value.setLevel(8);
+      }
+
+      // 데이터 다시 로드
+      loadLocations();
+    }
+  );
+
+  // 페이지 로드 시 위치 권한 요청 (권한만 요청, 필터는 적용하지 않음)
+  const requestLocationPermission = () => {
+    if (!navigator.geolocation) {
+      return;
+    }
+    // 위치 권한만 요청하고 userLocation은 설정하지 않음
+    navigator.geolocation.getCurrentPosition(
+      () => {
+        // 권한 허용됨 - 나중에 "내 주변" 클릭 시 사용
+      },
+      () => {
+        // 권한 거부되어도 페이지는 정상 동작
+      },
+      { enableHighAccuracy: false, timeout: 10000, maximumAge: 300000 }
+    );
+  };
+
+  onMounted(async () => {
+    console.log("[KakaoMap] onMounted");
+
+    // 페이지 로드 시 위치 권한 요청
+    requestLocationPermission();
+
+    try {
+      await loadKakaoMapsScript();
+      await nextTick(); // DOM이 렌더링될 때까지 대기
+      initMap();
+    } catch (error) {
+      console.error("[KakaoMap] Load error:", error);
+      // 지도 로드 실패해도 목록은 표시
+    } finally {
+      await loadLocations();
+    }
+  });
+</script>

+ 194 - 0
app/pages/ford/owner/accident.vue

@@ -0,0 +1,194 @@
+<template>
+  <div>
+    <breadCrumbs
+      :width="219"
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="ovwner--wrapper pb--0">
+      <div class="inner--wrap">
+        <div class="title--visual">
+          <h2>사고 수리를 위한 포드 서비스 부품</h2>
+          <div class="desc mt--40">
+            충돌사고를 겪고 싶은 사람은 아무도 없을 겁니다. <br />하지만 만에 하나 사고가
+            발생할 경우, 포드의 노력은 귀하의 차량이 다시 주행 가능한 상태가 되도록
+            도와드리는 데에서 그치지 않습니다. <br />포드는 철저한 설계의 ‘포드 서비스
+            부품’을 사용해 귀하의 차량이 완벽하게 포드다운 성능을 발휘할 수 있도록 하고
+            있습니다.
+          </div>
+        </div>
+        <div class="thums--wrap mt--70">
+          <img src="/img/owner/acc--visual1.png" alt="" />
+        </div>
+      </div>
+      <div class="consumable--parts--wrap">
+        <div class="inner--wrap">
+          <div class="prm--service pt--120">
+            <h2>올바른 부품</h2>
+            <div class="sub--title">
+              장기적인 차량 성능 및 운전자의 안심을 위해 올바른 부품 사용은 매우
+              중요합니다.
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="service--card--wrap mt--100">
+        <div class="img--wrap">
+          <img src="/img/owner/acc--visual2.png" alt="" />
+        </div>
+        <div class="desc--wrap">
+          <h3>올바른 정비소 선택</h3>
+          <p>
+            사고 수리를 위해서 어느 서비스센터를 선택할지는 귀하가 결정하실 수 있습니다.
+          </p>
+          <ul>
+            <li>
+              보험사에서는 귀하에게 특정 서비스센터를 이용하도록 강요할 수 없습니다.
+            </li>
+            <li>귀하에게는 새 제품의 포드 서비스 부품을 선택할 권리가 있습니다.</li>
+            <li>
+              수리를 시작하기 전에 직접 수리 부품 및 작업 내용을 확인하고 수리에
+              동의하셔야 합니다.
+            </li>
+            <li>
+              포드 차량에 대한 교육 수료 및 자격을 인증 받은 전문기술자를 보유한 포드
+              공식서비스센터를 선택하십시오.
+            </li>
+          </ul>
+        </div>
+      </div>
+      <div class="owner--inner--content mt--120">
+        <div class="desc--wrapper">
+          <h2>차별화된 품질의 포드 서비스 부품</h2>
+          <div class="captions">
+            귀하의 차량에 장착된 포드 서비스 부품은 오랜 시간 믿고 사용할 수 있는 높은
+            신뢰성을 제공합니다.
+          </div>
+        </div>
+      </div>
+      <div class="tfs--drop--menus pt--80 pb--60">
+        <ul>
+          <li :class="{ open: dropMenuOpen[0] }">
+            <div class="title" @click="toggleDropMenu(0)">휠</div>
+            <div class="drop--contents">
+              <div class="drop--part--wrap">
+                <div class="img--wrap">
+                  <img src="/img/owner/img--accident1.png" alt="휠" />
+                </div>
+                <div class="desc--wrap">
+                  <ul>
+                    <li>
+                      모든 새 부품은 철저한 시험을 거쳐 까다로운 안전 기준을 충족해야만
+                      포드 차량에 장착될 수 있습니다. 이러한 부품 시험은 독립적으로
+                      수행됩니다. <br />포드에서는 차량에 부품을 장착하고 제대로
+                      작동하는지 시험합니다.
+                    </li>
+                    <li>
+                      휠을 임의로 수리하면 금속의 강도가 약화되며 휠이나 타이어의 손상을
+                      유발할 수 있습니다.<br />이는 향후 사고 발생의 원인이 될 수
+                      있습니다.
+                    </li>
+                    <li>
+                      휠은 차량의 중량을 지탱하는 역할을 합니다. 휠에 금이 가거나 휘는
+                      경우, 차량 주행 중 발생하는 스트레스로 인해 이러한 손상의 정도가 더
+                      커질 수도 있습니다.
+                    </li>
+                  </ul>
+                </div>
+              </div>
+            </div>
+          </li>
+          <li :class="{ open: dropMenuOpen[1] }">
+            <div class="title" @click="toggleDropMenu(1)">전면 유리</div>
+            <div class="drop--contents">
+              <div class="drop--part--wrap">
+                <div class="img--wrap">
+                  <img src="/img/owner/img--accident2.png" alt="전면 유리" />
+                </div>
+                <div class="desc--wrap">
+                  <ul>
+                    <li>
+                      전면 유리는 차량의 구조적 구성요소에 해당합니다.<br />전면 유리가
+                      완벽하게 조립되어야만 포드의 엔지니어들이 설계 시 의도한 대로의
+                      성능을 발휘할 수 있습니다.
+                    </li>
+                    <li>
+                      포드 서비스 부품의 전면 유리는 포드 차량에 정확하게 조립되도록
+                      설계됩니다.
+                    </li>
+                    <li>
+                      균열 또는 파손은 충돌사고에서 운전자와 탑승자를 보호하기 위한 전면
+                      유리의 강도를 떨어뜨립니다.
+                    </li>
+                  </ul>
+                </div>
+              </div>
+            </div>
+          </li>
+          <li :class="{ open: dropMenuOpen[2] }">
+            <div class="title" @click="toggleDropMenu(2)">판금</div>
+            <div class="drop--contents">
+              <div class="drop--part--wrap">
+                <div class="img--wrap">
+                  <img src="/img/owner/img--accident3.png" alt="판금" />
+                </div>
+                <div class="desc--wrap">
+                  <ul>
+                    <li>
+                      판금 손상은 단순히 미관상의 문제만 초래하는 것이 아닙니다.<br />판금은
+                      문, 천장, 범퍼 등 차량 내부의 최첨단 부품과 시스템을 보호하는 역할을
+                      합니다.
+                    </li>
+                    <li>
+                      포드 서비스센터에서는 덴트 복원만으로 충분한지 확인하기 위해 판금
+                      손상 정도를 검사합니다.
+                    </li>
+                    <li>
+                      포드가 생산한 알루미늄 자재는 고강도의 알루미늄이며, 알루미늄 자재
+                      수리가 가능한 포드 공식 서비스센터는 여기에서 확인하실 수 있습니다.
+                    </li>
+                  </ul>
+                </div>
+              </div>
+            </div>
+          </li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+
+  // 드롭메뉴 관련 (기본 모두 열림)
+  const dropMenuOpen = ref([true, true, true]);
+
+  const toggleDropMenu = (index) => {
+    dropMenuOpen.value[index] = !dropMenuOpen.value[index];
+  };
+
+  // 샘플 데이터
+  const breadcrumbData = {
+    mainMenu: "OWNER",
+    currentSubMenu: "서비스",
+    subMenuItems: [
+      { label: "공인 서비스", to: "/ford/owner", active: false },
+      {
+        label: "소모성 부품 무상 교환 서비스",
+        to: "/ford/owner/consumableParts",
+        active: true,
+      },
+      { label: "24시간 긴급 출동 서비스", to: "/ford/owner/tfService", active: false },
+      { label: "순정 부품", to: "/ford/owner/genuine", active: false },
+      { label: "리콜 안내", to: "/ford/owner/recall", active: false },
+      {
+        label: "사고 수리를 위한 포드 서비스 부품",
+        to: "/ford/owner/accident",
+        active: false,
+      },
+      { label: "내비게이션", to: "/ford/owner/navigation", active: false },
+    ],
+  };
+</script>

+ 1110 - 0
app/pages/ford/owner/consumableParts.vue

@@ -0,0 +1,1110 @@
+<template>
+  <div>
+    <breadCrumbs
+      width="220"
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="ovwner--wrapper type--2 pb--0">
+      <div class="inner--wrap">
+        <div class="title--visual">
+          <h2>소모성 부품 무상교환 서비스</h2>
+          <div class="desc mt--40">
+            예기치 못한 수리로 과다한 비용을 지출하지 않아도 됩니다.<br />
+            포드 소모성 부품 무상교환 서비스가 귀하의 차량을 보호해 드립니다.<br />
+            이 서비스는 일반 소모품을 포함한 정비 서비스를 제공합니다.<br />
+            모든 교체 작업은 출고 시 장착된 부품을 기준으로 이루어지며 정비 비용이
+            높아지는 것을 사전에 방지할 수 있습니다.<br />
+            약간의 비용으로 미리 보증 적용 범위를 확대할 수도 있기 때문에 차량 관리에 대해
+            걱정하지 않으셔도 됩니다.<br />
+            차량 구입 시 기본으로 적용되는 프로그램은 EMP(Essential Maintenance
+            Program)입니다.<br />
+          </div>
+        </div>
+        <div class="thums--wrap mt--70">
+          <img src="/img/owner/cons-visual01.jpg" alt="" />
+        </div>
+      </div>
+
+      <div class="consumable--parts--wrap">
+        <div class="inner--wrap">
+          <div class="prm--service pt--180">
+            <h2>프리미엄 정비 서비스 (Premium Maintenance Upgrade Plan, PMP Upgrade)</h2>
+            <div class="sub--title">
+              정기 검사, 주기적인 점검, 예방 조치, 주기적인 교체가 필요한 일반 소모품의
+              교체가 기본 프로그램보다 광범위하게 포함되는<br />
+              업그레이드 서비스 입니다. 사용자 설명서의 권장 서비스 주기를 따르십시오.
+            </div>
+            <ul>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <path
+                      d="M29.83 30.859L35.307 30.8009C35.1715 33.5117 32.9265 34.6735 29.83 35.2156M29.83 39.6885C34.3393 39.6885 39.1583 39.5723 41.2485 39.2431C45.6804 38.5654 48.9124 37.0938 48.8931 34.5767C48.8737 32.0788 48.9512 29.9102 48.8737 27.4705C48.8737 23.3462 45.5062 18.4086 42.4484 15.7365M42.4484 15.7365C40.0292 17.4598 34.9973 18.641 29.83 19.2219M42.4484 15.7365C41.3066 12.8902 38.4616 7.06194 37.7455 5.90016C35.8876 2.91827 28.3398 2.957 23.8498 3.01509C19.4372 2.97636 12.0829 2.93764 10.0508 5.74526M45.5837 28.7097C43.687 28.2837 37.3585 31.1107 39.4099 33.1051M37.3972 39.5529V45.265H45.5837V38.6429M41.3646 12.5223C42.5839 10.7215 47.4803 9.28868 47.9835 11.1088C48.8931 15.0395 45.6998 16.511 43.7451 17.0145"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M2 8.39844H27.0627V46.5821H2V8.39844Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M5.52246 12.3477H11.9091V18.7374H5.52246V12.3477ZM5.52246 23.4232H11.9091V29.813H5.52246V23.4232ZM5.52246 34.4988H11.9091V40.8886H5.52246V34.4988Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M14.6567 15.0207L16.3985 17.2281L23.5787 11.7871M14.6567 25.4573L16.3985 27.6647L23.5787 22.2237M14.6567 35.8939L16.3985 38.1013L23.5787 32.6603"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                  </svg>
+                </div>
+                <div class="captions">
+                  소지하고 계신 기본 서비스 플랜(EMP)에 대해<br />
+                  업그레이드 후 이용가능한 서비스입니다.
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <path
+                      d="M19.0535 18.4549L24.5728 21.5626L23.2553 23.7675C24.6262 25.139 25.6054 26.8231 26.1218 28.6113H28.739V34.8267H26.1218C25.8547 35.7121 25.4808 36.5802 25.0001 37.4309C24.5016 38.2643 23.914 39.0282 23.2553 39.6879L24.5728 41.8928L19.0535 45.0005L17.736 42.7782C15.8843 43.2296 13.9081 43.247 12.003 42.7782L10.6855 44.9831L5.16619 41.8754L6.4837 39.6705C5.11277 38.299 4.13354 36.6149 3.61722 34.8267H1V28.6113H3.61722C3.86648 27.7259 4.24037 26.8578 4.73889 26.0071C5.2374 25.1737 5.82494 24.4098 6.4837 23.7501L5.16619 21.5452L10.6855 18.4375L12.003 20.6598C13.8546 20.2084 15.8309 20.191 17.7538 20.6598L19.0535 18.4549ZM11.6647 37.1705C14.7449 38.9066 18.6618 37.8823 20.4422 34.8788C22.2226 31.8753 21.1544 28.0557 18.0921 26.3196C15.0119 24.5835 11.095 25.6078 9.31457 28.6113C7.53415 31.6148 8.6024 35.4517 11.6647 37.1705Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M31.0713 6.38892L36.3591 5L36.9467 7.1181C38.6381 7.13546 40.2761 7.58686 41.7004 8.36812L43.285 6.82295L47.1485 10.5904L45.5639 12.1356C45.9556 12.83 46.2761 13.5766 46.4897 14.3752C46.7034 15.1738 46.828 15.9724 46.828 16.7711L49.0001 17.344L47.5758 22.5003L45.4037 21.9274C44.5669 23.299 43.374 24.4795 41.9141 25.3303L42.5016 27.4483L37.2137 28.8373L36.6262 26.7192C34.9348 26.7018 33.2968 26.2504 31.8725 25.4691L30.2879 27.0143L26.4244 23.2469L28.009 21.7017C27.6173 21.0073 27.2968 20.2607 27.0831 19.4621C26.8695 18.6635 26.7449 17.8648 26.7449 17.0662L24.5728 16.4933L25.9971 11.3369L28.1692 11.9099C29.006 10.5383 30.1989 9.35772 31.6588 8.50701L31.0713 6.38892ZM38.2286 22.1184C41.1663 21.3545 42.9111 18.403 42.1277 15.5384C41.3443 12.6738 38.3176 10.9723 35.3799 11.7362C32.4422 12.5001 30.6974 15.4516 31.4808 18.3336C32.2642 21.1982 35.2909 22.8997 38.2286 22.1357V22.1184Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                  </svg>
+                </div>
+                <div class="captions">
+                  브레이크 패드/와이퍼의 경우 업그레이드<br />
+                  플랜에 한하여 교환이 필요할 경우 점검 후<br />
+                  각 2회/1회까지 교환 가능합니다.
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <path
+                      d="M15.063 3H34.5069"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-miterlimit="10"
+                      stroke-linecap="round"
+                    />
+                    <path
+                      d="M19.7677 3L1.59473 28.821"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-miterlimit="10"
+                      stroke-linecap="round"
+                    />
+                    <path
+                      d="M29.5459 3L47.7189 28.821"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-miterlimit="10"
+                      stroke-linecap="round"
+                    />
+                    <path
+                      d="M47.6265 42.0898H2.37348C1.61493 42.0898 1 42.7087 1 43.4722V45.6179C1 46.3814 1.61493 47.0003 2.37348 47.0003H47.6265C48.3851 47.0003 49 46.3814 49 45.6179V43.4722C49 42.7087 48.3851 42.0898 47.6265 42.0898Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M14.2018 40.6226C16.0076 40.6226 17.4715 39.1493 17.4715 37.3318C17.4715 35.5144 16.0076 34.041 14.2018 34.041C12.396 34.041 10.9321 35.5144 10.9321 37.3318C10.9321 39.1493 12.396 40.6226 14.2018 40.6226Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M34.1276 40.6226C35.9334 40.6226 37.3973 39.1493 37.3973 37.3318C37.3973 35.5144 35.9334 34.041 34.1276 34.041C32.3218 34.041 30.8579 35.5144 30.8579 37.3318C30.8579 39.1493 32.3218 40.6226 34.1276 40.6226Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M30.8575 37.332H17.4712"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M10.9424 37.3345H8.0417C8.0417 37.3345 4.84375 37.7368 4.84375 33.7239C4.84375 30.3918 7.02697 27.9572 9.21018 27.3383C11.3934 26.7193 12.3466 26.8741 15.4831 23.9649C15.4831 23.9649 21.0692 18.4355 29.6279 18.4355C38.1865 18.4355 41.7739 25.0378 42.5017 26.7812C43.2294 28.5246 44.0186 31.2377 44.0186 34.0437C44.0186 36.8496 42.9014 37.3242 41.2717 37.3345C39.642 37.3448 37.3972 37.3345 37.3972 37.3345"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M14.5913 27.8945H39.2524"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M25.2412 27.8945V37.344"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M7.01672 29.0195C7.01672 29.0195 9.29218 30.5979 8.2672 32.6198C7.24221 34.6418 5.59199 33.9609 4.84375 34.0434"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M21.4277 24.5518C21.4277 24.5518 24.0619 21.3848 29.3611 21.3848C34.6603 21.3848 35.7775 24.7787 35.7775 24.7787"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M29.3511 21.375V24.181"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                  </svg>
+                </div>
+                <div class="captions">
+                  자세한 내용은 가까운 전시장이나 서비스센터로<br />
+                  문의 하여 주시기 바랍니다.
+                </div>
+              </li>
+            </ul>
+          </div>
+
+          <div class="dbl--contents pt--150">
+            <ul>
+              <li>
+                <div class="thumb">
+                  <img src="/img/owner/dbl-item01.png" />
+                </div>
+                <div class="desc--wrap pt--50">
+                  <h2>업그레이드 플랜 제공 서비스 항목 (PMP Upgrade 항목 구매 시)</h2>
+                  <ul>
+                    <li>스파크 플러그</li>
+                    <li>연료필터, 에어컨필터</li>
+                    <li>에어크리너</li>
+                    <li>브레이크 패드</li>
+                    <li>와이퍼 블레이드</li>
+                    <li>엔진 오일 교환 및 오일 필터 교체</li>
+                    <li>타이어 점검 및 위치 교환</li>
+                    <li>오일류 점검 및 보충</li>
+                  </ul>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <img src="/img/owner/dbl-item02.png" />
+                </div>
+                <div class="desc--wrap pt--50">
+                  <h2>멀티 포인트 인스펙션 항목</h2>
+                  <ul>
+                    <li>
+                      경적, 실내등, 외장 램프, 방향지시등, 비상등 및 제동등의 작동상태
+                    </li>
+                    <li>윈드실드 워셔 분사, 와이퍼 작동, 와이퍼 블레이드</li>
+                    <li>윈드실드의 균열, 손상, 흠집</li>
+                    <li>
+                      엔진 냉각장치, 라디에이터, 호스, 클램프 오일 및 용액 누유 (엔진오일,
+                      브레이크 리저버, 파워 스티어링, 윈도우 워셔, 트랜스미션, 냉각수 회수
+                      리저버 등)
+                    </li>
+                    <li>배기 시스템(누출, 손상, 느슨한 부품)</li>
+                    <li>스티어링 연동장치 및 볼 조인트(육안 검사)</li>
+                    <li>쇼크 업소버/스트럿 및 기타 서스펜션 부품 누출 및 손상</li>
+                    <li>액세서리 구동 벨트</li>
+                    <li>브레이크 시스템(라인, 호스, 주차 브레이크 포함)</li>
+                    <li>클러치 작동(해당 차량)</li>
+                    <li>등속 (CV) 드라이브 액슬 부트(장착시)</li>
+                    <li>타이어 마모도 및 공기압 설정</li>
+                  </ul>
+                </div>
+              </li>
+            </ul>
+          </div>
+        </div>
+
+        <div class="essential--maintenace--wrap mt--150">
+          <div class="inner--wrap pt--50 pb--150">
+            <div class="title--visual">
+              <h2>필수 정비 서비스 (Essential Maintenance Plan, EMP)</h2>
+              <div class="captions mt--40">
+                필수 정비 서비스(EMP)는 기본 점검/정비를 보증하는 서비스입니다.<br />
+                기본 정비에는 다음 항목이 포함되며, 차량 구입 시점부터 정기 차량 검사가
+                포함되는 든든한 서비스입니다.
+              </div>
+            </div>
+            <div class="column--3 pt--70">
+              <ul>
+                <li>
+                  <div class="thumb">
+                    <svg
+                      xmlns="http://www.w3.org/2000/svg"
+                      width="50"
+                      height="50"
+                      viewBox="0 0 50 50"
+                      fill="none"
+                    >
+                      <path
+                        d="M12.795 18.6361L10.0249 17.1355L14.1711 9.75586L16.9411 11.2388"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M16.9411 11.2372C21.7842 11.8551 22.5527 3.39858 28.8434 4.03414C42.3541 6.08207 44.9812 21.6004 47.7513 33.2348C48.7342 37.3836 46.7505 38.3016 42.783 39.714C36.4387 41.9914 32.65 43.3332 26.3057 45.6106C22.9816 46.8111 22.928 45.1692 22.642 41.603C21.8021 30.6572 18.8891 27.1616 12.7949 18.6344M41.121 22.1477C39.4054 15.4213 33.061 7.61801 27.9677 8.23592C16.9053 10.9724 43.2298 30.7455 41.121 22.1477Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M15.475 34.0821C15.475 37.7542 12.4547 40.7378 8.7375 40.7378C5.02026 40.7378 2 37.7542 2 34.0821C2 29.5449 6.75378 23.7012 8.7375 20.082C10.7212 23.7012 15.475 29.5449 15.475 34.0821Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M4.98438 33.6777C5.12735 36.0258 6.39621 36.9791 7.41488 37.3146"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                    </svg>
+                  </div>
+                  <div class="caption">엔진 오일 및 필터 교환</div>
+                </li>
+                <li>
+                  <div class="thumb">
+                    <svg
+                      xmlns="http://www.w3.org/2000/svg"
+                      width="50"
+                      height="50"
+                      viewBox="0 0 50 50"
+                      fill="none"
+                    >
+                      <path
+                        d="M33.356 12.5969C33.9704 12.9501 34.604 13.3032 35.1992 13.6564C35.7944 14.0096 36.3896 14.3627 36.9656 14.7159M35.276 46.4412C35.9288 46.088 36.5624 45.7349 37.196 45.4013C37.8296 45.0482 38.444 44.695 39.0584 44.3418M33.356 44.0083C33.9704 43.6552 34.604 43.302 35.2184 42.9488C35.8136 42.5957 36.428 42.2425 37.0232 41.8894M40.4216 39.4957C40.0568 39.1426 39.692 38.7894 39.3272 38.4363C38.9816 38.0831 38.636 37.73 38.2904 37.3768C38.7896 37.0236 39.2696 36.6705 39.7496 36.3369C40.2296 35.9838 40.7096 35.6306 41.1897 35.2775M34.9688 39.8097C35.2952 40.1628 35.6216 40.516 35.9672 40.8691C36.3128 41.2223 36.6584 41.5755 37.004 41.909M36.236 34.9439C36.716 34.5908 37.1768 34.2376 37.6376 33.9041C38.0984 33.5509 38.5592 33.1978 39.0008 32.8446M41.6121 30.4118C41.2281 30.0586 40.8248 29.7054 40.4408 29.3523C40.0568 28.9991 39.6536 28.646 39.2696 28.2928C39.6728 27.9397 40.0568 27.5865 40.4408 27.253C40.8248 26.8998 41.2089 26.5466 41.5929 26.1935M36.7928 30.7257C37.1576 31.0788 37.5224 31.432 37.9064 31.7851C38.2712 32.1383 38.6552 32.4915 39.02 32.8446M36.7928 25.8796C37.1576 25.5264 37.5224 25.1733 37.8872 24.8201C38.252 24.4669 38.6168 24.1138 38.9816 23.7606C38.54 23.4271 38.0984 23.0739 37.6376 22.7208C37.1768 22.3676 36.716 22.0145 36.2552 21.6613M41.1321 21.3278C40.652 20.9942 40.1912 20.6411 39.7112 20.2879C39.2312 19.9347 38.732 19.5816 38.252 19.2284C38.5976 18.8753 38.924 18.5221 39.2696 18.169C39.6152 17.8158 39.98 17.4626 40.3256 17.1095M35.0072 16.7956C35.3336 16.4424 35.6408 16.0893 35.9672 15.7557C36.2936 15.4026 36.6392 15.0494 36.9848 14.6962M35.276 10.1641C35.9288 10.5172 36.5624 10.8704 37.196 11.2235C37.8104 11.5767 38.4248 11.9298 39.0392 12.283"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M40.4993 8.84714C38.8673 6.94401 29.2287 7.57185 27.1743 8.27816"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M40.499 47.7362C47.8335 42.2426 47.8335 14.3432 40.499 8.84961"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M40.4993 47.7363C38.8673 49.6395 29.2287 49.0116 27.1743 48.3053"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M26.6942 21.0723C28.0575 21.0723 29.1519 24.6235 29.1519 28.9791C29.1519 33.3347 28.0575 36.8859 26.6942 36.8859C23.6414 36.8859 27.0014 36.8859 23.9486 36.8859C23.0078 36.8859 22.2014 35.1986 21.779 32.7265H4.17236V25.2317H21.779C22.2014 22.7596 23.0078 21.0723 23.9486 21.0723C27.0014 21.0723 23.6414 21.0723 26.6942 21.0723Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M27.1928 48.3253C25.1768 46.8146 23.6983 43.6558 22.7575 39.7514M22.3159 18.4835C22.7575 14.3241 25.2152 9.39949 27.1928 8.30078"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M11.276 19.1685L16.8056 13.5965M9.27914 15.6566L14.8088 10.0845M7.30151 12.1446L12.8312 6.57259M5.30469 8.61305L10.8344 3.04102"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M15.7505 14.6554L19.4562 21.2084C20.0898 22.3464 19.725 23.7983 18.6114 24.4653C17.4978 25.1128 16.0769 24.74 15.4241 23.6021L12.3329 18.1281L15.7697 14.675L15.7505 14.6554ZM6.36163 7.57265L4 3.374L8.03205 1L9.79847 4.11956L6.36163 7.57265Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M27.7109 48.1245C35.199 42.5328 35.199 14.0448 27.7109 8.45312"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                    </svg>
+                  </div>
+                  <div class="caption">타이어 위치 교환</div>
+                </li>
+                <li>
+                  <div class="thumb">
+                    <svg
+                      xmlns="http://www.w3.org/2000/svg"
+                      width="50"
+                      height="50"
+                      viewBox="0 0 50 50"
+                      fill="none"
+                    >
+                      <path
+                        d="M29.1039 31.2088L34.2413 31.1542C34.1142 33.7044 32.0085 34.7974 29.1039 35.3074M29.1039 39.5153C33.3336 39.5153 37.8538 39.406 39.8144 39.0963C43.9715 38.4588 47.0031 37.0744 46.9849 34.7063C46.9668 32.3564 47.0394 30.3162 46.9668 28.021C46.9668 24.141 43.8081 19.4959 40.9399 16.9821M40.9399 16.9821C38.6707 18.6033 33.9509 19.7145 29.1039 20.261M40.9399 16.9821C39.8688 14.3044 37.2003 8.82134 36.5286 7.72838C34.7859 4.92312 27.7061 4.95955 23.4946 5.0142C19.3556 4.97776 12.4574 4.94133 10.5513 7.58265M43.8807 29.1868C42.1017 28.7861 36.1656 31.4456 38.0898 33.3219M36.2019 39.3878V44.7615H43.8807V38.5316M39.9233 13.9583C41.067 12.2642 45.6597 10.9162 46.1317 12.6285C46.9849 16.3263 43.9896 17.7107 42.1561 18.1844"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M3 10.0781H26.5085V46.0001H3V10.0781Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M6.30371 13.7949H12.2943V19.8062H6.30371V13.7949ZM6.30371 24.2145H12.2943V30.2258H6.30371V24.2145ZM6.30371 34.634H12.2943V40.6453H6.30371V34.634Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M14.8716 16.3057L16.5054 18.3824L23.2402 13.2637M14.8716 26.1242L16.5054 28.2008L23.2402 23.0821M14.8716 35.9426L16.5054 38.0192L23.2402 32.9005"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                    </svg>
+                  </div>
+                  <div class="caption">멀티포인트 인스펙션</div>
+                </li>
+              </ul>
+            </div>
+
+            <div class="columb--thume--3 pt--150">
+              <ul>
+                <li>
+                  <div class="thumb">
+                    <img src="/img/owner/ess-visual01.png" />
+                  </div>
+                  <div class="captions">엔진 오일 및 필터 교환</div>
+                </li>
+                <li>
+                  <div class="thumb">
+                    <img src="/img/owner/ess-visual02.png" />
+                  </div>
+                  <div class="captions">타이어 위치 교환</div>
+                </li>
+                <li>
+                  <div class="thumb">
+                    <img src="/img/owner/ess-visual03.png" />
+                  </div>
+                  <div class="captions">멀티포인트 인스펙션</div>
+                </li>
+              </ul>
+            </div>
+          </div>
+        </div>
+
+        <div class="used--essential--wrap">
+          <div class="title--visual">
+            <h2>장기차량 유지관리 플랜 (Used Essential Maintenance Plan)</h2>
+            <div class="captions">
+              이 서비스는 필수 정비서비스 기간이 지난 차량 중 PMP업그레이드 가입이 되지
+              않은 차량에 한하여<br />
+              언제든지 중복가입이 가능한 유지관리 플랜입니다.
+            </div>
+          </div>
+          <div class="column--3">
+            <ul>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <path
+                      d="M4.78992 44.6255C5.27488 44.2866 5.77781 43.93 6.26277 43.5912C6.87347 43.1632 7.48417 42.7352 8.11283 42.3072C7.96913 40.8272 7.8434 39.3292 7.69971 37.8491C6.33462 37.225 4.96953 36.6009 3.60444 35.9589C2.74228 36.5652 1.86216 37.1715 1 37.7778C1.28739 30.4309 7.75359 33.1057 10.8789 30.9123L23.2366 20.1951L28.9304 14.9523C30.7086 13.2048 30.1698 9.53128 32.1456 7.42705C34.3728 5.07317 37.085 5.39416 39.7972 6.33928C39.3123 6.67809 38.8093 7.03474 38.3244 7.37356C37.7137 7.80153 37.103 8.22951 36.4743 8.65749C36.618 10.1376 36.7438 11.6355 36.8874 13.1156C38.2525 13.7397 39.6176 14.3817 40.9827 15.0058C41.8449 14.3995 42.725 13.7932 43.5872 13.1869C43.2998 20.5339 36.8336 17.859 33.7082 20.0524L21.3506 30.7697L15.6567 36.0124C13.8785 37.76 14.4174 41.4335 12.4416 43.5377C10.2143 45.8916 7.50213 45.5884 4.78992 44.6255Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M14.8849 15.2701L9.46043 14.111L5.68848 8.81475L8.1133 6.40737L10.5381 4L15.8907 7.72697L17.0582 13.1124M14.6334 15.5019L17.2738 12.8805L23.9555 19.5142L23.2191 20.1918L21.1535 21.9929L14.6154 15.5019H14.6334ZM28.8052 24.3111L32.9364 28.4126C32.4873 28.787 32.0203 29.215 31.5713 29.6787C31.1043 30.1423 30.6732 30.6059 30.296 31.0339L25.9852 26.7541L28.8052 24.3111ZM48.8684 39.2725L36.385 26.879C35.8282 26.3262 33.6728 27.5744 31.5533 29.6787C29.4518 31.7651 28.1765 33.9228 28.7333 34.4756L41.2167 46.8691C41.7735 47.4219 43.9289 46.1736 46.0484 44.0694C48.1679 41.9652 49.4252 39.8253 48.8684 39.2725Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                    />
+                  </svg>
+                </div>
+                <div class="caption">
+                  차량 유지관리 비용에 효율적으로<br />
+                  대비할 수 있습니다.
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <g clip-path="url(#clip0_166_3246)">
+                      <path
+                        d="M8.05078 44.1797H11.4406V47.5759"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M41.1017 44.1797H37.7119V47.5759"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M46.6102 44.1797H50V47.5759H0V44.1797H3.38983"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M18.4744 16.0364L14.7456 15.2171C14.0592 15.0642 13.5761 14.4444 13.6015 13.7397C13.6354 12.7038 14.7075 12.0331 15.6524 12.4534L19.1397 14.0071C19.5507 14.1897 19.8049 14.61 19.7753 15.0557C19.7329 15.7138 19.1185 16.1807 18.4744 16.0406V16.0364Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M13.8011 9.07872L11.3223 7.35938"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M11.2578 6.59375H11.2588C11.2636 6.59328 11.2661 6.59422 11.2676 6.59473C11.27 6.59557 11.2741 6.59777 11.2783 6.60156C11.2823 6.60522 11.2849 6.60869 11.2861 6.61133C11.2869 6.61309 11.2881 6.61605 11.2881 6.62207V7.50488C11.2881 7.51213 11.287 7.51576 11.2861 7.51758C11.2849 7.52017 11.2826 7.52331 11.2793 7.52637C11.276 7.52936 11.2727 7.53141 11.2705 7.53223C11.2692 7.53266 11.2658 7.53386 11.2588 7.5332H11.2578L9.0752 7.32031C9.06172 7.31897 9.05093 7.30786 9.05078 7.29297V6.83398L9.05762 6.81543C9.06204 6.81037 9.06828 6.80733 9.0752 6.80664L11.2578 6.59375Z"
+                        fill="#10075E"
+                        stroke="black"
+                        stroke-width="2"
+                      />
+                      <path
+                        d="M35.3604 9.07872L37.8392 7.35938"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M37.8936 6.59375H37.8945L40.0771 6.80664C40.0907 6.808 40.1015 6.81904 40.1016 6.83398V7.29297C40.1014 7.30785 40.0906 7.31896 40.0771 7.32031L37.8945 7.5332H37.8936C37.8865 7.53386 37.8831 7.53266 37.8818 7.53223C37.8796 7.53141 37.8763 7.52936 37.873 7.52637C37.8697 7.52331 37.8674 7.52017 37.8662 7.51758C37.8654 7.51576 37.8643 7.51213 37.8643 7.50488V6.62207C37.8643 6.61605 37.8654 6.61309 37.8662 6.61133C37.8674 6.60869 37.87 6.60522 37.874 6.60156C37.8782 6.59777 37.8823 6.59557 37.8848 6.59473C37.8863 6.59422 37.8888 6.59328 37.8936 6.59375Z"
+                        fill="#10075E"
+                        stroke="black"
+                        stroke-width="2"
+                      />
+                      <path
+                        d="M31.3559 16.0364L35.0847 15.2171C35.7712 15.0642 36.2542 14.4444 36.2288 13.7397C36.1949 12.7038 35.1229 12.0331 34.178 12.4534L30.6907 14.0071C30.2797 14.1897 30.0254 14.61 30.0551 15.0557C30.0974 15.7138 30.7119 16.1807 31.3559 16.0406V16.0364Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M24.7543 22.5284L34.4534 22.4732C36.7246 22.4605 38.5636 20.5713 38.5636 18.2449V14.3265C38.5636 12.7897 37.7458 11.3717 36.428 10.6288L36.267 10.5397C35.7034 10.2213 35.25 9.72456 34.9831 9.12597L33.1526 5.05897C32.589 3.80236 31.3602 3 30.0085 3H19.1483C17.7967 3 16.5721 3.80236 16.0043 5.05897L14.1738 9.12597C13.9026 9.72456 13.4534 10.2213 12.8899 10.5397L12.7289 10.6288C11.4111 11.3717 10.5933 12.7854 10.5933 14.3265V18.385C10.5933 18.385 12.606 22.6897 14.8729 22.5284H24.7543Z"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M19.9152 22.9512V24.5347C19.9152 25.2988 19.2965 25.9229 18.5296 25.9229H16.2118C15.4491 25.9229 14.8262 25.3031 14.8262 24.5347V22.9512"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M34.7457 22.9512V24.5347C34.7457 25.2988 34.1271 25.9229 33.3601 25.9229H31.0423C30.2796 25.9229 29.6567 25.3031 29.6567 24.5347V22.9512"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M7.62695 22.5273H14.4066"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M7.62694 47.9996V18.707H3.38965V47.9996"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M41.5254 22.5273H35.1694"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M41.9492 47.5758V18.2832H46.1865V47.5758"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M27.1183 46.7272V40.3083C27.1183 40.3083 31.4234 38.7333 31.4234 34.6408C31.4234 30.5484 27.9658 28.4512 27.9658 28.4512V33.6304L25.2539 35.6937H23.898L21.1861 33.6304V28.4512C21.1861 28.4512 17.7285 30.5484 17.7285 34.6408C17.7285 38.7333 22.0336 40.3083 22.0336 40.3083V46.7272"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M35.1694 8.94336H13.9829"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M19.0679 22.1033V18.707H30.9323V22.1033"
+                        stroke="black"
+                        stroke-width="2"
+                        stroke-linejoin="round"
+                      />
+                    </g>
+                    <defs>
+                      <clipPath id="clip0_166_3246">
+                        <rect width="50" height="50" fill="white" />
+                      </clipPath>
+                    </defs>
+                  </svg>
+                </div>
+                <div class="caption">
+                  필수 정비 서비스와 동일한 서비스를 제공합니다.<br />
+                  (엔진 오일 및 필터 교환, 타이어 위치 교환,<br />
+                  멀티포인트 인스펙션)
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <path
+                      d="M43.7798 6H6.22018C3.88944 6 2 7.89773 2 10.2387V31.4322C2 33.7732 3.88944 35.6709 6.22018 35.6709H43.7798C46.1106 35.6709 48 33.7732 48 31.4322V10.2387C48 7.89773 46.1106 6 43.7798 6Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M2 31.4355H48V32.0332C48 34.2755 46.1853 36.0981 43.9528 36.0981H6.04716C3.81468 36.0981 2 34.2755 2 32.0332V31.4355Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M43.3576 14.0566H32.8071"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M43.3579 18.293H39.1377"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M30.6129 41.1841H18.9609L19.5897 36.0977H29.8617L30.6129 41.1841Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M12.5508 44.5746V43.7734C12.5508 42.345 13.7029 41.1836 15.1293 41.1836H34.0315C35.4537 41.1836 36.61 42.3408 36.61 43.7734V44.5746"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M4.9541 45H44.2018"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M15.7155 25.1851C15.7155 26.3507 14.7744 27.296 13.6139 27.296C12.4533 27.296 11.5122 26.3507 11.5122 25.1851C11.5122 24.0194 12.4533 23.0742 13.6139 23.0742C14.7744 23.0742 15.7155 24.0194 15.7155 25.1851Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M30.8283 25.1851C30.8283 26.3507 29.8872 27.296 28.7267 27.296C27.5661 27.296 26.625 26.3507 26.625 25.1851C26.625 24.0194 27.5661 23.0742 28.7267 23.0742C29.8872 23.0742 30.8283 24.0194 30.8283 25.1851Z"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M30.6972 25.0775H34.9174V22.8522C34.9174 21.3135 33.8159 19.9953 32.2966 19.7028L29.0724 19.156L23.9702 15.9431C23.3288 15.5404 22.5902 15.3242 21.8348 15.3242H13.4999C12.4618 15.3242 11.4658 15.7269 10.7188 16.4475L8.88729 18.2108C8.2627 18.8127 7.9082 19.6435 7.9082 20.5124V25.0732H11.2843"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linejoin="round"
+                    />
+                    <path
+                      d="M15.9268 25.0762H26.4772"
+                      stroke="black"
+                      stroke-width="2"
+                      stroke-linejoin="round"
+                    />
+                  </svg>
+                </div>
+                <div class="caption">가입기간 만료 후에도 재가입이 가능합니다.</div>
+              </li>
+            </ul>
+          </div>
+
+          <div class="dbl--contents pt--150">
+            <ul>
+              <li>
+                <div class="thumb">
+                  <img src="/img/owner/dbl-item03.png" />
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <img src="/img/owner/dbl-item04.png" />
+                </div>
+              </li>
+            </ul>
+          </div>
+          <div class="column--4 pt--150">
+            <h2>모든 소비자가 알아야 할 포드 프리미엄 유지관리 플랜의 특징</h2>
+            <ul>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="56"
+                    height="56"
+                    viewBox="0 0 56 56"
+                    fill="none"
+                  >
+                    <g clip-path="url(#clip0_172_2979)">
+                      <path
+                        d="M13.3271 50.9579C13.3271 50.9579 16.3526 48.6954 22.6226 49.8461C24.0721 50.1092 27.9869 51.3457 31.2094 51.5682C32.1746 51.6347 33.1451 51.52 34.0666 51.2248C36.4886 50.4492 38.6483 49.0417 40.9266 47.9112C43.9007 46.4352 46.4125 44.468 48.8979 42.2955C50.6362 40.7762 52.5014 39.3311 54.1335 37.6957C56.7093 35.1143 53.0212 31.8219 48.7014 35.4713C47.3706 36.5955 43.8738 39.203 41.9928 39.7165"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M8.34326 42.3074C12.1658 40.1022 13.451 36.9157 16.832 35.512C21.7644 33.4629 24.922 36.2076 28.2525 36.593C29.3081 36.7148 33.2046 36.6151 35.9701 36.8733C37.4894 37.0154 39.0778 38.2548 39.0778 39.8112V39.8266C39.0778 41.5839 37.6186 42.4389 35.8385 42.2847C27.3243 41.5497 24.5107 43.8059 24.5107 43.8059"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M13.129 53.9219L10.0656 55.6946C9.27616 56.1512 8.2672 55.88 7.81172 55.0895L2.08801 45.1535C1.63253 44.3626 1.90255 43.3515 2.69195 42.8948L5.75535 41.1222C6.54475 40.6655 7.55371 40.9367 8.00919 41.7272L13.7329 51.6627C14.1884 52.4537 13.9184 53.4648 13.129 53.9214V53.9219Z"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M9.43262 51.0508L10.4113 52.7497"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M46.0453 22.6548C49.0102 18.9997 46.6045 13.3985 41.7331 13.332H41.7264C38.6366 13.332 37.1231 14.7868 36.4351 15.8277C35.7495 14.7887 34.2356 13.332 31.1438 13.332H31.1371C26.2662 13.399 23.8591 19.0007 26.825 22.6548C29.2743 25.6741 36.056 32.3255 36.4346 32.6988C38.6923 30.4671 44.0528 25.1101 46.0448 22.6548H46.0453Z"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M36.4351 6.33056V0.933594"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M18.6719 22.0059H20.7715"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M28.6033 8.43176L27.3916 6.33203"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M22.8702 14.1692L18.1992 11.4707"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M54.1982 22.0059H52.0986"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M44.2671 8.43176L45.4783 6.33203"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M50.0005 14.1692L54.671 11.4707"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                    </g>
+                    <defs>
+                      <clipPath id="clip0_172_2979">
+                        <rect width="56" height="56" fill="white" />
+                      </clipPath>
+                    </defs>
+                  </svg>
+                </div>
+                <h2>포드 모터 컴퍼니가 후원합니다.</h2>
+                <div>
+                  포드 ESP는 포드 모터 컴퍼니가 100% 후원하는 유일한 연장형 서비스입니다.
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <g clip-path="url(#clip0_172_3025)">
+                      <path
+                        d="M4.64354 44.9641C5.13108 44.6169 5.63667 44.2514 6.12421 43.9042C6.73815 43.4656 7.35209 43.027 7.98409 42.5884C7.83964 41.0716 7.71324 39.5365 7.56878 38.0197C6.19644 37.38 4.82411 36.7404 3.45177 36.0825C2.58503 36.7039 1.70023 37.3252 0.833496 37.9466C1.12241 30.4173 7.62295 33.1586 10.7649 30.9107L23.1881 19.9276L28.9122 14.5547C30.6999 12.7638 30.1582 8.99919 32.1444 6.84276C34.3835 4.43048 37.1101 4.75943 39.8367 5.72799C39.3492 6.07522 38.8436 6.44071 38.3561 6.78794C37.7421 7.22653 37.1282 7.66513 36.4962 8.10373C36.6406 9.62054 36.767 11.1556 36.9115 12.6724C38.2838 13.3121 39.6562 13.97 41.0285 14.6096C41.8952 13.9882 42.78 13.3669 43.6468 12.7455C43.3579 20.2748 36.8573 17.5336 33.7154 19.7814L21.2921 30.7645L15.5681 36.1374C13.7804 37.9283 14.3221 41.6929 12.3358 43.8493C10.0968 46.2616 7.37015 45.9509 4.64354 44.9641Z"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M14.7916 14.8837L9.33837 13.6958L5.54639 8.2682L7.98409 5.80109L10.4218 3.33398L15.8028 7.15343L16.9765 12.6724M14.5388 15.1213L17.1932 12.4349L23.9104 19.2331L23.1701 19.9276L21.0935 21.7733L14.5207 15.1213H14.5388ZM28.7858 24.149L32.9389 28.3523C32.4875 28.736 32.018 29.1746 31.5666 29.6498C31.0971 30.1249 30.6638 30.6001 30.2846 31.0387L25.9509 26.6527L28.7858 24.149ZM48.9556 39.4816L36.4059 26.7806C35.8461 26.2141 33.6793 27.4933 31.5486 29.6498C29.4359 31.7879 28.1538 33.9992 28.7136 34.5657L41.2633 47.2667C41.823 47.8333 43.9899 46.554 46.1206 44.3976C48.2513 42.2411 49.5153 40.0482 48.9556 39.4816Z"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                    </g>
+                    <defs>
+                      <clipPath id="clip0_172_3025">
+                        <rect width="50" height="50" fill="white" />
+                      </clipPath>
+                    </defs>
+                  </svg>
+                </div>
+                <h2>공식 인증받은 포드 부품 및 서비스를 제공합니다.</h2>
+                <div>
+                  수리는 포드에서 교육 및 인증 받은 포드 전문 테크니션에 의해 포드 순정
+                  부품을 사용하여 이루어집니다.
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <g clip-path="url(#clip0_172_3057)">
+                      <path
+                        d="M1 36.4473H28.9666"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M42.3514 18.5541L40.9375 16.7194L37.6281 9.78654C36.525 7.47558 34.1944 6 31.6463 6H16.9716C14.4158 6 12.093 7.47558 10.9899 9.78654L7.68049 16.7194L3.22913 22.489C2.79409 23.0511 2.56104 23.7382 2.56104 24.4486V29.3516C2.56104 31.1161 3.98267 32.5448 5.73836 32.5448H27.4203"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M11.0986 32.5464V35.0915C11.0986 35.841 10.4927 36.45 9.74692 36.45H5.46647C4.72069 36.45 4.11475 35.841 4.11475 35.0915V31.7656"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M15.9576 10.543L13.7824 15.1415C13.5572 15.6099 13.899 16.1564 14.4195 16.1564H34.3323C34.8528 16.1564 35.1869 15.6177 34.9694 15.1415L32.7942 10.543"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M7.21533 23.3708V26.0565C7.21533 26.5796 7.59599 27.0324 8.10871 27.1183L12.1872 27.8053C12.8397 27.9146 13.4301 27.4071 13.4301 26.7435V25.0806C13.4301 24.6512 13.1738 24.253 12.7698 24.089L8.69135 22.3792C7.98441 22.0826 7.21533 22.6057 7.21533 23.3708Z"
+                        stroke="white"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M27.4131 27.8607H16.879L15.7681 23.957H28.1899"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M47.2072 15.369H43.8589C43.587 15.369 43.354 15.1972 43.2685 14.9396L42.647 13.0737C42.515 12.6677 42.8102 12.2461 43.2374 12.2461H46.5779C46.8498 12.2461 47.0829 12.4179 47.1683 12.6755L47.7898 14.5414C47.9219 14.9474 47.6267 15.369 47.1994 15.369H47.2072Z"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M39.8813 14.6579L42.9344 14.1504"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M1.79994 15.369H5.1404C5.4123 15.369 5.64536 15.1972 5.73081 14.9396L6.35229 13.0737C6.48436 12.6677 6.18915 12.2461 5.76189 12.2461H2.42142C2.14953 12.2461 1.91647 12.4179 1.83102 12.6755L1.20954 14.5414C1.07747 14.9474 1.37268 15.369 1.79994 15.369Z"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M9.12578 14.6579L6.07275 14.1504"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M3.33057 42.6953H33.6278"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linejoin="round"
+                        stroke-dasharray="4 4"
+                      />
+                      <path
+                        d="M45.4201 20.8359C45.4201 23.061 46.7485 25.2939 48.9625 25.7389C49.4053 34.202 45.8939 39.995 39.2519 44.0002C32.602 39.9872 29.0596 34.202 29.5024 25.7389C31.7164 25.2939 33.0448 23.0688 33.0448 20.8359H45.4201Z"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M39.322 35.9253L33.4956 31.0458L35.4843 28.6489L38.9802 31.5767L43.4238 26.4785L45.7621 28.5318L39.322 35.9253Z"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linejoin="round"
+                      />
+                    </g>
+                    <defs>
+                      <clipPath id="clip0_172_3057">
+                        <rect width="50" height="50" fill="white" />
+                      </clipPath>
+                    </defs>
+                  </svg>
+                </div>
+                <h2>부대비용이 없습니다.</h2>
+                <div>공제금이 없어, 보증범위 내의 수리에 관한 부대비용이 없습니다.</div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    width="50"
+                    height="50"
+                    viewBox="0 0 50 50"
+                    fill="none"
+                  >
+                    <g clip-path="url(#clip0_172_3050)">
+                      <path
+                        d="M29.83 30.859L35.307 30.8009C35.1715 33.5117 32.9265 34.6735 29.83 35.2156M29.83 39.6885C34.3393 39.6885 39.1583 39.5723 41.2485 39.2431C45.6804 38.5654 48.9124 37.0938 48.8931 34.5767C48.8737 32.0788 48.9512 29.9102 48.8737 27.4705C48.8737 23.3462 45.5062 18.4086 42.4484 15.7365M42.4484 15.7365C40.0292 17.4598 34.9973 18.641 29.83 19.2219M42.4484 15.7365C41.3066 12.8902 38.4616 7.06194 37.7455 5.90016C35.8876 2.91827 28.3398 2.957 23.8498 3.01509C19.4372 2.97636 12.0829 2.93764 10.0508 5.74526M45.5837 28.7097C43.687 28.2837 37.3585 31.1107 39.4099 33.1051M37.3972 39.5529V45.265H45.5837V38.6429M41.3646 12.5223C42.5839 10.7215 47.4803 9.28868 47.9835 11.1088C48.8931 15.0395 45.6998 16.511 43.7451 17.0145"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M2 8.39844H27.0627V46.5821H2V8.39844Z"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M5.52246 12.3477H11.9091V18.7374H5.52246V12.3477ZM5.52246 23.4232H11.9091V29.813H5.52246V23.4232ZM5.52246 34.4988H11.9091V40.8886H5.52246V34.4988Z"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M14.6567 15.0207L16.3985 17.2281L23.5787 11.7871M14.6567 25.4573L16.3985 27.6647L23.5787 22.2237M14.6567 35.8939L16.3985 38.1013L23.5787 32.6603"
+                        stroke="#333333"
+                        stroke-width="2.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                    </g>
+                    <defs>
+                      <clipPath id="clip0_172_3050">
+                        <rect width="50" height="50" fill="white" />
+                      </clipPath>
+                    </defs>
+                  </svg>
+                </div>
+                <h2>
+                  기본 플랜에 더 많은 서비스 보장 을 원하실 경우 플랜 업그레이드가
+                  가능합니다.
+                </h2>
+                <div>
+                  업그레이드 플랜에 대한 자세한 사항은 공식 딜러사로 문의 주십시오.
+                </div>
+              </li>
+            </ul>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="nav--dis--wrap" :class="{ active: isDisOpen }">
+      <div class="dis--btn" @click="isDisOpen = !isDisOpen">
+        disclosures <i class="ico"></i>
+      </div>
+      <div class="dis--cont">
+        <div class="container">
+          <p>
+            [1] 정비를 위해 하루 이상의 시간이 걸릴 수 있습니다.<br /><br />
+
+            [2] 자세한 사항은 가까운 전시장 및 서비스센터에 문의하세요.
+          </p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+
+  // 탭 관련
+  const activeTab = ref(0);
+  const tabItems = ["공인 서비스", "차량관리 서비스", "서비스 센터에서의 관리"];
+
+  const setActiveTab = (index) => {
+    activeTab.value = index;
+  };
+  // disclosures 토글 (기본 열림)
+  const isDisOpen = ref(true);
+
+  // 샘플 데이터
+  const breadcrumbData = {
+    mainMenu: "OWNER",
+    currentSubMenu: "서비스",
+    subMenuItems: [
+      { label: "공인 서비스", to: "/ford/owner", active: false },
+      {
+        label: "소모성 부품 무상 교환 서비스",
+        to: "/ford/owner/consumableParts",
+        active: true,
+      },
+      { label: "24시간 긴급 출동 서비스", to: "/ford/owner/tfService", active: false },
+      { label: "순정 부품", to: "/ford/owner/genuine", active: false },
+      { label: "리콜 안내", to: "/ford/owner/recall", active: false },
+      {
+        label: "사고 수리를 위한 포드 서비스 부품",
+        to: "/ford/owner/accident",
+        active: false,
+      },
+      { label: "내비게이션", to: "/ford/owner/navigation", active: false },
+    ],
+  };
+</script>

+ 89 - 0
app/pages/ford/owner/contact.vue

@@ -0,0 +1,89 @@
+<template>
+  <div>
+    <breadCrumbs
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="ovwner--wrapper">
+      <div class="title--visual">
+        <h2>포드 고객센터</h2>
+        <p class="desc type2">신속한 서비스로 여러분의 관심과 성원에 보답하겠습니다.</p>
+        <div class="desc mt--40">
+          언제나 저희의 목표는 고객의 만족입니다. 질문이나 의견이 있으실 때에는 주저하지
+          마시고 저희에게 연락하시기 바랍니다.<br />
+          최대한 신속한 서비스로 여러분의 관심과 성원에 보답하겠습니다.
+        </div>
+      </div>
+      <div class="title--visual mt--100">
+        <h2 class="side--title2">개인정보 처리위탁 관련 고지 사항</h2>
+        <div class="desc mt--30">
+          당사는 원활한 개인정보 업무처리를 위하여 다음과 같이 개인정보 처리업무를
+          위탁하고 있습니다.
+        </div>
+      </div>
+      <div class="table--wrap">
+        <table>
+          <colgroup></colgroup>
+          <thead>
+            <tr>
+              <th>수탁자</th>
+              <th>위탁 업무 내용</th>
+              <th>재수탁자 및 업무 내용</th>
+            </tr>
+          </thead>
+          <tbody>
+            <!-- Mustang 2.3 -->
+            <tr>
+              <td>주식회사 선인 자동차</td>
+              <td>
+                자동차 고객 문의 접수 및 불만 관리 등<br />
+                FLAK 고객 지원 센터 운영
+              </td>
+              <td>
+                주식회사 오토브레인<br />
+                FLAK 고객 지원 센터 운영
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+
+      <div class="contact--wrap mt--80">
+        <div class="contact">
+          <i class="ico"></i>
+          <h3 class="mb--30">고객센터 전화번호</h3>
+          <span class="">1600-6003</span>
+          <p>(근무시간 : 평일 09 - 18시 운영, 점심시간 12-13시)</p>
+        </div>
+        <div class="contact">
+          <i class="ico"></i>
+          <h3>긴급 출동 서비스</h3>
+          <span>080-300-3673</span>
+          <p>(연중 무휴, 24시간 운영)</p>
+        </div>
+        <div class="contact">
+          <i class="ico"></i>
+          <h3>E-MAIL</h3>
+          <a class="mailto--type mb--30" href="mailto:customercenter@flak.co.kr">
+            customercenter@flak.co.kr
+          </a>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+
+  // 샘플 데이터
+  const breadcrumbData = {
+    mainMenu: "NETWORK",
+    currentSubMenu: "포드 고객센터",
+    subMenuItems: [
+      { label: "전시장 및 서비스센터", to: "/ford/network", active: true },
+      { label: "포드 고객센터", to: "/ford/owner/contact", active: true },
+    ],
+  };
+</script>

+ 391 - 0
app/pages/ford/owner/event/[id].vue

@@ -0,0 +1,391 @@
+<template>
+   <div>
+    <breadCrumbs
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="ovwner--wrapper">
+      <div class="title--visual">
+        <h2>PROMOTION</h2>        
+      </div>
+      <div class="detail--container">       
+        <div class="content--wrap">
+          <div v-if="loading" class="view--wrap">
+            <p style="text-align: center; padding: 40px 0;">로딩 중...</p>
+          </div>
+          <div v-else-if="error" class="view--wrap">
+            <p style="text-align: center; padding: 40px 0; color: red;">{{ error }}</p>
+          </div>
+          <div v-else-if="eventData" class="view--wrap">
+            <div class="title--wrap">
+              <h2>{{ eventData.title }}</h2>
+              <div class="meta--info">
+                <p>{{ formatDate(eventData.created_at) }}</p>
+                <p v-if="eventData.start_date && eventData.end_date" class="event--period">
+                  기간: {{ formatDate(eventData.start_date) }} ~ {{ formatDate(eventData.end_date) }}
+                </p>
+              </div>
+            </div>
+            <div class="cont--wrap">
+              <div class="suneditor-content" v-html="eventData.content"></div>
+
+              <!-- 링크 섹션 (Iron Auto only) -->
+              <div v-if="config.public.company === 'i' && eventLinks.length > 0" class="event--links">
+                <div class="link--title">관련 링크</div>
+                <div class="link--buttons">
+                  <div v-for="(link, index) in eventLinks" :key="index" class="link--item">
+                    <a :href="link" target="_blank" rel="noopener noreferrer" class="link--button">
+                      <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <path d="M7.33333 8.66667L12.6667 3.33333M12.6667 3.33333H9.33333M12.6667 3.33333V6.66667M6.66667 2H4C3.46957 2 2.96086 2.21071 2.58579 2.58579C2.21071 2.96086 2 3.46957 2 4V12C2 12.5304 2.21071 13.0391 2.58579 13.4142C2.96086 13.7893 3.46957 14 4 14H12C12.5304 14 13.0391 13.7893 13.4142 13.4142C13.7893 13.0391 14 12.5304 14 12V9.33333" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+                      </svg>
+                      {{ link }}
+                    </a>
+                  </div>
+                </div>
+              </div>
+
+              <!-- <div v-if="fileLinks.length > 0" class="file--links">
+                <div class="file--title">첨부파일</div>
+                <div class="file--buttons">
+                  <div v-for="(link, index) in fileLinks" :key="index" class="file--item">
+                    <a :href="link.url" :target="link.target" class="file--button">
+                      <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <path d="M14 10v2.667A1.333 1.333 0 0 1 12.667 14H3.333A1.333 1.333 0 0 1 2 12.667V10m2.667-4L8 9.333 11.333 6M8 2v7.333" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+                      </svg>
+                      {{ link.text }}
+                    </a>
+                    <span class="file--tooltip">{{ link.title }}</span>
+                  </div>
+                </div>
+              </div> -->
+            </div>
+            <!-- 이전글/다음글 -->
+             <div class="post--navigation">
+              <div class="nav--item prev" :class="{ disabled: !eventData.prev_post }">
+                <NuxtLink v-if="eventData.prev_post" :to="`${isLincolnPage == false ? '/ford' : '/lincoln'}/owner/event/${eventData.prev_post.id}`" class="nav--link">
+                  <span class="nav--label">이전글</span>
+                  <span class="nav--title">{{ eventData.prev_post.title }}</span>
+                </NuxtLink>
+                <div v-else class="nav--link disabled">
+                  <span class="nav--label">이전글</span>
+                  <span class="nav--title">이전글이 없습니다.</span>
+                </div>
+              </div>
+              <div class="nav--item next" :class="{ disabled: !eventData.next_post }">
+                <NuxtLink v-if="eventData.next_post" :to="`${isLincolnPage == false ? '/ford' : '/lincoln'}/owner/event/${eventData.next_post.id}`" class="nav--link">
+                  <span class="nav--label">다음글</span>
+                  <span class="nav--title">{{ eventData.next_post.title }}</span>
+                </NuxtLink>
+                <div v-else class="nav--link disabled">
+                  <span class="nav--label">다음글</span>
+                  <span class="nav--title">다음글이 없습니다.</span>
+                </div>
+              </div>
+            </div>
+            <div class="btn--wrap" style="margin-top: 40px; text-align: center;">
+              <button
+                @click="goToList"
+                class="btn--primary"
+                style="padding: 12px 40px; background: #000; color: #fff; border: none; cursor: pointer;"
+              >
+                목록으로                
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+const { getMediaUrl } = useImage();
+import { ref, computed, onMounted } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import '~/assets/scss/suneditor-content.scss';
+
+const route = useRoute();
+const router = useRouter();
+const config = useRuntimeConfig();
+const { apiBase } = useCompany();
+
+const eventData = ref(null);
+const loading = ref(true);
+const error = ref(null);
+const isLincolnPage = computed(() => {  
+  return route.path.includes("lincoln");
+});
+
+// 파일명 자르기 함수 (20자 내외)
+const truncateFileName = (fileName) => {
+  if (!fileName) return '';
+
+  // 확장자 분리
+  const lastDotIndex = fileName.lastIndexOf('.');
+  const name = lastDotIndex > 0 ? fileName.substring(0, lastDotIndex) : fileName;
+  const ext = lastDotIndex > 0 ? fileName.substring(lastDotIndex) : '';
+
+  // 이름이 20자 이하면 그대로 반환
+  if (name.length <= 20) {
+    return fileName;
+  }
+
+  // 20자로 자르고 ... 추가 후 확장자 붙이기
+  return name.substring(0, 20) + '...' + ext;
+};
+
+// 링크 목록 생성 (Iron Auto only)
+const eventLinks = computed(() => {
+  if (!eventData.value || !eventData.value.link) return [];
+
+  try {
+    const links = Array.isArray(eventData.value.link)
+      ? eventData.value.link
+      : JSON.parse(eventData.value.link || '[]');
+
+    // null이나 빈 문자열 제거
+    return links.filter(link => link && link.trim() !== '');
+  } catch (e) {
+    console.error('링크 파싱 오류:', e);
+    return [];
+  }
+});
+
+// 파일 링크 목록 생성
+const fileLinks = computed(() => {
+  if (!eventData.value || !eventData.value.file_urls) return [];
+
+  try {
+    const files = Array.isArray(eventData.value.file_urls)
+      ? eventData.value.file_urls
+      : JSON.parse(eventData.value.file_urls || '[]');
+
+    return files.map(file => {
+      let fileUrl = file.url || file;
+      const originalFileName = file.name || file;
+
+      // 상대 경로면 도메인 붙이기
+      if (fileUrl.startsWith('/')) {
+        fileUrl = apiBase.value + fileUrl;
+      }
+
+      return {
+        text: truncateFileName(originalFileName),
+        url: fileUrl,
+        target: '_blank',
+        class: 'pl--0',
+        title: originalFileName // 툴팁으로 전체 파일명 표시
+      };
+    });
+  } catch (e) {
+    console.error('파일 파싱 오류:', e);
+    return [];
+  }
+});
+
+// 날짜 포맷팅
+const formatDate = (dateString) => {
+  if (!dateString) return '-';
+  const date = new Date(dateString);
+  return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
+};
+
+// Event 데이터 로드
+const loadEventData = async () => {
+  try {
+    loading.value = true;
+    error.value = null;
+
+    const id = route.params.id;
+    const response = await fetch(`${apiBase.value}/api/event/public/${id}`);
+    const result = await response.json();
+
+    console.log('[Event Detail] API 응답:', result);
+
+    if (!result.success) {
+      error.value = result.message || '이벤트 정보를 불러올 수 없습니다.';
+      console.error('[Event Detail] API 에러:', result);
+    } else if (result.data) {
+      eventData.value = result.data;
+      console.log('[Event Detail] 로드 성공:', eventData.value);
+    } else {
+      error.value = '이벤트 정보를 찾을 수 없습니다.';
+    }
+  } catch (e) {
+    console.error('[Event Detail] 로드 실패:', e);
+    error.value = '이벤트 정보를 불러오는 중 오류가 발생했습니다.';
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 목록으로 돌아가기
+
+const goToList = () => {
+  router.push('/ford/owner/event');
+};
+
+   // 샘플 데이터
+const breadcrumbData = {
+    mainMenu: "OWNER",
+    currentSubMenu: "프로모션",
+    subMenuItems: [
+      { label: "프로모션", to: '/ford/owner/event', active: false },      
+    ],
+  };
+
+onMounted(() => {
+  loadEventData();
+});
+</script>
+
+<style scoped lang="scss">
+.event--links {
+  margin-top: 40px;
+  padding-top: 24px;
+  border-top: 1px solid rgba(252, 252, 253, 0.2);
+}
+
+.link--title {
+  font-size: 16px;
+  font-weight: 600;
+  color: rgba(252, 252, 253, 1);
+  margin-bottom: 16px;
+}
+
+.link--buttons {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.link--item {
+  display: block;
+}
+
+.link--button {
+  display: inline-flex;
+  align-items: center;
+  gap: 8px;
+  padding: 12px 20px;
+  background-color: rgba(255, 255, 255, 0.1);
+  border: 1px solid rgba(252, 252, 253, 0.3);
+  border-radius: 50px;
+  color: rgba(252, 252, 253, 0.9);
+  font-size: 14px;
+  text-decoration: none;
+  transition: all 0.3s ease;
+  cursor: pointer;
+  word-break: break-all;
+
+  svg {
+    flex-shrink: 0;
+  }
+
+  &:hover {
+    background-color: rgba(255, 255, 255, 0.2);
+    border-color: rgba(252, 252, 253, 0.5);
+    transform: translateY(-2px);
+  }
+
+  &:active {
+    transform: translateY(0);
+  }
+}
+
+.file--links {
+  margin-top: 40px;
+  padding-top: 24px;
+  border-top: 1px solid #444c57;
+}
+
+.file--title {
+  font-size: 16px;
+  font-weight: 600;
+  color: rgba(252, 252, 253, 1);
+  margin-bottom: 16px;
+}
+
+.file--buttons {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+}
+
+.file--item {
+  position: relative;
+  display: inline-block;
+
+  &:hover {
+    .file--tooltip {
+      opacity: 1;
+      visibility: visible;
+      transform: translateY(-5px);
+    }
+  }
+}
+
+.file--button {
+  display: inline-flex;
+  align-items: center;
+  gap: 8px;
+  padding: 12px 20px;
+  background-color: rgba(255, 255, 255, 0.1);
+  border: 1px solid rgba(252, 252, 253, 0.3);
+  border-radius: 50px;
+  color: rgba(252, 252, 253, 0.9);
+  font-size: 14px;
+  text-decoration: none;
+  transition: all 0.3s ease;
+  cursor: pointer;
+
+  svg {
+    flex-shrink: 0;
+  }
+
+  &:hover {
+    background-color: rgba(255, 255, 255, 0.2);
+    border-color: rgba(252, 252, 253, 0.5);
+    transform: translateY(-2px);
+  }
+
+  &:active {
+    transform: translateY(0);
+  }
+}
+
+.file--tooltip {
+  position: absolute;
+  bottom: calc(100% + 10px);
+  left: 50%;
+  transform: translateX(-50%);
+  background-color: rgba(0, 0, 0, 0.95);
+  color: #fff;
+  padding: 10px 14px;
+  border-radius: 6px;
+  font-size: 13px;
+  white-space: nowrap;
+  opacity: 0;
+  visibility: hidden;
+  transition: all 0.25s ease;
+  pointer-events: none;
+  z-index: 1000;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+
+  &::after {
+    content: '';
+    position: absolute;
+    top: 100%;
+    left: 50%;
+    transform: translateX(-50%);
+    border: 7px solid transparent;
+    border-top-color: rgba(0, 0, 0, 0.95);
+  }
+}
+
+.file--item:hover .file--tooltip {
+  transform: translateX(-50%) translateY(-5px);
+}
+
+</style>

+ 10 - 0
app/pages/ford/owner/event/index.vue

@@ -0,0 +1,10 @@
+<script setup>
+  const config = useRuntimeConfig();
+  const event = defineAsyncComponent(() =>
+    import(`~/components/event.vue`)
+  );
+</script>
+
+<template>
+  <event />
+</template>

+ 211 - 0
app/pages/ford/owner/genuine.vue

@@ -0,0 +1,211 @@
+<template>
+  <div>
+    <breadCrumbs
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="ovwner--wrapper">
+      <div class="title--visual">
+        <h2>순정 부품</h2>
+        <!-- <div class="desc mt--40">
+          귀하의 차량에 사용된 부품은 주행할 때마다 기능을 발휘합니다.<br />자격을 갖춘
+          숙련된 전문가가 귀하의 차량을 취급해야 하듯이 전문가가 수리에 사용하는 부품은
+          귀하의 차량을 위해 특별히 설계, 제작, 추천되는 제품이어야 합니다.<br />포드 순정
+          부품은 포드 모터 컴퍼니가 보증하며 포드 딜러 네트워크가 지원합니다.
+        </div> -->
+        <div class="thums--wrap mt--70">
+          <img src="/img/owner/parts--visual1.png" alt="" />
+        </div>
+      </div>
+
+      <div class="text--tab--layout mt--100">
+        <ul>
+          <li
+            v-for="(tab, index) in tabItems"
+            :key="index"
+            :class="{ 'anch--location': index === 0, actv: activeTab === index }"
+            @click="setActiveTab(index)"
+          >
+            <NuxtLink>{{ tab }}</NuxtLink>
+          </li>
+        </ul>
+      </div>
+
+      <div class="owner--inner--content mt--50">
+        <div :class="{ show: activeTab === 0, hidden: activeTab !== 0 }">
+          <div class="thumb">
+            <img src="/img/owner/parts--visual2.png" />
+          </div>
+          <div class="desc--wrapper">
+            <h2 class="mt--60">순정 부품의 장점</h2>
+            <h3 class="mt--50">안전</h3>
+            <div class="captions fz--17 pt--25">
+              저희는 귀하의 포드 차량이 귀하와 동승자, 보행자를 보호할 수 있도록 세심하게
+              고려했습니다. 이에 따라 포드 순정 부품은 법규 상의 안전 및 환경 기준을
+              만족하고, 많은 부분에서 더 우수하도록 설계, 생산, 시험된 제품입니다.
+            </div>
+            <h3 class="mt--50">성능</h3>
+            <div class="captions fz--17 pt--25">
+              귀하의 포드 차량에 사용된 모든 부품은 함께 작동하여 최적의 성능, 완벽한
+              쾌적함, 최상의 연비를 내도록 설계되었습니다. 출고 시 장착 부품과 같은 엄격한
+              규격에 따라 생산된 포드 순정 부품 역시 마찬가지입니다.
+            </div>
+            <h3 class="mt--50">품질</h3>
+            <div class="captions fz--17 pt--25">
+              포드 순정 부품은 높은 기준에 따라 설계 및 생산되어 최상의 조립과 마무리,
+              신뢰성과 성능을 보장합니다. 힘을 주지 않아도 빈틈 또는 비틀림 없이 완벽하게
+              조립되도록 설계되어, 양산 부품과 같은 마무리를 보여줍니다.
+            </div>
+            <h3 class="mt--50">보증</h3>
+            <div class="captions fz--17 pt--25">
+              포드의 보증이 있다면 서비스, 수리, 차체, 액세서리와 관계없이 안심하실 수
+              있습니다. 모든 수리 절차는 최상의 품질 기준으로 이루어집니다. 또한 귀하의
+              포드를 매각할 때에는 보증이 승계되어 새 소유주에게 혜택이 더해집니다.
+            </div>
+            <h3 class="mt--50">정품</h3>
+            <div class="captions fz--17 pt--25">
+              포드 순정 부품은 로고를 통해 진품 여부를 쉽게 확인할 수 있습니다. 포드
+              상표는 돋보이는 위치에 표시되어 있습니다.
+            </div>
+            <h3 class="mt--50">올바른 부품의 선택</h3>
+            <div class="captions fz--17 pt--25">
+              서비스 센터에서 정비하거나 직접 수리를 하더라도, 사용하는 부품의 차이가
+              중요합니다. 귀하께서 직접 차량 수리작업을 하거나 다른 이가 귀하의 차를
+              수리할 때에도, 항상 차량 출고 시에 장착된 것과 같은 성능을 내도록 특별히
+              설계 및 제작된 부품인 포드 모터 컴퍼니 순정 부품과 모터크래프트 부품을
+              사용하십시오. 수리 후에는 최적의 성능과 수명을 기대할 수 있습니다.
+            </div>
+          </div>
+        </div>
+        <div :class="{ show: activeTab === 1, hidden: activeTab !== 1 }">
+          <div class="thumb">
+            <img src="/img/owner/parts--visual3.png" />
+          </div>
+          <div class="desc--wrapper">
+            <h2 class="mt--60">위조 부품</h2>
+            <div class="captions">
+              포드 모터 컴퍼니는 위조 부품을 매우 심각하게 여기고 있으며 시장에 유통되어
+              귀하의 차량에 사용되지 않도록 예방조치를 취하고 있습니다. 위조품 생산은
+              세계적인 현상이며 포드 모터 컴퍼니는 세계적인 차원에서 포드 브랜드와 저희의
+              고객인 귀하를 보호하기 위해 노력을 기울이고 있습니다.
+              <!-- <br /><br />귀하를
+              보호하고자 하는 저희의 노력이 궁금하다면 아래 버튼을 클릭하십시오. -->
+            </div>
+            <div class="btn--wrap fl--start mt--50">
+              <NuxtLink
+                to="https://www.fordbrandprotection.com/"
+                class="btn--go"
+                target="_blank"
+                ><strong>Fordbrandprotection </strong> Go To Site <i class="ico"></i
+              ></NuxtLink>
+            </div>
+          </div>
+        </div>
+        <div :class="{ show: activeTab === 2, hidden: activeTab !== 2 }">
+          <div class="thumb">
+            <img src="/img/owner/parts--visual4.png" />
+          </div>
+          <div class="desc--wrapper">
+            <h2 class="mt--60">순정부품 가격정보</h2>
+            <div class="captions">
+              순정 부품 가격은 아래 공식 딜러사 홈페이지에서 확인하실 수 있습니다. 배너를
+              클릭하시면 각 딜러사의 부품 가격 정보 페이지로 이동합니다.
+              <div class="btn--wrap fl--start mt--50">
+                <NuxtLink
+                  to="https://suninford.co.kr/_ford/service_center/part_price.html"
+                  class="btn--go fw--500"
+                  target="_blank"
+                  ><strong class="color--blue">선인자동차 </strong> 부품 가격
+                  <i class="ico"></i
+                ></NuxtLink>
+                <NuxtLink
+                  to="https://www.premiermotors.co.kr/ford/center/parts_price.html?PHPSESSID=b5abb007710978e1dcf4d34acb8ffbaf"
+                  class="btn--go fw--500"
+                  target="_blank"
+                  ><strong class="color--blue">프리미어 모터스 </strong> 부품 가격
+                  <i class="ico"></i
+                ></NuxtLink>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="owner--part--wrap">
+        <div class="owner--part">
+          <i class="ico"></i>
+          <div class="part--content">
+            <h3>출고 시 장착되는 순정 부품에 대하여</h3>
+            <p>
+              저희는 교체용 부품을 신차와 마찬가지로 높은 기준에 따라 생산하고
+              있습니다.<br />저희는 100년 이상의 자동차 생산 경험을 통해 귀하의 포드
+              차량을 완벽한 상태로 유지하는 것이 얼마나 중요한지 잘 알고 있기 때문입니다.
+            </p>
+          </div>
+        </div>
+        <div class="owner--part">
+          <i class="ico"></i>
+          <div class="part--content">
+            <h3>출고 시 장착되는 순정 부품의 중요성</h3>
+            <p>
+              귀하의 차량에 알맞게 조립되고 설계된 부품은 더욱 뛰어난 성능을 발휘하도록
+              해줄 뿐 아니라 수명도 깁니다.<Br />따라서 부품 교체 횟수와 주기가 길어지므로
+              비용이 절감됩니다. <Br />포드 모터 컴퍼니 순정 부품,
+              모터크래프트(Motorcraft®) 부품, 그리고 포드 및 모터 크래프트 재생 부품은
+              성능, 수명, 가치에서 모두 탁월한 선택입니다. 또한 포드 모터 컴퍼니의 한정
+              부품 보증을 받으므로 성능에 대해 안심하실 수 있습니다.<Br />귀하의 차량을
+              위한 포드 및 모터크래프트의 재생 부품도 마련되어 있습니다.
+            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="nav--dis--wrap" :class="{ active: isDisOpen }">
+      <div class="dis--btn" @click="isDisOpen = !isDisOpen">
+        disclosures <i class="ico"></i>
+      </div>
+      <div class="dis--cont">
+        <div class="container">
+          <p>[1] 부품 보증에 관한 자세한 사항은 서비스 센터에 문의하십시오.</p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+
+  // 탭 관련
+  const activeTab = ref(0);
+  const tabItems = ["순정 부품의 장점", "위조 부품", "순정 부품 가격 정보"];
+
+  const setActiveTab = (index) => {
+    activeTab.value = index;
+  };
+  // disclosures 토글 (기본 열림)
+  const isDisOpen = ref(true);
+
+  // 샘플 데이터
+  const breadcrumbData = {
+    mainMenu: "OWNER",
+    currentSubMenu: "서비스",
+    subMenuItems: [
+      { label: "공인 서비스", to: "/ford/owner", active: false },
+      {
+        label: "소모성 부품 무상 교환 서비스",
+        to: "/ford/owner/consumableParts",
+        active: true,
+      },
+      { label: "24시간 긴급 출동 서비스", to: "/ford/owner/tfService", active: false },
+      { label: "순정 부품", to: "/ford/owner/genuine", active: false },
+      { label: "리콜 안내", to: "/ford/owner/recall", active: false },
+      {
+        label: "사고 수리를 위한 포드 서비스 부품",
+        to: "/ford/owner/accident",
+        active: false,
+      },
+      { label: "내비게이션", to: "/ford/owner/navigation", active: false },
+    ],
+  };
+</script>

+ 202 - 0
app/pages/ford/owner/index.vue

@@ -0,0 +1,202 @@
+<template>
+  <div>
+    <breadCrumbs
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="ovwner--wrapper">
+      <div class="title--visual">
+        <h2>공인 서비스</h2>
+        <div class="desc mt--40">
+          포드 전문가에게 맡기시면 차별화된 서비스를 받으실 수 있습니다.<br />
+          귀하의 차량을 포드 공식 서비스 센터로 가져오시면, 포드 전문 테크니션이 순정
+          부품을 사용하여 탁월한 서비스를 제공해드립니다.<br />
+          귀하의 차량에 가장 적합한 제품과 서비스입니다. 포드 공식 서비스센터 전문
+          테크니션들은 고객께 비교할 수 없는 작업 품질과 신뢰성을 보여 드립니다.
+        </div>
+        <div class="thums--wrap mt--70">
+          <img src="/img/owner/owner-visual.jpg" alt="" />
+        </div>
+      </div>
+
+      <div class="text--tab--layout mt--100">
+        <ul>
+          <li
+            v-for="(tab, index) in tabItems"
+            :key="index"
+            :class="{ 'anch--location': index === 0, actv: activeTab === index }"
+            @click="setActiveTab(index)"
+          >
+            <NuxtLink>{{ tab }}</NuxtLink>
+          </li>
+        </ul>
+      </div>
+
+      <div class="owner--inner--content mt--50">
+        <div :class="{ show: activeTab === 0, hidden: activeTab !== 0 }">
+          <div class="thumb">
+            <img src="/img/owner/owner-visual02.jpg" />
+          </div>
+          <div class="desc--wrapper">
+            <h2 class="mt--60">공인 서비스</h2>
+            <div class="captions">
+              귀하의 차량에 관한 문제라면 무엇이든 포드 공식 서비스센터로 맡겨
+              주십시오.<br />
+              정기적인 오일 교환이나 좀 더 복잡한 엔진 수리라도 포드 공식 서비스센터에서는
+              귀하께서 차량을 처음 구매하신 날부터 재구매를 위해 다시 찾아오시는
+              날까지,<br />
+              차량에 필요한 모든 사항을 관리 해 드립니다.<br /><br /><br />
+              아래 항목을 포함하는 모든 서비스를 완벽하게 제공합니다.
+              <ul>
+                <li>엔진 오일 및 필터 교환</li>
+                <li>차량 정기 점검</li>
+                <li>타이어 교체 및 정비</li>
+                <li>브레이크 패드 및 라이닝 교체</li>
+                <li>에어컨</li>
+                <li>엔진 점검</li>
+                <li>차량 종합 검사</li>
+                <li>4륜 구동 시스템</li>
+                <li>자동 변속기</li>
+                <li>엔진 냉각 계통</li>
+                <li>충전, 시동 또는 배터리 정비</li>
+                <li>휠 얼라인먼트</li>
+                <li>ABS 브레이크 시스템</li>
+                <li>기타 정비 및 점검</li>
+              </ul>
+
+              <div class="add--text">
+                다양한 서비스 제공을 위하여 전문적인 교육을 이수한 포드 테크니션이 언제나
+                귀하의 차량을 책임지기 위하여 노력하고 있습니다.
+              </div>
+            </div>
+          </div>
+        </div>
+        <div :class="{ show: activeTab === 1, hidden: activeTab !== 1 }">
+          <div class="thumb">
+            <img src="/img/owner/owner-visual03.jpg" />
+          </div>
+          <div class="desc--wrapper">
+            <h2 class="mt--60">차량관리 서비스</h2>
+            <div class="captions">
+              귀하의 차량을 최적의 상태로 유지하기 위해 어떤 서비스가 필요한지 확인하고자
+              방문하시면, 포드 공식 서비스센터에서 차량 종합 검사를 받아 보실 수 있습니다.
+              이 서비스의 목적은 귀하의 서비스에<br />
+              관한 이해를 돕는 한편 차량의 핵심 부품이 최적의 상태로 유지되도록
+              도와드립니다.<br /><br /><br />
+
+              포드 공식 서비스 센터에 도착하시면, 귀하의 차량은 포드 전문가에게
+              맡겨집니다. 귀하의 차량을 위해 포드 서비스 직원들은 아래 항목을 포함하는
+              모든 주요 부분을 검사하고 평가합니다.
+              <ul>
+                <li>오일류 레벨 확인</li>
+                <li>엔진 오일</li>
+                <li>변속기 오일</li>
+                <li>워셔액 탱크</li>
+                <li>브레이크 액</li>
+                <li>냉각수</li>
+                <li>누유/누수</li>
+                <li>타이어 트레드 잔존량 및 마모 패턴</li>
+                <li>배터리</li>
+                <li>휠</li>
+                <li>브레이크(마모도 측정/점검)</li>
+                <li>쇼크 업소버</li>
+                <li>배기계통</li>
+                <li>등속 조인트</li>
+                <li>드라이브 샤프트</li>
+                <li>U 조인트</li>
+                <li>벨트 및 호스 점검</li>
+                <li>엔진 필터(엔진)</li>
+              </ul>
+
+              <div class="add--text">
+                검사 항목의 평가는 아래와 같은 색상 구분이 되어 있어 쉽게 이해하실 수
+                있습니다.<br />
+                - 녹색: 점검 결과 현재 양호한 상태입니다.<br />
+                - 황색: 앞으로 상태를 지켜보거나 다시 점검해야 합니다.<br />
+                - 적색: 즉시 조치해야 합니다.
+              </div>
+            </div>
+          </div>
+        </div>
+        <div :class="{ show: activeTab === 2, hidden: activeTab !== 2 }">
+          <div class="thumb">
+            <img src="/img/owner/owner-visual04.jpg" />
+          </div>
+          <div class="desc--wrapper">
+            <h2 class="mt--60">서비스 센터에서의 관리</h2>
+            <div class="captions">
+              포드의 정기점검 서비스를 통해 차량을 위한 지출계획을 가장 합리적으로 준비
+              하실 수 있습니다. 포드가 권장하는 정비 서비스 주기는 필요한 시기에 귀하의
+              차량이 최적의 운행 상태를 유지하도록 마련 되어 있습니다.
+              <h3 class="mt--60">
+                귀하의 차량 정기점검 서비스 안내에서 다음의 안내 사항을 확인하십시오.
+              </h3>
+              <ul class="pt--20">
+                <li>정기점검의 필요성</li>
+                <li>주행거리 주기 및 시기</li>
+              </ul>
+
+              <h3 class="mt--60">
+                지정된 권장 정비 주기를 따르면 아래와 같은 점이 좋습니다.
+              </h3>
+              <ul class="pt--20">
+                <li>최적의 작동, 신뢰성, 안전성이 보장됩니다.</li>
+                <li>차량의 수명이 연장됩니다.</li>
+                <li>예상치 못한 고장이 예방됩니다.</li>
+                <li>연료소비 효율이 극대화됩니다.</li>
+              </ul>
+
+              <h3 class="mt--60">
+                중고차 거래 시 가치가 높아집니다.아래와 같은 현상이 하나 이상 나타나면
+                즉시 서비스 예약을 하시기 바랍니다.
+              </h3>
+              <ul class="pt--20">
+                <li>시동을 건 후 ‘Service Engine Soon’ 경고등이 꺼지지 않을 때</li>
+                <li>엔진에서 오일류가 누유될 때</li>
+                <li>하나 이상의 엔진 부품에 뚜렷한 손상이 있을 때</li>
+                <li>엔진에서 큰 소음이 날 때</li>
+                <li>보닛 아래에서 연기가 날 때</li>
+              </ul>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+
+  // 탭 관련
+  const activeTab = ref(0);
+  const tabItems = ["공인 서비스", "차량관리 서비스", "서비스 센터에서의 관리"];
+
+  const setActiveTab = (index) => {
+    activeTab.value = index;
+  };
+
+  // 샘플 데이터
+  const breadcrumbData = {
+    mainMenu: "OWNER",
+    currentSubMenu: "서비스",
+    subMenuItems: [
+      { label: "공인 서비스", to: "/ford/owner", active: false },
+      {
+        label: "소모성 부품 무상 교환 서비스",
+        to: "/ford/owner/consumableParts",
+        active: true,
+      },
+      { label: "24시간 긴급 출동 서비스", to: "/ford/owner/tfService", active: false },
+      { label: "순정 부품", to: "/ford/owner/genuine", active: false },
+      { label: "리콜 안내", to: "/ford/owner/recall", active: false },
+      {
+        label: "사고 수리를 위한 포드 서비스 부품",
+        to: "/ford/owner/accident",
+        active: false,
+      },
+      { label: "내비게이션", to: "/ford/owner/navigation", active: false },
+    ],
+  };
+</script>

+ 926 - 0
app/pages/ford/owner/navigation.vue

@@ -0,0 +1,926 @@
+<template>
+  <div>
+    <breadCrumbs
+      :width="219"
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="ovwner--wrapper pb--0">
+      <div class="inner--wrap">
+        <div class="title--visual">
+          <h2>포드 내비게이션 서비스 종료 안내</h2>
+          <!-- <div class="desc mt--40">
+            포드코리아에서 제공 중인 내비게이션 서비스 관련하여 <br />고객님께 다음의 안내
+            사항을 전달해 드립니다.
+          </div> -->
+        </div>
+        <div class="thums--wrap mt--70">
+          <img src="/img/owner/nav--visual2.png" alt="" />
+        </div>
+      </div>
+      <div class="nav--desc--wrap">
+        <p>
+          안녕하세요. 포드코리아입니다. <br /><br />
+          포드코리아에서 제공 중인
+          <b>내비게이션 서비스</b> 관련하여<br />고객님께 다음의 안내 사항을 전달해
+          드립니다.<br /><br />
+          2024년 12월 말 내비게이션 업체와의 계약 만료에 따라,
+          <b>2025년부터 맵 업데이트 서비스 및 신차 대상 내비게이션 장착이 순차적</b>으로
+          <b>중단</b>됨을 안내드립니다. <br /><br />이번 결정에 대한 고객님들의 너른
+          양해를 부탁드리며, 관련한 고객님의 문의에 성심껏 답변해 드리겠습니다.<br />보다
+          자세한 내용은 당사 고객센터로 문의해 주시기를 바랍니다.<br /><br />앞으로도
+          고객님께 더 나은 서비스를 제공하기 위해 최선을 다하겠습니다.<br /><br />감사합니다.
+        </p>
+        <div class="btn--wrap pt--50 pb--150 fl--start">
+          <button class="btn--go fw--500" @click="showModal = true">
+            서비스 약관 <i class="ico"></i>
+          </button>
+          <NuxtLink class="btn--go fw--500" to="/warranty/warranty.pdf" target="_blank"
+            ><strong class="">제품</strong> 보증서 <i class="ico"></i
+          ></NuxtLink>
+          <button class="btn--go fw--500" @click="showModal2 = true">
+            아틀란 내비게이션 이용약관 <i class="ico"></i>
+          </button>
+          <NuxtLink
+            class="btn--go fw--500"
+            to="https://ktfordlincoln-go.com"
+            target="_blank"
+            ><strong class="">KT 내비게이션 업데이트 바로가기</strong> <i class="ico"></i
+          ></NuxtLink>
+        </div>
+      </div>
+    </div>
+    <!-- <div class="nav--dis--wrap" :class="{ active: isDisOpen }">
+      <div class="dis--btn" @click="isDisOpen = !isDisOpen">
+        disclosures <i class="ico"></i>
+      </div>
+      <div class="dis--cont">
+        <div class="container">
+          <p>
+            본 내비게이션 앱과 관련한 하드웨어는 (주) 엠핀스가, 소프트웨어는 ㈜케이티가
+            포드세일즈서비스코리아와의 공급계약을 통하여 제공하고 있습니다.<br /><br />본
+            내비게이션 앱의 하드웨어에 대해서는 (주) 엠핀스의 보증정책에 따라 (주)
+            엠핀스가 보증서비스를 제공합니다.<br /><br />본 내비게이션 앱의 소프트웨어를
+            통하여 제공하는 서비스 및 그 콘텐츠는 ㈜케이티에서 관리하며, ㈜케이티의
+            보증정책에 따라 ㈜케이티에서 보증서비스를 제공합니다.
+          </p>
+        </div>
+      </div>
+    </div> -->
+
+    <!-- 서비스 약관 모달 -->
+    <div v-if="showModal" class="modal--overlay" @click="showModal = false">
+      <div class="modal--content" @click.stop>
+        <h2>
+          포드 차량 “내비게이션 & 인포테인먼트 서비스” 이용약관
+          <button class="modal--close" @click="showModal = false"></button>
+        </h2>
+        <div class="modal--body">
+          <div class="list--wrap">
+            <h3>제 1조 (약관의 목적)</h3>
+            <p>
+              이 약관은 계약자가 포드세일즈서비스코리아 유한회사(이하 "회사")에서 제공하는
+              “내비게이션 & 인포테인먼트 서비스” (이하 "서비스")의 이용에 관한 일체의
+              관계에 적용합니다.
+            </p>
+          </div>
+          <div class="list--wrap">
+            <h3>제 2조 (약관의 적용 범위와 약관 외 준칙)</h3>
+            <p>
+              이 약관은 회사가 제공하는 서비스에 관하여 적용되고, 이 약관에 명시되지
+              아니한 사항에 대하여 해당 서비스 제공사 약관이 적용됩니다.
+            </p>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 3조 (약관의 명시와 개정)</h3>
+            <ul>
+              <li class="num--1">
+                회사는 약관의 내용과 상호, 주요 영업소 소재지, 서비스 관리자의 성명 및
+                연락처 등을 계약자가 알 수 있도록 홈페이지 (<a
+                  href="http://www.ford.co.kr/"
+                  target="_blank"
+                  >http://www.ford.co.kr/</a
+                >) (이하 통칭하여 ‘사이트’라 한다.)에 게시 혹은 링크 표시하여 명시합니다.
+                2022년 11월 현재 회사의 상호, 주소 및 대표 전화번호는 아래와 같습니다.
+              </li>
+              <div class="table--wrap mt--40 mb--40">
+                <table>
+                  <colgroup>
+                    <col style="width: 33.3333%" />
+                    <col style="width: 33.3333%" />
+                    <col style="width: 33.3333%" />
+                  </colgroup>
+                  <thead>
+                    <tr>
+                      <th>상호</th>
+                      <th>주소</th>
+                      <th>대표 전화번호</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr>
+                      <td>포드 세일즈 서비스 코리아 유한회사</td>
+                      <td>서울특별시 강남구 삼성로 511 (삼성동)</td>
+                      <td>1600-6003</td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <li class="num--2 mb--30">
+                회사는 약관을 개정하거나 변경할 경우, 적용 일자 및 개정 사유를 사이트에
+                명시하여 그 적용 일자 14일 이전부터 적용 일자 전일까지 공지하는 방법으로
+                이용자에게 통지합니다.
+              </li>
+              <li class="num--3">
+                회사는 사전 통지한 후 서비스와 관련한 주요 정책을 변경할 수 있습니다.
+              </li>
+            </ul>
+          </div>
+          <div class="list--wrap">
+            <h3>제 4 조 (용어의 정의)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                내비게이션 & 인포테인먼트 서비스 : 회사가 제공하는 인포테인먼트
+                서비스로서, 길안내 AI 음성인식, 날씨정보, 엔터테인먼트 서비스 등을 통칭함
+                (차종 별로 제공서비스 상이) (이하 "서비스"라 합니다.)
+              </li>
+
+              <li class="num--2 mb--30">
+                이용자 : 서비스를 제공 받기 위해 필요한 단말기가 장착된 차량구매하여
+                이용하는 자 이하 "이용자 "라 한다.
+              </li>
+
+              <li class="num--3 mb--30">
+                서비스 제공사 : 회사가 서비스를 제공하기 위하여 상호 계약을 체결한 서비스
+                제공사 (이하 "서비스 제공사"라 합니다.)
+              </li>
+
+              <li class="num--4 mb--30">
+                차량용 단말장치 : 회사가 서비스를 제공하기 위해 차량에 장착한 시스템 및
+                차량과의 연계 부분 일체 (이하 "단말장치"라 합니다.)
+              </li>
+
+              <li class="num--5">
+                포드 고객센터 : 회사가 서비스를 제공하기 위해 운영하는 상담센터
+              </li>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 5 조 (서비스의 내용)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                이 약관에 기초하여 계약자가 이용할 수 있는 서비스의 내용은 다음과 같으며
+                출시 시점에 따라 추가, 변경 또는 삭제될 수 있습니다.
+              </li>
+              <div class="table--wrap mt--40 mb--40">
+                <table>
+                  <colgroup>
+                    <col style="width: 33.3333%" />
+                    <col style="" />
+                  </colgroup>
+                  <thead>
+                    <tr>
+                      <th>구분</th>
+                      <th>서비스</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr>
+                      <td>AI 음성인식</td>
+                      <td>AI 음성인식 / 일반대화</td>
+                    </tr>
+                    <tr>
+                      <td>내비게이션</td>
+                      <td>길안내 / 주변 안내 서비스 / 카오너 서비스</td>
+                    </tr>
+                    <tr>
+                      <td>인포테인먼트</td>
+                      <td>
+                        뮤직 스트리밍 / 음성뉴스 / 팟케스트 / 인터넷 라디오/ 날씨 &amp;
+                        미세먼지 정보
+                      </td>
+                    </tr>
+                    <tr>
+                      <td>기타</td>
+                      <td>위키피디아 검색 / 감성채팅 / 번역 / 환율 / 운세 등</td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <li class="num--2 mb--30">
+                회사는 상당한 이유가 있는 경우에 운영상, 기술상의 필요에 따라 제공하고
+                있는 전부 또는 일부 서비스를 변경할 수 있습니다. 또한, 서비스 제공사의
+                사정 및 서비스 제공 가능 환경의 변화 등으로 인하여 서비스 제공을
+                중단하거나 변경할 수 있습니다.
+              </li>
+
+              <li class="num--3 mb--30">
+                차량용 단말장치는 자동차 전용으로서 내비게이션 & 인포테인먼트 서비스로
+                정의한 기능을 구현하기 위한 용도로 한정하여 사용됩니다.
+              </li>
+
+              <li class="num--4">
+                차량용 단말장치 장착시 이와 동시에 적용이 불가한 용품 또는 서비스가 있을
+                수 있습니다.
+              </li>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 6 조 (서비스의 일시 중단)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                회사는 설비의 보수점검, 교체 및 고장등의 사유가 발생한 경우, 서비스의
+                제공을 일시적으로 중단할 수 있습니다.
+              </li>
+              <li class="num--2 mb--30">
+                회사는 국가 비상사태, 정전, 서비스 설비의 장애 또는 서비스 이용의 폭주
+                등으로 정상적인 서비스 제공이 불가능할 경우, 서비스의 전부 또는 일부를
+                제한하거나 중지할 수 있습니다.
+              </li>
+              <li class="num--3 mb--30">
+                회사는 다음 각 호의 1 에 해당하는 경우 서비스 제공을 일시적으로 중단할 수
+                있습니다.
+                <ul class="pt--30">
+                  <li class="num--1 mb--30">
+                    시스템 보수를 정기적으로, 또는 긴급히 행하는 경우
+                  </li>
+
+                  <li class="num--2 mb--30">
+                    화재, 정전 등에 따라 서비스 제공을 할 수 없게 된 경우
+                  </li>
+
+                  <li class="num--3 mb--30">
+                    천재지변 또는 전쟁, 폭동, 소란, 노동쟁의 등에 의해 서비스 제공을 할 수
+                    없는 경우
+                  </li>
+
+                  <li class="num--4 mb--30">서비스 제공사에 의해 서비스가 정지된 경우</li>
+
+                  <li class="num--5">
+                    그 외, 운용 또는 기술상, 회사가 서비스 제공의 일시적인 중단이
+                    필요하다고 판단한 경우
+                  </li>
+                </ul>
+              </li>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 7 조 (서비스의 중단 및 계약 종료시 대책)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                회사는 서비스 제공사와 계약이 지속되어 계속 서비스가 제공될 수 있도록
+                노력합니다
+              </li>
+
+              <li class="num--2 mb--30">
+                서비스 제공이 중단될 경우 회사는 홈페이지 게시 또는 단말장치에 내용 고지,
+                개별 문자발송 등을 통해 30 일 이내 사전 통지의 의무를 수행해야 합니다.
+              </li>
+
+              <li class="num--3">
+                이용자는 전쟁, 폭동, 화재, 정전, 천재지변 등 예측이 불가능한 돌발 상황의
+                경우, 사전 통지 없이 서비스가 일시 중단, 지연될 수 있음을 인정합니다.
+              </li>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 8 조 (정보갱신의 조건)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                교통정보는 정보를 수집, 가공, 전송하는 절차를 거쳐 제공됨으로 실제시간의
+                정보와 다를 수 있습니다.
+              </li>
+
+              <li class="num--2 mb--30">
+                도로정보는 현지 실사, 시스템 반영, 최적화 과정을 거친 후 제공되는 것으로
+                실제 사용시간의 도로정보와 차이가 발생할 수 있습니다.
+              </li>
+
+              <li class="num--3 mb--30">
+                각종 부가정보 컨텐츠는 정기, 비정기적으로 정보를 갱신하여 제공하는 것으로
+                실제 사용시간의 상황과 다를 수 있습니다.
+              </li>
+
+              <li class="num--4">
+                제 1 항 내지 제 3 항과 관련된 부가 컨텐츠는 이를 제공하는 회사의 사정에
+                의해 일시중단, 지연되거나 혹은 제공되지 아니할 수 있습니다.
+              </li>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 9 조 (주행 중 일부기능 정지 및 제한)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                주행 중에는 안전운전에 지장을 주는 단말장치 조작 방지를 위해 일부 서비스는
+                제공되지 않습니다.
+              </li>
+
+              <li class="num--2">
+                회사가 정한 기능 이외에 임의 조작으로 인해 야기된 문제에 대해 회사는
+                책임을 면합니다.
+              </li>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 10 조 (보증 및 운영)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                차량용 단말장치의 단말기(하드웨어)에 대하여는 엠핀스가 보증책임을 가지며,
+                회사의 용품 보증 조건 및 기간(출고 후 1 년 이내 및 주행거리 2 만 km
+                이내)에 따라 보증을 시행하며, 엠핀스에서 A/S 를 위탁 처리합니다.
+              </li>
+              <li class="num--2">
+                차량용 소프트웨어에 대하여는 KT 가 운영하며, KT 의 보증 정책에 따라 A/S
+                합니다.
+              </li>
+              <div class="table--wrap mt--40 mb--40">
+                <table>
+                  <colgroup>
+                    <col style="" />
+                    <col style="" />
+                    <col style="" />
+                    <col style="" />
+                    <col style="" />
+                  </colgroup>
+                  <thead>
+                    <tr>
+                      <th>서비스 종류</th>
+                      <th>제공받는자</th>
+                      <th>위치정보 수집여부</th>
+                      <th>수집항목</th>
+                      <th>보유기간</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr>
+                      <td>소프트웨어 A/S</td>
+                      <td>KT</td>
+                      <td>X</td>
+                      <td>
+                        AS, CS 를 위한 디바이스 정보(Serial, IMEI, ICCID),<br />
+                        무상 보증을 위한 출고일 정보
+                      </td>
+                      <td>판매 후 5 년&nbsp; &nbsp;</td>
+                    </tr>
+                    <tr>
+                      <td>단말 A/S</td>
+                      <td>엠핀스</td>
+                      <td>X</td>
+                      <td>
+                        AS, CS 를 위한 디바이스 정보(Serial, IMEI, ICCID),<br />
+                        무상 보증을 위한 출고일 정보
+                      </td>
+                      <td>판매 후 1 년</td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 11 조 (분쟁의 처리)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                회사는 서비스의 원활한 진행과 신속한 고객 불만 처리를 위해 담당자를
+                지정하며 [별표 1]가 같이 운영합니다.
+              </li>
+
+              <li class="num--2 mb--30">
+                이용자의 민원사항은 계약자보호위원회를 통하여 접수 즉시 처리하고
+                처리기간이 소요되는 사항은 소요기간을 해당 계약자에게 통지합니다.
+              </li>
+
+              <li class="num--3">
+                회사와 이용자간에 발생하는 분쟁이 전항에 의하여 해결되지 아니하는 경우
+                민사소송법상의 관할법원에 제기합니다.
+              </li>
+            </ul>
+          </div>
+          <div class="list--wrap">
+            <h3>제 12 조 (개인정보 보호 정책의 수립 및 시행)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                계약자의 개인정보 보호에 관해서는 관련법령 및 회사가 정하는
+                "개인정보처리방침"에 정한 바에 의합니다.
+              </li>
+            </ul>
+          </div>
+          <div class="list--wrap">
+            <h3>제 13 조 (약관 동의서 수집 및 저장 방식)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                차량용 단말장치 기동 후 초기화면에 동의함으로써 서비스에 대해 동의하는
+                것으로 간주합니다.
+              </li>
+            </ul>
+          </div>
+          <div class="list--wrap">
+            <h3>부 칙</h3>
+            <ul>
+              <li class="mb--30 pl--0">
+                (시행일) 이 약관은 2022 년 11 월 28 일부터 시행합니다.
+              </li>
+              <li class="mb--30 pl--0">[별표 1] 이용자 불만 유형별 처리 대책</li>
+            </ul>
+          </div>
+          <div class="list--wrap">
+            <ul>
+              <li class="num--1 mb--30">
+                이용자 문의/불만 처리 절차<br />회사는 고객의 불편을 최소화하고, 고객이
+                서비스를 원활하게 이용할 수 있도록 각종 민원에 대해 다음과 같은 기본
+                원칙으로 이용자에게 안내합니다.
+                <ul>
+                  <li class="num--1 mb--30 mt--30">
+                    서비스 이용 방법등 간단하고 유형화된 문의에 대해서는 즉시 또는 접수
+                    당일 고객에게 안내합니다.
+                  </li>
+                  <li class="num--2 mb--30">
+                    회사 내부의 분석, 검토, 개선 또는 의사결정이 필요한 민원에 대해서는
+                    처리 방향을 먼저 안내한 후, 접수일로부터 10 일 이내에 고객에게
+                    답변합니다.
+                  </li>
+                </ul>
+              </li>
+              <li class="num--2">이용자 불만 유형별 처리 대책</li>
+              <div class="table--wrap mt--40 mb--40">
+                <table>
+                  <colgroup>
+                    <col style="width: 15.3333%" />
+                    <col style="" />
+                  </colgroup>
+                  <thead>
+                    <tr>
+                      <th>불만의 유형</th>
+                      <th>처리 대책</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr>
+                      <td>서비스 작동·품질</td>
+                      <td>
+                        유형화된 민원의 경우 상담사가 즉시 또는 당일 처리하고, 관련
+                        시스템의 분석 등 해결에 일정 시간이 소요되는 민원의 경우
+                        접수일로부터 5 일 이내 민원의 원인을 설명하고 10 일 이내 해결을
+                        원칙으로 함. 단, 기술적으로 세부적인 검토가 필요한 내용인 경우 10
+                        일 이내 민원의 원인을 설명하고 해결이 불가능한 경우에는 그 사유를
+                        고객에게 설명함.
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+            </ul>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 아틀란 네비게이션 모달 -->
+    <div v-if="showModal2" class="modal--overlay" @click="showModal2 = false">
+      <div class="modal--content" @click.stop>
+        <h2>
+          아틀란3D지도 탑재 FLS-250A 단말 “내비게이션 서비스” 이용약관
+          <button class="modal--close" @click="showModal2 = false"></button>
+        </h2>
+        <div class="modal--body">
+          <div class="list--wrap">
+            <h3>제 1조 (약관의 목적)</h3>
+            <p>
+              이 약관은 계약자가 에프엘오토코리아 유한회사(이하 "회사")에서 제공하는
+              “내비게이션 서비스” (이하 "서비스")의 이용에 관한 일체의 관계에 적용합니다.
+            </p>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 2조 (약관의 적용 범위와 약관 외 준칙)</h3>
+            <p>
+              이 약관은 회사가 제공하는 서비스에 관하여 적용되고, 이 약관에 명시되지
+              아니한 사항에 대하여 해당 서비스 제공사 약관이 적용됩 니다.
+            </p>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 3조 (약관의 명시와 개정)</h3>
+            <p>
+              회사는 약관의 내용과 상호, 주요 영업소 소재지, 서비스 관리자의 성명 및
+              연락처 등을 계약자가 알 수 있도록 홈페이지
+              <a href="http://www.flak.co.kr" target="_blank">(http://www.flak.co.kr/)</a>
+              (이하 통칭하여 ‘사이트’라 한다.)에 게시 혹은 링크 표시하여 명시합니다.
+              2026년 01월 현재 회사의 상 호, 주소 및 대표 전화번호는 아래와 같습니다.
+            </p>
+          </div>
+
+          <div class="list--wrap">
+            <ul>
+              <div class="table--wrap mt--40 mb--40">
+                <table>
+                  <colgroup>
+                    <col style="width: 33.3333%" />
+                    <col style="width: 33.3333%" />
+                    <col style="width: 33.3333%" />
+                  </colgroup>
+                  <thead>
+                    <tr>
+                      <th>상호</th>
+                      <th>주소</th>
+                      <th>대표 전화번호</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr>
+                      <td>에프엘오토코리아 유한회사</td>
+                      <td>서울 성동구 자동차시장 1길 104-13, 10층</td>
+                      <td>1600-6003</td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <li class="num--2 mb--30">
+                회사는 약관을 개정하거나 변경할 경우, 적용 일자 및 개정 사유를 사이트에
+                명시하여 그 적용 일자 14일 이전부터 적용 일자 전일까지 공지하는 방법으로
+                이용자에게 통지합니다.
+              </li>
+              <li class="num--3">
+                회사는 사전 통지한 후 서비스와 관련한 주요 정책을 변경할 수 있습니다.
+              </li>
+            </ul>
+          </div>
+          <div class="list--wrap">
+            <h3>제 4 조 (용어의 정의)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                내비게이션 서비스 : 회사가 제공하는 길안내 서비스를 의미함. (이하
+                "서비스"라 합니다.)
+              </li>
+
+              <li class="num--2 mb--30">
+                이용자 : 서비스를 제공받기 위해 필요한 단말기가 장착된 차량을 구매하여
+                이용하는 자 이하 "이용자 "라 한다.
+              </li>
+
+              <li class="num--3 mb--30">
+                서비스 제공사 : 회사가 서비스를 제공하기 위하여 상호 계약을 체결한 서비스
+                제공사 (이하 "서비스 제공사"라 합니다.)
+              </li>
+
+              <li class="num--4 mb--30">
+                차량용 단말장치 : 회사가 서비스를 제공하기 위해 차량에 장착한 시스템 및
+                차량과의 연계 부분 일체 (이하 "단말장치"라 합니 다.)
+              </li>
+
+              <li class="num--5">
+                포드 고객센터 : 회사가 서비스를 제공하기 위해 운영하는 상담센터
+              </li>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 5 조 (서비스의 내용)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                이 약관에 기초하여 계약자가 이용할 수 있는 서비스의 내용은 다음과 같으며
+                출시 시점에 따라 추가, 변경 또는 삭제될 수 있습니다.
+              </li>
+              <div class="table--wrap mt--40 mb--40">
+                <table>
+                  <colgroup>
+                    <col style="width: 33.3333%" />
+                    <col style="" />
+                  </colgroup>
+                  <thead>
+                    <tr>
+                      <th>구분</th>
+                      <th>서비스</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr>
+                      <td>내비게이션</td>
+                      <td>길안내 / 주변 안내 서비스</td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <li class="num--2 mb--30">
+                회사는 불가피한 사유가 있는 경우에 운영상, 기술상의 필요에 따라 제공하고
+                있는 전부 또는 일부 서비스를 변경할 수 있습니 다. 또한, 서비스 제공사의
+                사정 및 서비스 제공 가능 환경의 변화 등으로 인하여 서비스 제공을
+                중단하거나 변경할 수 있습니 다.
+              </li>
+
+              <li class="num--3 mb--30">
+                차량용 단말장치는 자동차 전용으로서 내비게이션 서비스로 정의한 기능을
+                구현하기 위한 용도로 한정하여 사용됩니다.
+              </li>
+
+              <li class="num--4">
+                차량용 단말장치 장착시 이와 동시에 적용이 불가한 용품 또는 서비스가 있을
+                수 있습니다.
+              </li>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 6 조 (서비스의 일시 중단)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                회사는 설비의 보수점검, 교체 및 고장등의 사유가 발생한 경우, 서비스의
+                제공을 일시적으로 중단할 수 있습니다.
+              </li>
+              <li class="num--2 mb--30">
+                회사는 국가 비상사태, 정전, 서비스 설비의 장애 또는 서비스 이용의 폭주
+                등으로 정상적인 서비스 제공이 불가능할 경우, 서 비스의 전부 또는 일부를
+                제한하거나 중지할 수 있습니다.
+              </li>
+              <li class="num--3 mb--30">
+                회사는 다음 각 호의 1 에 해당하는 경우 서비스 제공을 일시적으로 중단할 수
+                있습니다.
+                <ul class="pt--30">
+                  <li class="num--1 mb--30">
+                    시스템 보수를 정기적으로, 또는 긴급히 행하는 경우
+                  </li>
+
+                  <li class="num--2 mb--30">
+                    화재, 정전 등에 따라 서비스 제공을 할 수 없게 된 경우
+                  </li>
+
+                  <li class="num--3 mb--30">
+                    천재지변 또는 전쟁, 폭동, 소란, 노동쟁의 등에 의해 서비스 제공을 할 수
+                    없는 경우
+                  </li>
+
+                  <li class="num--4 mb--30">서비스 제공사에 의해 서비스가 정지된 경우</li>
+
+                  <li class="num--5">
+                    그 외, 운용 또는 기술상, 회사가 서비스 제공의 일시적인 중단이
+                    필요하다고 판단한 경우
+                  </li>
+                </ul>
+              </li>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 7 조 (서비스의 중단 및 계약 종료시 대책)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                회사는 서비스 제공사와 계약이 지속되어 계속 서비스가 제공될 수 있도록
+                노력합니다
+              </li>
+
+              <li class="num--2 mb--30">
+                서비스 제공이 중단될 경우 회사는 홈페이지 게시 또는 단말장치에 내용 고지,
+                개별 문자발송 등을 통해 30 일 이내 사전 통지 의 의무를 수행해야 합니다.
+              </li>
+
+              <li class="num--3">
+                이용자는 전쟁, 폭동, 화재, 정전, 천재지변 등 예측이 불가능한 돌발 상황의
+                경우, 사전 통지 없이 서비스가 일시 중단, 지연될 수 있음을 인정합니다.
+              </li>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 8 조 (정보갱신의 조건)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                교통정보는 정보를 수집, 가공, 전송하는 절차를 거쳐 제공됨으로 실제 시간의
+                정보와 다를 수 있습니다.
+              </li>
+
+              <li class="num--2 mb--30">
+                도로정보는 현지 실사, 시스템 반영, 최적화 과정을 거친 후 제공되는 것으로
+                실제 사용시간의 도로정보와 차이가 발생할 수 있 습니다.
+              </li>
+
+              <li class="num--3 mb--30">
+                각종 부가정보 컨텐츠는 정기, 비정기적으로 정보를 갱신하여 제공하는 것으로
+                실제 사용시간의 상황과 다를 수 있습니다.
+              </li>
+
+              <li class="num--4">
+                제 1 항 내지 제 3 항과 관련된 부가 컨텐츠는 이를 제공하는 회사의 사정에
+                의해 일시중단, 지연되거나 혹은 제공되지 아니할 수 있습니다.
+              </li>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 9 조 (주행 중 일부기능 정지 및 제한)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                주행 중에는 안전운전에 지장을 주는 단말장치 조작 방지를 위해 일부 서비스는
+                제공되지 않습니다.
+              </li>
+
+              <li class="num--2">
+                회사가 정한 기능 외의 이용자 임의 조작으로 인해 직접적으로 발생한 장애
+                또는 손해와 그로 인해 야기된 문제에 대해 회사 는 책임을 면합니다.
+              </li>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 10 조 (보증 및 운영)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                내비게이션 서비스 제공에 필요한 차량용 단말기에 대하여는 ㈜엠핀스가
+                보증책임을 가지며, 회사의 용품 보증 조건 및 기간 (출고 후 1 년 이내 및
+                주행거리 2 만 km 이내)에 따라 보증을 시행하며, ㈜엠핀스에서 A/S 를 위탁
+                처리합니다.
+              </li>
+              <li class="num--2">
+                단말기에 탑재된 아틀란3D 지도 소프트웨어의 관리 및 데이터 업데이트
+                서비스는 지도 제작사인 ㈜맵퍼스에서 운영하며, 연동 된 단말의 단종일로부터
+                최소 5년간 무상으로 지도 업데이트를 제공합니다. (아틀란3D 공식 홈페이지
+                <a href="https://www.atlan.co.kr" target="_blank">www.atlan.co.kr</a> 내
+                회원 가입 및 단말 시리얼 등록 후 차량에 장착된 단말기 내 MicroSD를 PC에
+                연결 후 PC용 업데이트 프로그램을 통해 지도 업데이 트 가능)
+              </li>
+              <div class="table--wrap mt--40 mb--40">
+                <table>
+                  <colgroup>
+                    <col style="" />
+                    <col style="" />
+                    <col style="" />
+                    <col style="" />
+                    <col style="" />
+                  </colgroup>
+                  <thead>
+                    <tr>
+                      <th>서비스 종류</th>
+                      <th>제공받는자</th>
+                      <th>위치정보 수집여부</th>
+                      <th>수집항목</th>
+                      <th>보유기간</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr>
+                      <td>단말 A/S 보증기간<br />(소프트웨어 포함)</td>
+                      <td>엠핀스</td>
+                      <td>X</td>
+                      <td>
+                        AS, CS 를 위한 디바이스 정보(Serial, IMEI, ICCID),<br />
+                        무상 보증을 위한 출고일 정보
+                      </td>
+                      <td>판매 후 1 년&nbsp; &nbsp;</td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+            </ul>
+          </div>
+
+          <div class="list--wrap">
+            <h3>제 11 조 (분쟁의 처리)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                회사는 서비스의 원활한 진행과 신속한 고객 불만 처리를 위해 담당자를
+                지정하며 [별표 1]가 같이 운영합니다.
+              </li>
+
+              <li class="num--2 mb--30">
+                이용자의 민원사항은 계약자보호위원회를 통하여 접수 즉시 처리하고
+                처리기간이 소요되는 사항은 소요기간을 해당 계약자에 게 통지합니다.
+              </li>
+
+              <li class="num--3">
+                회사와 이용자 간에 발생하는 분쟁이 전항에 의하여 해결되지 아니하는 경우
+                민사소송법상의 관할법원에 제기합니다.
+              </li>
+            </ul>
+          </div>
+          <div class="list--wrap">
+            <h3>제 12 조 (개인정보 보호 정책의 수립 및 시행)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                계약자의 개인정보 보호에 관해서는 관련법령 및 회사가 정하는
+                "개인정보처리방침"에 정한 바에 의합니다.
+              </li>
+            </ul>
+          </div>
+          <div class="list--wrap">
+            <h3>제 13 조 (약관 동의서 수집 및 저장 방식)</h3>
+            <ul>
+              <li class="num--1 mb--30">
+                차량용 단말장치 기동 후 초기화면에 동의함으로써 서비스에 대해 동의하는
+                것으로 간주합니다<a href="https://flak.co.kr" target="_blank"
+                  >(https://flak.co.kr)</a
+                >
+                .
+              </li>
+            </ul>
+          </div>
+          <div class="list--wrap">
+            <h3>부 칙</h3>
+            <ul>
+              <li class="mb--30 pl--0">
+                (시행일) 이 약관은 2025 년 12 월 01 일부터 시행합니다.
+              </li>
+              <li class="mb--30 pl--0">[별표 1] 이용자 불만 유형별 처리 대책</li>
+            </ul>
+          </div>
+          <div class="list--wrap">
+            <ul>
+              <li class="num--1 mb--30">
+                이용자 문의/불만 처리 절차<br />회사는 고객의 불편을 최소화하고, 고객이
+                서비스를 원활하게 이용할 수 있도록 각종 민원에 대해 다음과 같은 기본
+                원칙으로 이용자에게 안내합니다.
+                <ul>
+                  <li class="num--1 mb--30 mt--30">
+                    서비스 이용 방법 등 간단하고 유형화된 문의에 대해서는 즉시 또는 접수
+                    당일 고객에게 안내합니다.
+                  </li>
+                  <li class="num--2 mb--30">
+                    회사 내부의 분석, 검토, 개선 또는 의사결정이 필요한 민원에 대해서는
+                    처리 방향을 먼저 안내한 후, 접수일로부터 10 일 이내에 고객에게
+                    답변합니다.
+                  </li>
+                </ul>
+              </li>
+              <li class="num--2">이용자 불만 유형별 처리 대책</li>
+              <div class="table--wrap mt--40 mb--40">
+                <table>
+                  <colgroup>
+                    <col style="width: 15.3333%" />
+                    <col style="" />
+                  </colgroup>
+                  <thead>
+                    <tr>
+                      <th>불만의 유형</th>
+                      <th>처리 대책</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr>
+                      <td>서비스 작동·품질</td>
+                      <td>
+                        유형화된 민원의 경우 상담사가 즉시 또는 당일 처리하고, 관련
+                        시스템의 분석 등 해결에 일정 시간이 소요되는 민원의 경우 접
+                        수일로부터 5 일 이내 민원의 원인을 설명하고 10 일 이내 해결을
+                        원칙으로 함. 단, 기술적으로 세부적인 검토가 필요한 내용인 경우 10
+                        일 이내 민원의 원인을 설명하고 해결이 불가능한 경우에는 그 사유를
+                        고객에게 설명함.
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+            </ul>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+
+  // 서비스 약관 모달
+  const showModal = ref(false);
+  const showModal2 = ref(false);
+
+  // disclosures 토글 (기본 열림)
+  const isDisOpen = ref(true);
+
+  // 드롭메뉴 관련 (기본 모두 열림)
+  const dropMenuOpen = ref([true, true, true]);
+
+  const toggleDropMenu = (index) => {
+    dropMenuOpen.value[index] = !dropMenuOpen.value[index];
+  };
+
+  // 샘플 데이터
+  const breadcrumbData = {
+    mainMenu: "OWNER",
+    currentSubMenu: "서비스",
+    subMenuItems: [
+      { label: "공인 서비스", to: "/ford/owner", active: false },
+      {
+        label: "소모성 부품 무상 교환 서비스",
+        to: "/ford/owner/consumableParts",
+        active: true,
+      },
+      { label: "24시간 긴급 출동 서비스", to: "/ford/owner/tfService", active: false },
+      { label: "순정 부품", to: "/ford/owner/genuine", active: false },
+      { label: "리콜 안내", to: "/ford/owner/recall", active: false },
+      {
+        label: "사고 수리를 위한 포드 서비스 부품",
+        to: "/ford/owner/accident",
+        active: false,
+      },
+      { label: "내비게이션", to: "/ford/owner/navigation", active: false },
+    ],
+  };
+</script>

+ 101 - 0
app/pages/ford/owner/recall.vue

@@ -0,0 +1,101 @@
+<template>
+  <div>
+    <breadCrumbs
+      width="220"
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="ovwner--wrapper type--2 pb--0">
+      <div class="inner--wrap">
+        <div class="title--visual">
+          <h2>리콜 안내</h2>
+          <!-- <div class="desc mt--40">
+            포드코리아 리콜 안내입니다. 아래 링크를 통해서 자신이 소유한 차량의 리콜
+            대상여부를 확인할 수 있습니다.
+          </div> -->
+        </div>
+        <div class="thums--wrap mt--70">
+          <img src="/img/owner/recall-visual.jpg" alt="" />
+        </div>
+      </div>
+
+      <div class="consumable--parts--wrap">
+        <div class="inner--wrap">
+          <div class="prm--service pt--120">
+            <h2>리콜 제도란?</h2>
+            <div class="sub--title">
+              리콜 제도는 제품의 결함으로 인해 소비자의 신체 또는 재산상의 위해를 끼치거나
+              끼칠 우려가 있는 제품에<br />
+              대하여 제품을 제조, 수입 또는 유통시킨 자가 제품의 문제점에 대해 소비자에게
+              통지하고 관련 제품을 신속하게 수리 교환 등의 적절한<br />
+              조치를 취함으로써 안전과 관련된 사고와 소비자 피해를 사전에 예방하고 재발을
+              방지하는 제도입니다.<br />
+              리콜제도는 제작자의 자발적인 리콜과 강제적인 리콜로 나눌 수 있으며, 리콜
+              제도가 활발하게 이루어지고 있는 미국의 경우 리콜 중<br />
+              95%가 자발적인 리콜로 이루어지고 있을 정도로 강제적인 리콜보다는 제작자
+              스스로가 시행하는 자발적 리콜이 대부분을 차지하고 있습니다.
+            </div>
+            <div class="btn--wrap mt--70 mb--160">
+              <NuxtLink
+                style="width: 230px"
+                class="btn--sky"
+                to="https://www.car.go.kr/ri/recall/list.do"
+                target="_blank"
+                >리콜 대상 확인하기<i class="ico"></i
+              ></NuxtLink>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="nav--dis--wrap" :class="{ active: isDisOpen }">
+      <div class="dis--btn" @click="isDisOpen = !isDisOpen">
+        disclosures <i class="ico"></i>
+      </div>
+      <div class="dis--cont">
+        <div class="container">
+          <p>[1] 자세한 사항은 가까운 전시장 및 서비스센터에 문의하세요.</p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+
+  // disclosures 토글 (기본 열림)
+  const isDisOpen = ref(true);
+
+  // 탭 관련
+  const activeTab = ref(0);
+  const tabItems = ["공인 서비스", "차량관리 서비스", "서비스 센터에서의 관리"];
+
+  const setActiveTab = (index) => {
+    activeTab.value = index;
+  };
+
+  // 샘플 데이터
+  const breadcrumbData = {
+    mainMenu: "OWNER",
+    currentSubMenu: "서비스",
+    subMenuItems: [
+      { label: "공인 서비스", to: "/ford/owner", active: false },
+      {
+        label: "소모성 부품 무상 교환 서비스",
+        to: "/ford/owner/consumableParts",
+        active: true,
+      },
+      { label: "24시간 긴급 출동 서비스", to: "/ford/owner/tfService", active: false },
+      { label: "순정 부품", to: "/ford/owner/genuine", active: false },
+      { label: "리콜 안내", to: "/ford/owner/recall", active: false },
+      {
+        label: "사고 수리를 위한 포드 서비스 부품",
+        to: "/ford/owner/accident",
+        active: false,
+      },
+      { label: "내비게이션", to: "/ford/owner/navigation", active: false },
+    ],
+  };
+</script>

+ 325 - 0
app/pages/ford/owner/tfService.vue

@@ -0,0 +1,325 @@
+<template>
+  <div>
+    <breadCrumbs
+      :width="219"
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="ovwner--wrapper pb--0">
+      <div class="inner--wrap">
+        <div class="title--visual">
+          <h2>24시간 긴급 출동 서비스</h2>
+          <div class="desc mt--40">
+            <!-- 운전 중 예상치 못한 긴급한 상황이 발생하더라도, 귀하의 곁에 저희 포드가 -->
+            <!-- 언제든지 달려가 도움을 드립니다.<br /> -->
+            <!-- 저희 포드코리아가 수입한 차량을 구매하신 고객께는 다양한 항목의 포드 긴급출동
+            서비스가 365일, 24시간 제공됩니다. -->
+          </div>
+        </div>
+        <div class="dbl--info--cont pt--70">
+          <ul>
+            <li>
+              <div class="thumb">
+                <svg
+                  xmlns="http://www.w3.org/2000/svg"
+                  width="40"
+                  height="40"
+                  viewBox="0 0 40 40"
+                  fill="none"
+                >
+                  <path
+                    d="M12.0508 2.40039H27.6059"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-miterlimit="10"
+                    stroke-linecap="round"
+                  />
+                  <path
+                    d="M15.8143 2.40039L1.27588 23.0572"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-miterlimit="10"
+                    stroke-linecap="round"
+                  />
+                  <path
+                    d="M23.6367 2.40039L38.1751 23.0572"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-miterlimit="10"
+                    stroke-linecap="round"
+                  />
+                  <path
+                    d="M38.101 33.6719H1.89859C1.29175 33.6719 0.799805 34.167 0.799805 34.7778V36.4943C0.799805 37.1051 1.29175 37.6002 1.89859 37.6002H38.101C38.7079 37.6002 39.1998 37.1051 39.1998 36.4943V34.7778C39.1998 34.167 38.7079 33.6719 38.101 33.6719Z"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                  <path
+                    d="M11.3614 32.4977C12.806 32.4977 13.9771 31.319 13.9771 29.8651C13.9771 28.4111 12.806 27.2324 11.3614 27.2324C9.91672 27.2324 8.74561 28.4111 8.74561 29.8651C8.74561 31.319 9.91672 32.4977 11.3614 32.4977Z"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                  <path
+                    d="M27.3023 32.4977C28.7469 32.4977 29.918 31.319 29.918 29.8651C29.918 28.4111 28.7469 27.2324 27.3023 27.2324C25.8576 27.2324 24.6865 28.4111 24.6865 29.8651C24.6865 31.319 25.8576 32.4977 27.3023 32.4977Z"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                  <path
+                    d="M24.6856 29.8652H13.9766"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                  <path
+                    d="M8.75392 29.8672H6.43336C6.43336 29.8672 3.875 30.1891 3.875 26.9787C3.875 24.3131 5.62157 22.3654 7.36815 21.8702C9.11472 21.3751 9.87731 21.4989 12.3865 19.1716C12.3865 19.1716 16.8554 14.748 23.7023 14.748C30.5492 14.748 33.4191 20.0299 34.0013 21.4246C34.5835 22.8193 35.2149 24.9898 35.2149 27.2346C35.2149 29.4793 34.3211 29.859 33.0173 29.8672C31.7136 29.8755 29.9178 29.8672 29.9178 29.8672"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                  <path
+                    d="M11.6729 22.3164H31.4017"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                  <path
+                    d="M20.1934 22.3164V29.876"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                  <path
+                    d="M5.61337 23.2148C5.61337 23.2148 7.43374 24.4775 6.61376 26.0951C5.79377 27.7126 4.47359 27.1679 3.875 27.234"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                  <path
+                    d="M17.1426 19.641C17.1426 19.641 19.2499 17.1074 23.4893 17.1074C27.7286 17.1074 28.6224 19.8226 28.6224 19.8226"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                  <path
+                    d="M23.4814 17.0996V19.3444"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                </svg>
+              </div>
+              <div class="desc">
+                <h2>전시장 및 서비스센터 찾기</h2>
+                <NuxtLink class="location--btn" to="/ford/network">
+                  바로가기
+                  <i>
+                    <svg
+                      xmlns="http://www.w3.org/2000/svg"
+                      width="16"
+                      height="16"
+                      viewBox="0 0 16 16"
+                      fill="none"
+                    >
+                      <path
+                        d="M3.33301 8H12.6663"
+                        stroke="#3B3B3B"
+                        stroke-width="1.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                      <path
+                        d="M8 3.33398L12.6667 8.00065L8 12.6673"
+                        stroke="#3B3B3B"
+                        stroke-width="1.5"
+                        stroke-linecap="round"
+                        stroke-linejoin="round"
+                      />
+                    </svg>
+                  </i>
+                </NuxtLink>
+              </div>
+            </li>
+            <li>
+              <div class="thumb">
+                <svg
+                  xmlns="http://www.w3.org/2000/svg"
+                  width="40"
+                  height="40"
+                  viewBox="0 0 40 40"
+                  fill="none"
+                >
+                  <path
+                    d="M20 3C29.3732 3 37.0001 10.6269 37.0001 20.0001"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                  <path
+                    d="M20 8.45312C26.3642 8.45312 31.5473 13.6311 31.5473 20.0004"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                  <path
+                    d="M20 14.5332C23.0141 14.5332 25.4681 16.9872 25.4681 20.0013"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                  <path
+                    d="M31.318 31.7402C31.262 32.1831 31.0532 32.6261 30.7732 32.9061L28.7112 34.9732C21.6851 40.8537 12.765 32.0915 12.6733 32.0049L8.00962 27.3361C7.92306 27.2445 -0.844298 18.3295 5.01079 11.3237L7.10844 9.22607C7.38338 8.95113 7.83142 8.74239 8.26928 8.68129C8.35583 8.67111 8.43729 8.66602 8.51875 8.66602C8.8446 8.66602 9.10935 8.75257 9.2621 8.90531L13.6203 13.9356C14.1447 14.46 14.1447 15.3052 13.6203 15.8296L12.0318 17.4232C11.6398 17.8458 11.3648 18.2429 11.1866 18.6451C10.8404 19.5972 10.2855 22.0869 12.6784 24.6631L15.3565 27.3463C17.9174 29.724 20.4071 29.169 21.3898 28.8126C21.7563 28.6497 22.1586 28.3697 22.5913 27.9674L24.1697 26.3891C24.6737 25.8851 25.5291 25.8545 26.0891 26.4146L31.0736 30.7321C31.2823 30.951 31.3689 31.3176 31.3129 31.7402H31.318Z"
+                    stroke="white"
+                    stroke-width="1.5"
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                  />
+                </svg>
+              </div>
+              <div class="desc">
+                <h2>24시간 긴급 출동 서비스 콜센터</h2>
+                <div class="phone">080-300-3673</div>
+              </div>
+            </li>
+          </ul>
+        </div>
+        <div class="thums--wrap mt--70">
+          <img src="/img/owner/tfs-visual01.jpg" alt="" />
+        </div>
+      </div>
+      <div class="tfs--drop--menus pt--90 pb--150">
+        <ul>
+          <li :class="{ open: dropMenuOpen[0] }">
+            <div class="title" @click="toggleDropMenu(0)">제공 서비스</div>
+            <div class="drop--contents">
+              <h2>긴급 견인 서비스</h2>
+              <div>
+                차량의 이상으로 인해 운행이 불가한 경우, 발생지점으로부터 50 km 범위 내
+                가장 가까운 포드 공식서비스센터로의 무상 견인서비스를 제공해 드립니다. 단,
+                사고로 인한 견인 서비스는 유상입니다.
+              </div>
+              <h2>배터리 충전 서비스</h2>
+              <div>
+                배터리 방전으로 운행이 불가한 경우, 임시 배터리 충전서비스(월 2회 한함)를
+                제공해 드립니다.
+              </div>
+              <h2>타이어 교체 서비스</h2>
+              <div>
+                타이어 파손으로 교체가 필요한 경우, 고객 차량에 장착된 예비타이어로 교체해
+                드립니다.
+              </div>
+              <h2>비상 연료 또는 요소수액 보충 서비스</h2>
+              <div style="text-transform: uppercase">
+                연료 또는 요소수액 소진에 따른 운행이 불가한 경우, 비상연료 또는 요소수액
+                보충 서비스(월 1회 한함)를 제공해 드립니다. 다만, 제공되는 보충량은 최대
+                10L이나, 사정에 따라 제공량은 변동될 수 있습니다. 아울러, 요소수액
+                보충서비스는 경유차량에 한합니다.
+              </div>
+              <p>
+                제공되는 서비스 범위 및 내용은 당사 서비스정책에 따라 별도의 사전고지없이
+                변경될 수 있습니다.
+              </p>
+            </div>
+          </li>
+          <li :class="{ open: dropMenuOpen[1] }">
+            <div class="title" @click="toggleDropMenu(1)">서비스 제공 시간</div>
+            <div class="drop--contents">
+              <div class="solo">
+                차량 소유권 변동에 관계없이 주행거리 6만km 이내이면, 해당 서비스를
+                이용하실 수 있습니다.
+              </div>
+            </div>
+          </li>
+          <li :class="{ open: dropMenuOpen[2] }">
+            <div class="title" @click="toggleDropMenu(2)">서비스 제공 조건</div>
+            <div class="drop--contents">
+              <h3>
+                24시간 긴급출동 서비스 이용을 위해서는 다음의 사항이 준수되어야 합니다.
+              </h3>
+              <ul>
+                <li>
+                  24시간 긴급출동 서비스 콜센터에 요청하시는 서비스 및 서비스 제공을 위한
+                  관련 정보를 제공하십시오.
+                </li>
+                <li>
+                  24시간 긴급출동 서비스를 위한 콜센터의 안내사항을 반드시 준수하십시오.
+                </li>
+              </ul>
+            </div>
+          </li>
+          <li :class="{ open: dropMenuOpen[3] }">
+            <div class="title" @click="toggleDropMenu(3)">긴급출동 서비스 이용 방법</div>
+            <div class="drop--contents">
+              <h3>
+                24시간 긴급출동 서비스를 이용하고자 하실 때에는 다음 정보를 준비 및
+                제공하여 주십시오.
+              </h3>
+              <ul>
+                <li>서비스 대상 차량정보 (차량번호 및 VIN(차대번호*))</li>
+                <li>
+                  서비스 대상 차량의 정확한 위치(주소) 및 출동인원과 즉시 연락가능한
+                  전화번호
+                </li>
+              </ul>
+              <p class="mt--50">
+                참고: 차대번호는 17자리의 숫자 및 문자로 구성되어 있으며, 이는
+                차량등록증에 기재되어 있습니다.<br />
+                또한 운전석 측 대시보드의 앞 유리창 부근 및 운전석 측 도어 기둥에도 해당
+                정보를 확인하실 수 있습니다.
+              </p>
+            </div>
+          </li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+
+  // 드롭메뉴 관련 (기본 모두 열림)
+  const dropMenuOpen = ref([true, true, true, true]);
+
+  const toggleDropMenu = (index) => {
+    dropMenuOpen.value[index] = !dropMenuOpen.value[index];
+  };
+
+  // 샘플 데이터
+  const breadcrumbData = {
+    mainMenu: "OWNER",
+    currentSubMenu: "서비스",
+    subMenuItems: [
+      { label: "공인 서비스", to: "/ford/owner", active: false },
+      {
+        label: "소모성 부품 무상 교환 서비스",
+        to: "/ford/owner/consumableParts",
+        active: true,
+      },
+      { label: "24시간 긴급 출동 서비스", to: "/ford/owner/tfService", active: false },
+      { label: "순정 부품", to: "/ford/owner/genuine", active: false },
+      { label: "리콜 안내", to: "/ford/owner/recall", active: false },
+      {
+        label: "사고 수리를 위한 포드 서비스 부품",
+        to: "/ford/owner/accident",
+        active: false,
+      },
+      { label: "내비게이션", to: "/ford/owner/navigation", active: false },
+    ],
+  };
+</script>

+ 74 - 0
app/pages/ford/owner/warranty.vue

@@ -0,0 +1,74 @@
+<template>
+  <div>
+    <breadCrumbs
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="ovwner--wrapper type--2 pb--0">
+      <div class="inner--wrap">
+        <div class="title--visual">
+          <h2>보증 및 서비스</h2>
+          <div class="desc mt--40">
+            고객께 최적의 서비스를 제공해야 하기에 포드는 업계 최고수준의 서비스를 제공합니다.<br />포드에서 제공하는 보증과 소모성 부품 무상교환 서비스(ESP)로 마음까지 편안하게 해드립니다.
+          </div>
+        </div>
+        <div class="thums--wrap mt--70">
+          <img src="/img/owner/warranty--visual.png" alt="" />
+        </div>
+      </div>
+
+      <div class="consumable--parts--wrap">
+        <div class="inner--wrap">
+          <div class="prm--service pt--120">
+            <h2>소모성 부품 무상교환 서비스 (ESP)</h2>
+            <div class="sub--title">
+              포드 고객께서는 무상 보증서비스 혜택과 함께 차량 구입 시 제공되는 소모성 부품 무상교환 서비스(ESP)로 차량 유지관리비를 대폭 절감하실 수 있습니다.
+            </div>
+            <ul class="sub--list">
+              <li>3년/6만km 이내 소모성 부품 유지 프로그램(기본형, EMP) 제공(차량관리에 필요한 기본정기점검 포함)</li>
+              <li>소정의 비용을 지불하여 업그레이드 시, 서비스 기간을 5년/10만km로 연장하고 제공 서비스를 기본형에서 고급형(PMP Upgrade)으로 확대 제공</li>
+            </ul>
+            <div class="btn--wrap mb--160">
+              <NuxtLink class="btn--sky" to="/ford/owner/consumableParts">자세히 보기<i class="ico"></i></NuxtLink>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="nav--dis--wrap" :class="{ active: isDisOpen }">
+      <div class="dis--btn" @click="isDisOpen = !isDisOpen">disclosures <i class="ico"></i></div>
+      <div class="dis--cont">
+        <div class="container">
+          <p>
+            [1] 자세한 사항은 가까운 전시장 및 서비스센터에 문의하세요.
+          </p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+
+  // disclosures 토글 (기본 열림)
+  const isDisOpen = ref(true);
+
+  // 탭 관련
+  const activeTab = ref(0);
+  const tabItems = ["공인 서비스", "차량관리 서비스", "서비스 센터에서의 관리"];
+
+  const setActiveTab = (index) => {
+    activeTab.value = index;
+  };
+
+  // 샘플 데이터
+  const breadcrumbData = {
+    mainMenu: "OWNER",
+    currentSubMenu: "보증 및 서비스",
+    subMenuItems: [
+      { label: "보증 및 서비스", to: "/", active: false },
+    ],
+  };
+</script>

+ 73 - 0
app/pages/ford/shopping/brochure.vue

@@ -0,0 +1,73 @@
+<template>
+  <div>
+    <breadCrumbs
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="container">
+      <div>
+        <div class="title--wrap">
+          <h2 class="">{{ pageTitle }}</h2>
+          <p v-if="!isAfterLaunch">{{ pageDescription }}</p>
+        </div>
+        <div class="brochure--wrap">
+          <a :href="isAfterLaunch == false ? '/brochure/25-Explorer-kr.pdf' : '/brochure/26-Explorer-kr.pdf'" download class="brochure">
+            <img :src="isAfterLaunch == false ? '/img/models/explorer.png' :  '/img/models/explorer2026.png'" alt="explorer" />            
+            <span>EXPLORER</span>
+            <div>다운로드 <i class="ico"></i></div>
+          </a>
+          <a href="/brochure/24-expedition-catalog.pdf" download class="brochure">
+            <img src="/img/img--expedition.png" alt="explorer" />
+            <span>expedition</span>
+            <div>다운로드 <i class="ico"></i></div>
+          </a>
+          <a href="/brochure/bronco-25.pdf" download class="brochure">
+            <img src="/img/img--bronco.png" alt="explorer" />
+            <span>bronco</span>
+            <div>다운로드 <i class="ico"></i></div>
+          </a>
+          <!-- 2026-04-15 08:00:00 이후(차량 순서 변경) -->
+          <a v-if="isAfterLaunch" href="/brochure/25my_mustang.pdf" download class="brochure">
+            <img src="/img/img--mustang.png" alt="mustang" />
+            <span>mustang</span>
+            <div>다운로드 <i class="ico"></i></div>
+          </a>
+          <a v-if="isAfterLaunch" href="/brochure/next-gen-ranger-kr-brochure.pdf" download class="brochure">
+            <img src="/img/img--ranger.png" alt="explorer" />
+            <span>ranger</span>
+            <div>다운로드 <i class="ico"></i></div>
+          </a>
+          <!-- 2026-04-15 08:00:00 이전(차량 순서 변경) -->
+          <a v-if="!isAfterLaunch" href="/brochure/next-gen-ranger-kr-brochure.pdf" download class="brochure">
+            <img src="/img/img--ranger.png" alt="explorer" />
+            <span>ranger</span>
+            <div>다운로드 <i class="ico"></i></div>
+          </a>
+          <a v-if="!isAfterLaunch" href="/brochure/25my_mustang.pdf" download class="brochure">
+            <img src="/img/img--mustang.png" alt="mustang" />
+            <span>mustang</span>
+            <div>다운로드 <i class="ico"></i></div>
+          </a>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  const isAfterLaunch = useTimeSwitch('2026-04-15T08:00:00+09:00');
+  const pageTitle = "브로슈어 다운로드";
+  const pageDescription = "포드에서 전하는 최신 정보와 특별한 이야기를 만나보세요.";
+
+  // 샘플 데이터
+  const breadcrumbData = {
+    mainMenu: "SHOPPING",
+    currentSubMenu: "브로슈어 다운로드",
+    subMenuItems: [
+      { label: "브로슈어 다운로드", to: "/ford/shopping/brochure", active: true },
+      { label: "타이어 에너지 소비 효율", to: "/ford/shopping/tire", active: false },
+      // { label: "신청하기", to: "/ford/shopping/apply", active: false },
+    ],
+  };
+</script>

+ 313 - 0
app/pages/ford/shopping/tire.vue

@@ -0,0 +1,313 @@
+<template>
+  <div>
+    <breadCrumbs
+      :main-menu="breadcrumbData.mainMenu"
+      :current-sub-menu="breadcrumbData.currentSubMenu"
+      :sub-menu-items="breadcrumbData.subMenuItems"
+    />
+    <div class="container">
+      <div>
+        <div class="title--wrap">
+          <h2 class="">{{ pageTitle }}</h2>
+          <!-- <p>{{ pageDescription }}</p> -->
+        </div>
+        <div class="table--wrap">
+          <table>
+            <colgroup></colgroup>
+            <thead>
+              <tr>
+                <th>모델명</th>
+                <th>타이어 업체</th>
+                <th>타이어 모델명</th>
+                <th>타이어 규격</th>
+                <th>회전 저항 등급</th>
+                <th>젖은 노면 제동 등급</th>
+                <th>비고 (현재 장착 여부)</th>
+              </tr>
+            </thead>
+            <tbody>
+              <!-- Mustang 2.3 -->
+              <!-- <tr>
+                <td rowspan="2">Mustang 2.3</td>
+                <td class="uppercase">PIRELLI</td>
+                <td>Pzero</td>
+                <td class="uppercase">255/40ZR19 96Y</td>
+                <td>5</td>
+                <td>1</td>
+                <td class="uppercase">FR/RR</td>
+              </tr> -->
+              <!-- <tr>
+                <td class="uppercase">PIRELLI</td>
+                <td>Pzero</td>
+                <td class="uppercase">255/40ZR19 100Y</td>
+                <td>4</td>
+                <td>2</td>
+                <td class="uppercase">FR/RR</td>
+              </tr> -->
+              <!-- Mustang 5.0 -->
+              <!-- <tr>
+                <td rowspan="5">Mustang 5.0</td>
+                <td rowspan="2" class="uppercase">MICHELIN</td>
+                <td rowspan="2">Pilot Sport4 S</td>
+                <td>255/40ZR19 (100Y)</td>
+                <td>N/A</td>
+                <td>N/A</td>
+                <td>FR</td>
+              </tr>
+              <tr>
+                <td>275/40ZR19 (105Y)</td>
+                <td>N/A</td>
+                <td>N/A</td>
+                <td>RR</td>
+              </tr>
+              <tr>
+                <td class="uppercase">PIRELLI</td>
+                <td>Pzero</td>
+                <td>265/40R19 98Y</td>
+                <td>5</td>
+                <td>1</td>
+                <td>FR/RR</td>
+              </tr> -->
+              <!-- <tr>
+                <td class="uppercase">GOODYEAR</td>
+                <td class="uppercase">EAGLE F1 ASYMMETRIC ALL SEASON</td>
+                <td>265/35R20 99W</td>
+                <td>3</td>
+                <td>4</td>
+                <td>FR/RR</td>
+              </tr>
+              <tr>
+                <td class="uppercase">PIRELLI</td>
+                <td>Pzero</td>
+                <td>255/40ZR19 (96Y)</td>
+                <td>N/A</td>
+                <td>N/A</td>
+                <td>FR</td>
+              </tr> -->
+              <!-- Explorer 2.3 -->
+              <!-- <tr>
+                <td rowspan="4">Explorer 2.3</td>
+                <td class="uppercase">MICHELIN</td>
+                <td>Latitude Tour HP</td>
+                <td>245/60R18 104H</td>
+                <td>3</td>
+                <td>4</td>
+                <td></td>
+              </tr> -->
+              <!-- <tr>
+                <td class="uppercase">HANKOOK</td>
+                <td>Ventus S1 Noble2</td>
+                <td>255/50R20 105H</td>
+                <td>3</td>
+                <td>3</td>
+                <td>동일</td>
+              </tr> -->
+              <!-- <tr>
+                <td class="uppercase">MICHELIN</td>
+                <td>Primacy A/S</td>
+                <td>255/55R20 110V</td>
+                <td>3</td>
+                <td>3</td>
+                <td></td>
+              </tr> -->
+              <!-- <tr>
+                <td class="uppercase">PIRELLI</td>
+                <td>SCORPION ZERO A/S</td>
+                <td>275/45R21 110W</td>
+                <td>1</td>
+                <td>2</td>
+                <td></td>
+              </tr> -->
+              <!-- Explorer 3.0 -->
+              <!--               
+              <tr>
+                <td>Explorer 3.0</td>
+                <td class="uppercase">PIRELLI</td>
+                <td>SCORPION ZERO</td>
+                <td>275/45R21 110W</td>
+                <td>N/A</td>
+                <td>N/A</td>
+                <td></td>
+              </tr> -->
+              <!-- Explorer 3.0 PHEV -->
+              <!-- <tr>
+                <td>Explorer 3.0 PHEV</td>
+                <td class="uppercase">MICHELIN</td>
+                <td>Primacy A/S</td>
+                <td>255/55R20 110V</td>
+                <td>3</td>
+                <td>3</td>
+                <td></td>
+              </tr> -->
+              <!-- Explorer 3.3 FHEV -->
+              <!-- <tr>
+                <td>Explorer 3.3 FHEV</td>
+                <td class="uppercase">MICHELIN</td>
+                <td>Primacy A/S</td>
+                <td>255/55R20 110V</td>
+                <td>3</td>
+                <td>3</td>
+                <td></td>
+              </tr> -->
+              <!-- Ranger Raptor -->
+              <!-- <tr>
+                <td>Ranger Raptor</td>
+                <td class="uppercase">GENERAL</td>
+                <td>GRABBER AT3</td>
+                <td>LT285/70R17 116/113S</td>
+                <td>4</td>
+                <td>2</td>
+                <td></td>
+              </tr> -->
+              <!-- Ranger Wildtrak -->
+              <!-- <tr>
+                <td rowspan="2">Ranger Wildtrak</td>
+                <td class="uppercase">CONTINENTAL</td>
+                <td>CROSSCONTACT LX</td>
+                <td>265/60R18 110T</td>
+                <td>5</td>
+                <td>4</td>
+                <td></td>
+              </tr>
+              <tr>
+                <td class="uppercase">GOODYEAR</td>
+                <td>WRANGLER TERRITORY HT</td>
+                <td>255/65R18 111H</td>
+                <td>1</td>
+                <td>4</td>
+                <td></td>
+              </tr>-->
+              <!-- Expedition 3.5L -->
+              <!--<tr>
+                <td rowspan="2">Expedition 3.5L</td>
+                <td class="uppercase">HANKOOK</td>
+                <td>Dynapro HT</td>
+                <td>285/45R22 114H</td>
+                <td>2</td>
+                <td>3</td>
+                <td>PLATINUM</td>
+              </tr>
+              <tr>
+                <td class="uppercase">general</td>
+                <td>GRABBER HTS60</td>
+                <td>285/45R22 114H</td>
+                <td>4</td>
+                <td>4</td>
+                <td>PLATINUM</td>
+              </tr> -->
+              <!-- Bronco 2.7 -->
+              <!-- <tr>
+                <td>Bronco 2.7</td>
+                <td class="uppercase">BRIDGESTON</td>
+                <td>DUELER A/T RH-S</td>
+                <td>255/70R18 113T</td>
+                <td>2</td>
+                <td>4</td>
+                <td>OUTERBANKS</td>
+              </tr> -->
+
+              <tr>
+                <td rowspan="2">Explorer 2.3L</td>
+                <td>MICHELIN</td>
+                <td>Primacy A/S</td>
+                <td>255/55R20 110V</td>
+                <td>3</td>
+                <td>3</td>
+                <td>Platinum</td>
+              </tr>
+              <tr>
+                <td>PIRELLI</td>
+                <td>SCORPION ZERO A/S</td>
+                <td>275/45R21 110W</td>
+                <td>1</td>
+                <td>2</td>
+                <td>ST-Line</td>
+              </tr>
+
+              <tr>
+                <td>Explorer 3.0L</td>
+                <td>BRIDGESTON</td>
+                <td>DUELER A/T REVO3</td>
+                <td>265/65R 112T</td>
+                <td>4</td>
+                <td>4</td>
+                <td>Tremor</td>
+              </tr>
+
+              <tr>
+                <td rowspan="2">Expedition 3.5L</td>
+                <td>MICHELIN</td>
+                <td>PRIMACY LTX</td>
+                <td>275/50R22 115H</td>
+                <td>1</td>
+                <td>3</td>
+                <td>Platinum</td>
+              </tr>
+              <tr>
+                <td>GOODYEAR</td>
+                <td>EAGLE TOURING</td>
+                <td>285/40R24 112H</td>
+                <td>2</td>
+                <td>4</td>
+                <td>Platinum</td>
+              </tr>
+
+              <!-- Bronco 2.3 -->
+              <tr>
+                <td>Bronco 2.3</td>
+                <td class="uppercase">BRIDGESTON</td>
+                <td>DUELER A/T RH-S</td>
+                <td>255/70R18 113T</td>
+                <td>2</td>
+                <td>4</td>
+                <td>Outerbanks</td>
+              </tr>
+              <!-- S650 Mustang 2.3L -->
+              <tr>
+                <td>S650 Mustang 2.3L</td>
+                <td class="uppercase">PIRELLI</td>
+                <td>Pzero</td>
+                <td>255/40R19 96Y</td>
+                <td>5</td>
+                <td>1</td>
+                <td>FR/RR</td>
+              </tr>
+              <!-- S650 Mustang GT 5.0L -->
+              <tr>
+                <td rowspan="2">S650 Mustang GT 5.0L</td>
+                <td rowspan="2" class="uppercase">PIRELLI</td>
+                <td rowspan="2">Pzero</td>
+                <td>255/40ZR19 (96Y)</td>
+                <td>5</td>
+                <td>1</td>
+                <td>FR</td>
+              </tr>
+              <tr>
+                <td>275/40R19 (101Y)</td>
+                <td>4</td>
+                <td>3</td>
+                <td>RR</td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  const pageTitle = "타이어 에너지 소비 효율";
+  const pageDescription =
+    "포드 코리아는 타이어의 에너지 소비 효율 등급 표시 정보를 소비자에게 제공합니다.";
+  // 샘플 데이터
+  const breadcrumbData = {
+    mainMenu: "SHOPPING",
+    currentSubMenu: "타이어 에너지 소비 효율",
+    subMenuItems: [
+      { label: "브로슈어 다운로드", to: "/ford/shopping/brochure", active: false },
+      { label: "타이어 에너지 소비 효율", to: "/ford/shopping/tire", active: true },
+      // { label: "신청하기", to: "/ford/shopping/apply", active: false },
+    ],
+  };
+</script>

+ 279 - 0
app/pages/ford/vehicle/bronco.vue

@@ -0,0 +1,279 @@
+<template>
+  <main>
+    <SwiperBanner
+      class="sticky--banner"
+      :slides="bannerSlides"
+      :autoplay="false"
+      :loop="false"
+      type=""
+    />
+
+    <div class="outer--wrapper">
+      <div class="trim--spec--wrap">
+        <ul>
+          <li>
+            <span>소비자 가격</span>
+            <div>
+              73,100,000<em>원 부터<i>*</i></em>
+            </div>
+          </li>
+          <li>
+            <span>엔진</span>
+            <div>EcoBoost 2.3L</div>
+          </li>
+          <li>
+            <span>승차 정원(명)</span>
+            <div>5</div>
+          </li>
+        </ul>
+      </div>
+
+      <div class="title--visual">
+        <h2>Bronco Outer Banks</h2>
+        <div class="sub--title">
+          오프로드 성능과 스타일리시한 디자인을 모두 갖춘 브롱코는<br />광활한 대자연을
+          열망하고 모험을 추구하는 내면의 열정을 실현시켜줄 진정한 버디입니다.
+        </div>
+      </div>
+
+      <div class="models--visual--01 mt--80">
+        <div class="thumb--wrap">
+          <img src="/img/models/bronco/visual-01.jpg" />
+        </div>
+      </div>
+
+      <div class="car--price--small--pic--wrap mt--120">
+        <div>
+          <h2>BRONCO Models</h2>
+          <div class="car--list--wrap">
+            <ul>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/bronco/brc-car.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">Outer Banks 2.3L</h2>
+                  <div class="price--wrap">
+                    소비자 가격 : <em>73,100,000 원 부터<i>*</i></em>
+                  </div>
+                </div>
+              </li>
+            </ul>
+          </div>
+        </div>
+      </div>
+
+      <div class="title--visual mt--150">
+        <h2>편안하고 더 넓은 SUV</h2>
+        <div class="sub--title">
+          오프로드 성능과 스타일리시한 디자인을 모두 갖춘 브롱코는<br />
+          광활한 대자연을 열망하고 모험을 추구하는 내면의 열정을 실현시켜줄 진정한
+          버디입니다.
+        </div>
+      </div>
+      <div class="models--visual--grid mt--80 type--2">
+        <ul>
+          <li>
+            <div class="thumb--wrap">
+              <img src="/img/models/bronco/grid-01.jpg" />
+            </div>
+            <div class="desc--wrap">
+              <h2>탈착식 도어/루프</h2>
+              <div class="captions">
+                루프와 도어는 편리하게 탈부착이 가능합니다.<br />
+                브롱코의 독특한 디자인과 함께 최고의 오픈-에어<br />
+                경험을 즐길 수 있습니다.
+              </div>
+            </div>
+          </li>
+          <li>
+            <div class="thumb--wrap">
+              <img src="/img/models/bronco/grid-02.jpg" />
+            </div>
+            <div class="desc--wrap">
+              <h2>
+                G.O.A.T (Goes Over Any Terrain)<br />
+                지형 관리 시스템
+              </h2>
+              <div class="captions">
+                G.O.A.T. MODESTM (Goes Over Any Type<br />
+                of Terrain) 를 포함한 지형 관리 시스템(The Terrain<br />
+                Management SystemTM)은 간단한 작동만으로도<br />
+                지형, 날씨 또는 도로 상황에 맞는 퍼포먼스를 발휘할 수<br />
+                있도록 설계되었습니다. 6가지 모드 중 주행 상황에<br />
+                가장 적합한 모드를 선택하십시오. (NORMAL,<br />
+                ECO, SLIPPERY, SAND, MUD/RUTS, SPORT)
+              </div>
+            </div>
+          </li>
+          <li>
+            <div class="thumb--wrap">
+              <img src="/img/models/bronco/grid-03.jpg" />
+            </div>
+            <div class="desc--wrap">
+              <h2>BRONCO ACCESSORIES</h2>
+              <div class="captions">
+                나의 라이프 스타일에 맞춰 다양한 액세서리를 장착할 수 있습니다.<br />
+                개성을 표현하기 위해 외관을 업그레이드하거나<br />
+                아웃도어 활동 시 편의를 위한 소품의 추가 장착도<br />
+                가능합니다. 구매 가능 아이템에 대한 자세한 사항은<br />
+                각 포드 딜러사 서비스 센터에서 확인하실 수 있습니다.
+              </div>
+            </div>
+          </li>
+        </ul>
+      </div>
+
+      <!-- <SwiperBanner3
+        class="mt--100"
+        :slides="bannerSlides2"
+        :autoplay="false"
+        :loop="false"
+        mtitle="design"
+        stitle="브롱코 아우터 뱅크스와 함께 스타일리시한 아웃도어 라이프를 즐겨보십시오.<br/>
+  아우터 뱅크스(Outer Banks) 모델은 LED 시그니처 라이팅 및 테일램프, 외장 컬러와 동일한 펜더 플레어,튜브 스텝 등이 포함되어 있어 오프로드를<br/>
+  위한 성능은 물론 멋진 스타일도 놓치지 않았습니다. 12인치 LCD 스크린과 B&O® 사운드 시스템 등은 브롱코와 함께하는<br/>
+  여정을 더욱 편리하고 즐겁게 만들어 드립니다."
+        type=""
+      />
+
+      <SwiperBanner3
+        :slides="bannerSlides3"
+        :autoplay="false"
+        :loop="false"
+        type=""
+        mtitle="technology"
+        stitle="브롱코의 에코부스트 엔진은 일상적인 주행 상황에서 뿐만 아니라 오프로드, 트레일 라이딩에서도 뛰어난 성능을 발휘합니다.<br/>노면 상태에 따라 다양하게 4×4 모드변경을 통해 더욱 더 안정적인 주행에 도움을 받을 수 있습니다."
+      /> -->
+
+      <div class="title--visual mt--150">
+        <h2>gallery</h2>
+        <!-- <div class="sub--title">
+          일상과 자유로운 아웃도어 라이프스타일을 향한 열망을 모두 충족시켜주는 브롱코의
+          디자인은 1세대 브롱코를 현대적 감각으로 재해석
+        </div> -->
+      </div>
+      <div class="gallery--wrap mt--80">
+        <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery1"
+            :autoplay="false"
+            :loop="false"
+            type=""
+          />
+        </div>
+        <!-- <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery2"
+            :autoplay="false"
+            :loop="false"
+            type="interior"
+          />
+        </div> -->
+      </div>
+      <div class="nav--dis--wrap model mt--150" :class="{ active: isDisOpen }">
+        <div class="dis--btn" @click="isDisOpen = !isDisOpen">disclosures <i class="ico"></i></div>
+        <div class="dis--cont">
+          <div class="container">
+            <p>
+              *본 웹사이트에 공지된 가격정보는 부가세(VAT)를 포함하고 있습니다. 본 가격은 개별소비세 인하에 관한 정부시책을 반영한 것이며 가격정보는 예고없이 변동될 수 있습니다. 
+             가격정보는 고객님의 차량 구입에 도움을 드리고자 함이며 법적 구속력은 없습니다. <br/><br/>
+             사이트에서 제공하는 차량에 대한 모든 이미지 또는 사양은 실제 판매 모델과 다를 수 있습니다. 정확한 정보는 가까운 공식 딜러를 통해 확인하시기 바랍니다.
+            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </main>
+</template>
+
+<script setup>
+  // disclosures 토글 (기본 열림)
+  const isDisOpen = ref(true);
+  const bannerSlides = ref([
+    {
+      image: "/img/models/bronco/brc_banner01.jpg",
+      moImage: "/img/models/bronco/brc_banner01_mo.jpg",
+      alt: "THE BRONCO LEGEND FORGES AHEAD",
+      title: "THE BRONCO<br/>LEGEND FORGES AHEAD",
+      subtitle: "한 번도 인류의 발길이 닿지 않은 거대한 세계, 그곳으로 향하는 도전의 길",
+      smalldesc: "",
+      morelink: "/ford/network",
+      morelinktitle: "시승 신청",
+    },
+  ]);
+
+  const bannerSlides2 = ref([
+    {
+      image: "/img/models/bronco/design-01-1.png",
+      alt: "Bronco Outer Banks",
+      title: "Bronco Outer Banks",
+      smalldesc:
+        "국내 출시된 아우터 뱅크스 모델은 각진 브롱코 레터링 그릴, 전면의 둥근 헤드램프, 이목을 사로잡는 펜더 플레어로 둘러싸인 대형 타이어 등은 1세대 브롱코 디자인의<br/>특별하고도 독특한 아이텐티티를 잘 보여줍니다.",
+    },
+    {
+      image: "/img/models/bronco/design-01-2.png",
+      alt: "COMFORT MEETS DURABILITY",
+      title: "COMFORT MEETS DURABILITY",
+      smalldesc:
+        "자유로우면서도 강인한 익스테리 감각적인 인테리어는 브롱코를 더욱 빛내주는 중요한 요소입니다.<br/>다양한 편의시설과 운전자의 체형에 따라 조절가능한 1열 파워시트, 넉넉한 레그룸을 갖춘 2열 공간은 브롱코를 더욱 매력적으로 만듭니다.",
+    },
+  ]);
+
+  const bannerSlides3 = ref([
+    {
+      image: "/img/models/bronco/design-02-1.png",
+      alt: "10-Speed Transmissions",
+      title: "10-Speed Transmissions",
+
+      smalldesc:
+        "브롱코 아우터뱅크스에 장착된 10단 자동 트랜스미션은 오프로드 상황에서도 엔진의 파워를 잘 유지시켜줄 수 있도록 설계되었으며 특히 저단 기어에서는 더욱 더 인상적인<br/>성능을 발휘합니다.",
+    },
+    {
+      image: "/img/models/bronco/design-02-2.png",
+      alt: "THE WHOLE HOSS",
+      title: "THE WHOLE HOSS",
+
+      smalldesc:
+        "브롱코의 스릴 넘치는 오프로드 기량을 만들어 내는 다양한 핵심 기술 중인 HOSS (High-Performance, Off-Road, Stability, Suspension) 시스템 최신 기술을 바탕으로 설계된<br/>서스펜션은 오프로드 주행 시에는 다이내믹하면서도 안정적으로 운전의 즐거움을 만끽하도록 도와주며, 일반 도로 주행 시에는 부드럽고 편안한 주행 경험을 제공합니다.<br/>* 오프로드 주행전에는사용자 설명서를 참고하시고, 주행전 주변지형을 사전에 파악하신 후 안전을 위해 주행 환경에 적합한 기능을 사용하시기 바랍니다.",
+    },
+  ]);
+
+  const gallery1 = ref([
+    { image: "/img/models/bronco/gallery_1_1.avif" },
+    { image: "/img/models/bronco/gallery_1_2.avif" },
+    { image: "/img/models/bronco/gallery_1_3.avif" },
+    { image: "/img/models/bronco/gallery_1_4.avif" },
+    { image: "/img/models/bronco/gallery_1_5.avif" },
+    { image: "/img/models/bronco/gallery_1_6.avif" },
+    { image: "/img/models/bronco/gallery_1_7.avif" },
+    { image: "/img/models/bronco/gallery_1_8.jpg" },
+    { image: "/img/models/bronco/gallery_1_9.avif" },
+    { image: "/img/models/bronco/gallery_1_10.avif" },
+    { image: "/img/models/bronco/gallery_1_11.webp" },
+    { image: "/img/models/bronco/gallery_1_12.avif" },
+    { image: "/img/models/bronco/gallery_2_1.avif" },
+    { image: "/img/models/bronco/gallery_2_2.avif" },
+    { image: "/img/models/bronco/gallery_2_3.avif" },
+    { image: "/img/models/bronco/gallery_2_4.avif" },
+    { image: "/img/models/bronco/gallery_2_5.avif" },
+    { image: "/img/models/bronco/gallery_2_6.avif" },
+    { image: "/img/models/bronco/gallery_2_7.jpg" },
+    { image: "/img/models/bronco/gallery_2_8.avif" },
+    { image: "/img/models/bronco/gallery_2_9.webp" },
+    { image: "/img/models/bronco/gallery_2_10.avif" },  
+  ]);
+
+  const gallery2 = ref([
+    { image: "/img/models/bronco/gallery_2_1.avif" },
+    { image: "/img/models/bronco/gallery_2_2.avif" },
+    { image: "/img/models/bronco/gallery_2_3.avif" },
+    { image: "/img/models/bronco/gallery_2_4.avif" },
+    { image: "/img/models/bronco/gallery_2_5.avif" },
+    { image: "/img/models/bronco/gallery_2_6.avif" },
+    { image: "/img/models/bronco/gallery_2_7.jpg" },
+    { image: "/img/models/bronco/gallery_2_8.avif" },
+    { image: "/img/models/bronco/gallery_2_9.webp" },
+    { image: "/img/models/bronco/gallery_2_10.avif" },
+  ]);
+</script>

+ 315 - 0
app/pages/ford/vehicle/expedition.vue

@@ -0,0 +1,315 @@
+<template>
+  <main>
+    <SwiperBanner
+      class="sticky--banner"
+      :slides="bannerSlides"
+      :autoplay="false"
+      :loop="false"
+      type=""
+    />
+
+    <div class="outer--wrapper">
+      <div class="trim--spec--wrap">
+        <ul>
+          <li>
+            <span>소비자 가격</span>
+            <div>
+              111,100,000<em>원 부터<i>*</i></em>
+            </div>
+          </li>
+          <li>
+            <span>엔진</span>
+            <div>Platinum 3.5L EcoBoost V6</div>
+          </li>
+          <li>
+            <span>승차 정원(명)</span>
+            <div>7 ~ 8</div>
+          </li>
+        </ul>
+      </div>
+
+      <div class="title--visual">
+        <h2>The Road To adventure is often unpaved</h2>
+        <div class="sub--title">
+          Ford Expedition®은 대형 SUV 중에서 확실한 존재감을 보여주고 있습니다.<br />
+          사람들의 시선을 압도하는 큰 차체와 차량 내에 설치된 각종 편의 사양들은 다른
+          SUV와는 확실한 차별점을 느끼실 수 있게 해드립니다.<br />
+          Ford Expedition® Platinum 시리즈를 경험해 보십시오.
+        </div>
+      </div>
+
+      <div class="models--visual--01 mt--80">
+        <div class="thumb--wrap">
+          <img src="/img/models/expedition/visual-01.jpg" />
+        </div>
+      </div>
+
+      <div class="car--price--small--pic--wrap mt--120">
+        <div>
+          <h2>EXPEDITION Models</h2>
+          <div class="car--list--wrap">
+            <ul>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/expedition/expd-plt7.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">platinum 7 seater standard</h2>
+                  <div class="price--wrap">
+                    소비자 가격 : <em>111,100,000 원 부터 <i>*</i></em>
+                  </div>
+                </div>
+              </li>
+              <!-- <li>
+                <div class="thumb">
+                  <img src="/img/models/expedition/expd-plt8.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">platinum 8 seater standard</h2>
+                  <div class="price--wrap">
+                    소비자 가격 : <em>111,100,000 원 부터<i>*</i></em>
+                  </div>
+                </div>
+              </li> -->
+            </ul>
+          </div>
+        </div>
+      </div>
+
+      <div class="title--visual mt--150">
+        <h2>편안하고 더 넓은 SUV</h2>
+        <div class="sub--title">
+          최대 8명이 탑승할 수 있는 편안한 좌석은 물론, Ford CO-PILOT360TM 시스템이
+          안전하고 편안한 주행을 지원합니다.<br />
+          또한 3.5리터 가솔린 엔진이 뿜어내는 강력한 파워는 익스페디션 만의 퍼포먼스
+          주행을 실현합니다.
+        </div>
+      </div>
+      <div class="models--visual--grid mt--80 type--2">
+        <ul>
+          <li>
+            <div class="thumb--wrap">
+              <img src="/img/models/expedition/grid-01.jpg" />
+            </div>
+            <div class="desc--wrap">
+              <h2>3.5L 가솔린 엔진</h2>
+              <div class="captions">
+                포드 익스페디션은 3.5리터 가솔린 엔진과 10단<br />
+                자동 변속기가 장착되어 원하시는 곳이 어디든지<br />
+                편안하게 모셔드립니다.
+              </div>
+            </div>
+          </li>
+          <li>
+            <div class="thumb--wrap">
+              <img src="/img/models/expedition/grid-02.jpg" />
+            </div>
+            <div class="desc--wrap">
+              <h2>10단 자동 변속기</h2>
+              <div class="captions">
+                10단 자동 변속기는 최적화된 변속 타이밍을 제공합니다.<br />
+                또한, 견인/끌기 모드는 가파른 오르막 경사에서<br />
+                원치 않는 빈번한 기어 변속을 억제합니다.
+              </div>
+            </div>
+          </li>
+          <li>
+            <div class="thumb--wrap">
+              <img src="/img/models/expedition/grid-03.jpg" />
+            </div>
+            <div class="desc--wrap">
+              <h2>60/40 리클라이닝 파워 폴드 시트</h2>
+              <div class="captions">
+                포드 익스페디션의 좌석은 1열 파워 운전석, 캡틴 시트<br />
+                또는 벤치 시트로 구성된 2열 좌석, 60/40으로<br />
+                분할된 리클라이닝 파워폴드 시트로 구성된 3열<br />
+                좌석으로 구성되었습니다. 최대 8명이 가능한<br />
+                익스페디션의 넓은 공간은 모든 탑승객에<br />
+                만족감과 편안함을 선사합니다.
+              </div>
+            </div>
+          </li>
+        </ul>
+      </div>
+
+      <!-- <SwiperBanner3
+        class="mt--100"
+        :slides="bannerSlides2"
+        :autoplay="false"
+        :loop="false"
+        mtitle="design"
+        stitle="오랜 시간 동안 포드는 가족들의 편안함을 위한 더 크고, 더 나은 suV를 만들기 위해 고민해왔습니다.<br/>이제, 익스페디션은 바로 그 해답들 중 하나입니다."
+        type=""
+      />
+
+      <SwiperBanner3
+        :slides="bannerSlides3"
+        :autoplay="false"
+        :loop="false"
+        type=""
+        mtitle="technology"
+        stitle="때로는 사소한 것들이 우리의 삶을 지탱할 수 있게 해줍니다.<br/>익스페디션에는 작지만 세심한 배려가 숨어 있습니다."
+      /> -->
+
+      <div class="title--visual mt--150">
+        <h2>gallery</h2>
+        <!-- <div class="sub--title">작지만 세심한 배려가 숨어 있는 디자인</div> -->
+      </div>
+      <div class="gallery--wrap mt--80">
+        <div class="thumb--wrap">
+          <!-- <SwiperBanner2
+            :slides="gallery1"
+            :autoplay="false"
+            :loop="false"
+            type="exterior"
+          /> -->
+          <SwiperBanner2
+            :slides="gallery1"
+            :autoplay="false"
+            :loop="false"
+            type=""
+          />
+        </div>
+        <!-- <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery2"
+            :autoplay="false"
+            :loop="false"
+            type="interior"
+          />
+        </div> -->
+      </div>
+      <div class="nav--dis--wrap model mt--150" :class="{ active: isDisOpen }">
+        <div class="dis--btn" @click="isDisOpen = !isDisOpen">disclosures <i class="ico"></i></div>
+        <div class="dis--cont">
+          <div class="container">
+            <p>
+            *본 웹사이트에 공지된 가격정보는 부가세(VAT)를 포함하고 있습니다. 본 가격은 개별소비세 인하에 관한 정부시책을 반영한 것이며 가격정보는 예고없이 변동될 수 있습니다. 
+             가격정보는 고객님의 차량 구입에 도움을 드리고자 함이며 법적 구속력은 없습니다. <br/><br/>
+             사이트에서 제공하는 차량에 대한 모든 이미지 또는 사양은 실제 판매 모델과 다를 수 있습니다. 정확한 정보는 가까운 공식 딜러를 통해 확인하시기 바랍니다.
+            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </main>
+</template>
+
+<script setup>
+  // disclosures 토글 (기본 열림)
+  const isDisOpen = ref(true);
+  const bannerSlides = ref([
+    {
+      image: "/img/models/expedition/expd_banner01.jpg",
+      moImage: "/img/models/expedition/expd_banner01_mo.png",
+      alt: "ford expedition",
+      title: "ford expedition",
+      subtitle: "사람들의 시선을 압도하는 큰 차체 다른 SUV와는 확실한 차별점",
+      smalldesc: "",
+      morelink: "/ford/network",
+      morelinktitle: "시승 신청",
+      morelinktarget: "",
+    },
+  ]);
+
+  const bannerSlides2 = ref([
+    {
+      image: "/img/models/expedition/design-01-1.png",
+      alt: "ford expedition",
+      title: "LIVING LARGE",
+
+      smalldesc:
+        "3.5L 가솔린 engine과 22인치 알루미늄 휠, 그리고 파워 러닝 보드 등이 적용되어 있어 큰 차체 때문에 느낄 수 있는 운전 및 승하차의 불편함을 덜어 드립니다.<br/>또한 360도 카메라는 주차시 주변 장애물을 쉽게 인지할 수 있도록 도와드립니다.",
+    },
+    {
+      image: "/img/models/expedition/design-01-2.png",
+      alt: "LIVING COMFORT",
+      title: "LIVING COMFORT",
+
+      smalldesc:
+        "열선 및 통풍 기능이 포함된 럭셔리 가죽시트는 운전자는 물론, 동승자에게도 편안함을 제공해드립니다. <br />뿐만 아니라, 인테리어 곳곳에서 익스페디션만의 특별함을 느끼실 수 있습니다.",
+    },
+    {
+      image: "/img/models/expedition/design-01-3.png",
+      alt: "LOOKS MEET COMFORT",
+      title: "LOOKS MEET COMFORT",
+
+      smalldesc:
+        "익스페디션 실내에 적용된 독특한 우드 액센트, 가죽으로 마감처리된 인스트루먼트 패널 및 파노라믹 비스타 루프가 서로 조화를 이루어 익스페디션을 더욱 돋보이게 만듭니다.",
+    },
+  ]);
+
+  const bannerSlides3 = ref([
+    {
+      image: "/img/models/expedition/design-02-1.png",
+      alt: "STAY WELL-CONNECTED",
+      title: "STAY WELL-CONNECTED",
+
+      smalldesc:
+        "포드 익스페디션에 제공되는 싱크4 (SYNC)® 시스템과 함께라면 운전 중에도 각종 기능들을 안전하게 이용하실 수 있습니다. <br />싱크4 (SYNC)®의 음성 명령 기능으로 차량을 컨트롤할 수 있음은 물론, 대형 터치스크린에서 간단한 조작만으로도 필요한 기능을 사용하실 수 있습니다. <br />싱크4 (SYNC)® 1로 애플 카플레이(Apple CarPlayTM)2와 안드로이드 오토(Android AutoTM) 의 다양한 기능을 즐길 수 있습니다.",
+    },
+    {
+      image: "/img/models/expedition/design-02-2.png",
+      alt: "THE POWER TO MOVE YOU",
+      title: "THE POWER TO MOVE YOU",
+
+      smalldesc:
+        "포드 익스페디션은 3.5리터 가솔린 엔진과 10 단 자동 변속기가 장착되어 원하시는 곳이 어디든지 편안하게 모셔드립니다.<br/>간단한 조작만으로도 7개의 드라이브 모드*를 사용할 수 있도록 설계되어 있어, 도로 여건에 최적화된 드라이빙을 즐기실 수 있도록 도와드립니다.<br/>* 7개의 드라이브 모드 : NORMAL, SPORT, ECO, GRASS/GRAVEL/SNOW(4WD), TOW/HAUL, SAND, MUD/RUTS",
+    },
+  ]);
+
+  const gallery1 = ref([
+    { image: "/img/models/expedition/gallery_1_1.jpg" },
+    { image: "/img/models/expedition/gallery_1_2.jpg" },
+    { image: "/img/models/expedition/gallery_1_3.jpg" },
+    { image: "/img/models/expedition/gallery_1_4.jpg" },
+    { image: "/img/models/expedition/gallery_1_5.jpg" },
+    { image: "/img/models/expedition/gallery_1_6.jpg" },
+    { image: "/img/models/expedition/gallery_1_7.jpg" },
+    { image: "/img/models/expedition/gallery_1_8.jpg" },
+    { image: "/img/models/expedition/gallery_1_9.jpg" },
+    { image: "/img/models/expedition/gallery_2_1.jpg" },
+    { image: "/img/models/expedition/gallery_2_2.jpg" },
+    { image: "/img/models/expedition/gallery_2_3.jpg" },
+    { image: "/img/models/expedition/gallery_2_4.jpg" },
+    { image: "/img/models/expedition/gallery_2_5.jpg" },
+    { image: "/img/models/expedition/gallery_2_6.jpg" },
+    { image: "/img/models/expedition/gallery_2_7.jpg" },
+    { image: "/img/models/expedition/gallery_2_8.jpg" },
+    { image: "/img/models/expedition/gallery_2_9.jpg" },
+    { image: "/img/models/expedition/gallery_2_10.jpg" },
+    { image: "/img/models/expedition/gallery_2_11.jpg" },
+    { image: "/img/models/expedition/gallery_2_12.jpg" },
+    { image: "/img/models/expedition/gallery_2_13.jpg" },
+    { image: "/img/models/expedition/gallery_2_14.jpg" },
+    { image: "/img/models/expedition/gallery_2_15.jpg" },
+    { image: "/img/models/expedition/gallery_2_16.jpg" },
+    { image: "/img/models/expedition/gallery_2_17.jpg" },
+    { image: "/img/models/expedition/gallery_2_18.jpg" },
+    { image: "/img/models/expedition/gallery_2_19.jpg" },
+    { image: "/img/models/expedition/gallery_2_20.jpg" },
+  ]);
+
+  const gallery2 = ref([
+    { image: "/img/models/expedition/gallery_2_1.jpg" },
+    { image: "/img/models/expedition/gallery_2_2.jpg" },
+    { image: "/img/models/expedition/gallery_2_3.jpg" },
+    { image: "/img/models/expedition/gallery_2_4.jpg" },
+    { image: "/img/models/expedition/gallery_2_5.jpg" },
+    { image: "/img/models/expedition/gallery_2_6.jpg" },
+    { image: "/img/models/expedition/gallery_2_7.jpg" },
+    { image: "/img/models/expedition/gallery_2_8.jpg" },
+    { image: "/img/models/expedition/gallery_2_9.jpg" },
+    { image: "/img/models/expedition/gallery_2_10.jpg" },
+    { image: "/img/models/expedition/gallery_2_11.jpg" },
+    { image: "/img/models/expedition/gallery_2_12.jpg" },
+    { image: "/img/models/expedition/gallery_2_13.jpg" },
+    { image: "/img/models/expedition/gallery_2_14.jpg" },
+    { image: "/img/models/expedition/gallery_2_15.jpg" },
+    { image: "/img/models/expedition/gallery_2_16.jpg" },
+    { image: "/img/models/expedition/gallery_2_17.jpg" },
+    { image: "/img/models/expedition/gallery_2_18.jpg" },
+    { image: "/img/models/expedition/gallery_2_19.jpg" },
+    { image: "/img/models/expedition/gallery_2_20.jpg" },
+  ]);
+</script>

+ 591 - 0
app/pages/ford/vehicle/explorer.vue

@@ -0,0 +1,591 @@
+<template>
+  <main>
+    <SwiperBanner
+      v-if="!isAfterLaunch"
+      class="sticky--banner"
+      :slides="bannerSlides"
+      :autoplay="false"
+      :loop="false"
+      type=""
+    />
+    <SwiperBanner
+      v-if="isAfterLaunch"
+      class="sticky--banner"
+      :slides="bannerSlides2"
+      :autoplay="false"
+      :loop="false"
+      type=""
+    />
+    <div class="outer--wrapper" v-if="!isAfterLaunch">
+      <div class="trim--spec--wrap">
+        <ul>
+          <li>
+            <span>소비자 가격</span>
+            <div>
+              62,000,000<em>원 부터<i>*</i></em>
+            </div>
+          </li>
+          <li>
+            <span>엔진</span>
+            <div>Ecoboost 2.3L<em></em></div>
+          </li>
+          <li>
+            <span>승차 정원(명)</span>
+            <div>6 ~ 7</div>
+          </li>
+        </ul>
+      </div>
+
+      <div class="title--visual">
+        <h2>it’s all in the name, 포드 익스플로러</h2>
+        <div class="sub--title">
+          1990년 처음 미국에서 출시된 익스플로러는 대형 SUV라는 새로운 장르를 개척하며
+          35년 넘는 시간 동안 성장과 진화를 거듭해 왔습니다.<br />가장 익스플로러 다운
+          그리고 더욱 새로워진 더 뉴 익스플로러(The new Explorer)를 소개합니다
+        </div>
+      </div>
+
+      <div class="models--visual--01 mt--80">
+        <div class="thumb--wrap">
+          <img src="/img/models/explorer/visual-01.jpg" />
+        </div>
+      </div>
+
+      <div class="car--price--small--pic--wrap mt--120">
+        <div>
+          <h2>Explorer Models</h2>
+          <div class="car--list--wrap">
+            <ul>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/explorer/explorer-stl.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">ST-Line <span class="trim--desc">25MY</span></h2>
+                  <div class="price--wrap">
+                    소비자 가격 : <em>62,000,000 원 부터<i>*</i></em>
+                  </div>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/explorer/explorer-plt.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">Platinum <span class="trim--desc">25MY</span></h2>
+                  <div class="price--wrap">
+                    소비자 가격 : <em>68,000,000 원 부터<i>*</i></em>
+                  </div>
+                </div>
+              </li>
+            </ul>
+          </div>
+        </div>
+      </div>
+
+      <div class="title--visual mt--150">
+        <h2>New face of exploration</h2>
+        <div class="sub--title">
+          더 커진 그릴과 더 날렵해진 헤드 랩프는 익스플로러만의 유니크한 스타일링을
+          완성해주었습니다.<br />
+          익스플로러의 첫 인상을 만들어 내는 전면부 디자인은 전체적으로 무게 중심이 낮게
+          보이도록 설계되어 시각적인 안정감을 제공합니다.
+        </div>
+      </div>
+      <div class="models--visual--01 mt--80 type--2">
+        <div class="thumb--wrap">
+          <img src="/img/models/explorer/visual-02.png" />
+        </div>
+      </div>
+
+      <div class="title--visual mt--150">
+        <h2>ST-line</h2>
+        <div class="sub--title">
+          Stylish 대형 SUV라는 새로운 장르를 개척한 ST-Line은 도심 속에서 더욱 빛을
+          발합니다.<br />
+          하지만 도심을 벗어나 오프로드에 진입하는 순간에는 익스플로러라는 이름에 걸맞은
+          4WD SUV 본연의 역할을 충실히 해냅니다.<br />
+          다재다능한 The New Explorer ST-Line과 합께 하십시오.
+        </div>
+      </div>
+      <div class="models--visual--01 mt--80 type--2">
+        <div class="thumb--wrap">
+          <img src="/img/models/explorer/visual-03.png" />
+        </div>
+      </div>
+
+      <div class="title--visual mt--150">
+        <h2>platinum</h2>
+        <div class="sub--title">
+          독특한 문양의 그릴이 돋보이는 Platinum 시리즈는 도심형 SUV의 진수를
+          보여드립니다.<br />
+          최대 7명까지 탑승 가능한 Platinum 시리즈는 많은 인원이 함께 여정을 함께하는
+          순간에도 편안함을 제공하며 패밀리 SUV로서 명성을 충분히 수행합니다.
+        </div>
+      </div>
+      <div class="models--visual--01 mt--80 type--2">
+        <div class="thumb--wrap">
+          <img src="/img/models/explorer/visual-04.png" />
+        </div>
+      </div>
+
+      <div class="title--visual mt--150">
+        <h2>gallery</h2>
+        <!-- <div class="sub--title">
+          가장 익스플로러 다운 그리고 더욱 새로워진 더 뉴 익스플로러 (The new explorer)를
+          소개합니다.
+        </div> -->
+      </div>
+      <div class="gallery--wrap mt--80">
+        <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery1"
+            :autoplay="false"
+            :loop="false"
+            type=""
+          />
+        </div>
+        <!-- <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery2"
+            :autoplay="false"
+            :loop="false"
+            type="interior"
+          />
+        </div> -->
+      </div>
+      <div class="nav--dis--wrap model mt--150" :class="{ active: isDisOpen }">
+        <div class="dis--btn" @click="isDisOpen = !isDisOpen">disclosures <i class="ico"></i></div>
+        <div class="dis--cont">
+          <div class="container">
+            <p>
+             *본 웹사이트에 공지된 가격정보는 부가세(VAT)를 포함하고 있습니다. 본 가격은 개별소비세 인하에 관한 정부시책을 반영한 것이며 가격정보는 예고없이 변동될 수 있습니다. 
+             가격정보는 고객님의 차량 구입에 도움을 드리고자 함이며 법적 구속력은 없습니다. <br/><br/>
+             사이트에서 제공하는 차량에 대한 모든 이미지 또는 사양은 실제 판매 모델과 다를 수 있습니다. 정확한 정보는 가까운 공식 딜러를 통해 확인하시기 바랍니다.            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="outer--wrapper" v-if="isAfterLaunch">
+      <div class="trim--spec--wrap">
+        <ul>
+          <li>
+            <span>소비자 가격</span>
+            <div>
+              77,500,000<em>원 부터<i>*</i></em>
+            </div>
+          </li>
+          <li>
+            <span>엔진</span>
+            <div>2.3L Ecoboost  I-4<br />3.0L Ecoboost V6</div>
+          </li>
+          <li>
+            <span>승차 정원(명)</span>
+            <div>6 ~ 7</div>
+          </li>
+        </ul>
+      </div>
+
+      <div class="title--visual">
+        <h2>IT’S ALL IN THE NAME, FORD EXPLORER<br/>익스플로러 시작은 호기심과 확인</h2>
+        <div class="sub--title">
+          국내 대형 SUV를 개척해온 익스플로러는 오랜시간 동안 성장과 진화를 거듭해 왔습니다.<br/>가장 익스플로러다운 그리고 더욱 새로워진 뉴 익스플로러(The new Explorer)를 소개합니다.
+        </div>
+      </div>
+
+      <div class="models--visual--01 type--5 mt--80">
+        <div class="thumb--wrap">
+          <ClientOnly>
+           <video
+              autoplay
+              muted
+              loop
+              playsinline              
+              poster="https://audi.interscope.co.kr/video/flak/flak_26my_web.mp4"
+              preload="metadata"
+            >
+              <source src="https://audi.interscope.co.kr/video/flak/flak_26my_web.mp4" type="video/mp4" />
+            </video>
+            </ClientOnly>
+        </div>
+      </div>
+
+      <div class="car--price--small--pic--wrap mt--120">
+        <div>
+          <h2>Models</h2>
+          <div class="car--list--wrap">
+            <ul>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/explorer/n-explorer-tremor.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">Tremor</h2>
+                  <div class="price--wrap flex--column">
+                    <div>소비자 가격 : <em>88,500,000 원<i>*</i></em></div>
+                    <div>엔진 : <em>3.0L Ecoboost V6</em></div>
+                    <div>승차 정원 (명) : <em>6</em></div>
+                  </div>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/explorer/n-explorer-stl.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">ST-Line</h2>
+                  <div class="price--wrap flex--column">
+                    <div>소비자 가격 : <em>77,500,000 원<i>*</i></em></div>
+                    <div>엔진 : <em>2.3L Ecoboost I-4</em></div>
+                    <div>승차 정원 (명) : <em>6</em></div>
+                  </div>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/explorer/n-explorer-plt.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">Platinum</h2>
+                  <div class="price--wrap flex--column">
+                    <div>소비자 가격 : <em>84,500,000 원<i>*</i></em></div>
+                    <div>엔진 : <em>2.3L Ecoboost I-4</em></div>
+                    <div>승차 정원 (명) : <em>7</em></div>
+                  </div>
+                </div>
+              </li>
+            </ul>
+          </div>
+        </div>
+      </div>
+
+      <div class="car--color--pick--wrap mt--120">
+        <div>
+          <div class="tab">
+            <span
+              :class="{ active: activeColorTab === 0 }"
+              @click="activeColorTab = 0"
+            >Tremor</span>
+            <span
+              :class="{ active: activeColorTab === 1 }"
+              @click="activeColorTab = 1"
+            >ST-Line</span>
+            <span
+              :class="{ active: activeColorTab === 2 }"
+              @click="activeColorTab = 2"
+            >Platinum</span>
+          </div>
+
+          <div v-show="activeColorTab === 0">
+            <ColorPicker
+              title="Tremor"
+              :colors="colorpick3"
+              titleOption
+              class="mt--80"
+            />
+          </div>
+          <div v-show="activeColorTab === 1">
+            <ColorPicker
+              title="st-line"
+              :colors="colorpick2"
+              titleOption
+              class="mt--80"
+            />
+          </div>
+          <div v-show="activeColorTab === 2">
+            <ColorPicker
+              title="platinum"
+              titleOption
+              :colors="colorpick"
+              class="mt--80"
+            />
+          </div>
+        </div>
+      </div>
+
+      <div class="models--visual--tab--type">
+        <h3 class="title--interior">Interior Theme</h3>
+        <div v-show="activeColorTab === 0" class="">
+          <div class="thumb--wrap none--border">
+            <img src="/img/models/explorer/2026/img--tremor2.jpg" alt="">
+          </div>
+        </div>
+        <div v-show="activeColorTab === 1" class="">
+          <div class="thumb--wrap none--border">
+            <img src="/img/models/explorer/2026/img--stline2.jpg" alt="">
+          </div>
+        </div>
+        <div v-show="activeColorTab === 2" class="">
+          <div class="thumb--wrap none--border">
+            <img src="/img/models/explorer/2026/img--platinum2.jpg" alt="">
+          </div>
+        </div>
+      </div>
+
+      <div class="models--visual--3view mt--100">
+        <div class="thumb--wrap">
+          <img src="/img/models/explorer/2026/img--tremor1.jpg" alt="Tremor">
+        </div>
+        <div class="thumb--wrap">
+          <img src="/img/models/explorer/2026/img--stline1.jpg" alt="ST-Line">
+        </div>
+        <div class="thumb--wrap">
+          <img src="/img/models/explorer/2026/img--platinum1.jpg" alt="Platinum">
+        </div>
+      </div>
+
+      <div class="title--visual mt--150">
+        <h2>gallery</h2>
+        <!-- <div class="sub--title">
+          가장 익스플로러 다운 그리고 더욱 새로워진 더 뉴 익스플로러 (The new explorer)를
+          소개합니다.
+        </div> -->
+      </div>
+      <div class="gallery--wrap mt--80">
+        <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery2"
+            :autoplay="false"
+            :loop="false"
+            type=""
+          />
+        </div>
+        <!-- <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery2"
+            :autoplay="false"
+            :loop="false"
+            type="interior"
+          />
+        </div> -->
+      </div>
+      <div class="nav--dis--wrap model mt--150" :class="{ active: isDisOpen }">
+        <div class="dis--btn" @click="isDisOpen = !isDisOpen">disclosures <i class="ico"></i></div>
+        <div class="dis--cont">
+          <div class="container">
+            <p>
+             *본 웹사이트에 공지된 가격정보는 부가세(VAT)를 포함하고 있습니다. 본 가격은 개별소비세 인하에 관한 정부시책을 반영한 것이며 가격정보는 예고없이 변동될 수 있습니다. 
+             가격정보는 고객님의 차량 구입에 도움을 드리고자 함이며 법적 구속력은 없습니다. <br/><br/>
+             사이트에서 제공하는 차량에 대한 모든 이미지 또는 사양은 실제 판매 모델과 다를 수 있습니다. 정확한 정보는 가까운 공식 딜러를 통해 확인하시기 바랍니다.            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </main>
+</template>
+
+<script setup>
+  const isAfterLaunch = useTimeSwitch('2026-04-15T08:00:00+09:00');
+
+  // disclosures 토글 (기본 열림)
+  const isDisOpen = ref(true);
+  const activeColorTab = ref(0);
+  const activeIntTab = ref(0);
+  const bannerSlides = ref([
+    {
+      image: "/img/models/explorer/exp_banner01.png",
+      moImage: "/img/models/explorer/exp_banner01_mo.jpg",
+      alt: "the new ford explorer",
+      title: "the new ford explorer",
+      subtitle: "익스플로러 시작은 호기심과 확신",
+      smalldesc: "",
+      morelink: "/ford/network",
+      morelinktitle: "시승 신청",
+      morelinktarget: "",
+    },
+  ]);
+
+  const bannerSlides2 = ref([
+    {
+      image: "/img/models/explorer/2026/banner.png",
+      moImage: "/img/models/explorer/2026/banner_mo.jpg",
+      alt: "",
+      title: "",
+      subtitle: "",
+      smalldesc: "",
+      // morelink: "/ford/network",
+      // morelinktitle: "시승 신청",
+      morelinktarget: "",
+    },
+  ]);
+
+  const colorpick = ref([
+    {
+      image : "/img/models/explorer/exp_agate_black.png",
+      alt : "",
+      title : "Agate Black",
+      colorValue : "#535353"
+    },
+    {
+      image : "/img/models/explorer/exp_space_white.png",
+      alt : "",
+      title : "Space White",
+      colorValue : "#939BA3"
+    },
+    {
+      image : "/img/models/explorer/exp_carbonized_gray.png",
+      alt : "",
+      title : "Carbonized Gray",
+      colorValue : "#58585B"
+    },
+    {
+      image : "/img/models/explorer/exp_rapid_red.png",
+      alt : "",
+      title : "Rapid Red",
+      colorValue : "#A21E26"
+    },
+    {
+      image : "/img/models/explorer/exp_star_white.png",
+      alt : "",
+      title : "Star White",
+      colorValue : "#CACBC5"
+    }   
+  ])
+  const colorpick2 = ref([
+    {
+      image : "/img/models/explorer/exp_st_agate_black.png",
+      alt : "",
+      title : "Agate Black",
+      colorValue : "#535353"
+    },
+    {
+      image : "/img/models/explorer/exp_st_marsh_gray.png",
+      alt : "",
+      title : "Marsh Gray",
+      colorValue : "#5B534D"
+    },
+    {
+      image : "/img/models/explorer/exp_st_carbonized_gray.png",
+      alt : "",
+      title : "Carbonized Gray",
+      colorValue : "#58585B"
+    },
+    {
+      image : "/img/models/explorer/exp_st_vapor_blue.png",
+      alt : "",
+      title : "Vapor Blue",
+      colorValue : "#384F64"
+    },
+    {
+      image : "/img/models/explorer/exp_st_rapid_red.png",
+      alt : "",
+      title : "Rapid Red",
+      colorValue : "#A21E26"
+    },
+    {
+      image : "/img/models/explorer/exp_st_star_white.png",
+      alt : "",
+      title : "Star White",
+      colorValue : "#CACBC5"
+    }   
+  ])
+  const colorpick3 = ref([
+    {
+      image : "/img/models/explorer/exp_tremor_agate_black.png",
+      alt : "",
+      title : "Agate Black",
+      colorValue : "#535353"
+    },
+    {
+      image : "/img/models/explorer/exp_tremor_marsh_gray.png",
+      alt : "",
+      title : "Marsh Gray",
+      colorValue : "#5B534D"
+    },
+    {
+      image : "/img/models/explorer/exp_tremor_space_white.png",
+      alt : "",
+      title : "Space White",
+      colorValue : "#A0A7B1"
+    },   
+    {
+      image : "/img/models/explorer/exp_tremor_carbonized_gray.png",
+      alt : "",
+      title : "Carbonized Gray",
+      colorValue : "#58585B"
+    },
+    {
+      image : "/img/models/explorer/exp_tremor_vapor_blue.png",
+      alt : "",
+      title : "Vapor Blue",
+      colorValue : "#384F64"
+    },
+    {
+      image : "/img/models/explorer/exp_tremor_star_white.png",
+      alt : "",
+      title : "Star White",
+      colorValue : "#CACBC5"
+    },
+    
+  ])
+
+  const gallery1 = ref([
+    { image: "/img/models/explorer/gallery_1_1.jpg", },
+    { image: "/img/models/explorer/gallery_1_2.jpg", },
+    { image: "/img/models/explorer/gallery_1_3.jpg", },
+    { image: "/img/models/explorer/gallery_1_4.avif", },
+    { image: "/img/models/explorer/gallery_1_5.avif", },
+    { image: "/img/models/explorer/gallery_1_6.avif", },
+    { image: "/img/models/explorer/gallery_1_7.avif", },
+    { image: "/img/models/explorer/gallery_1_8.avif", },
+    { image: "/img/models/explorer/gallery_1_9.avif", },
+    { image: "/img/models/explorer/gallery_1_10.avif", },
+    { image: "/img/models/explorer/gallery_1_11.avif", },
+    { image: "/img/models/explorer/gallery_1_12.avif", },
+    { image: "/img/models/explorer/gallery_1_13.avif", },
+    { image: "/img/models/explorer/gallery_1_14.avif", },
+    { image: "/img/models/explorer/gallery_1_15.avif", },
+    { image: "/img/models/explorer/gallery_1_16.jpg", },
+    { image: "/img/models/explorer/gallery_1_17.jpg", },
+    { image: "/img/models/explorer/gallery_1_18.avif", },
+    { image: "/img/models/explorer/gallery_1_19.avif", },
+    { image: "/img/models/explorer/gallery_1_20.avif", },
+    { image: "/img/models/explorer/gallery_2_1.avif", },
+    { image: "/img/models/explorer/gallery_2_2.avif", },
+    { image: "/img/models/explorer/gallery_2_3.avif", },
+    { image: "/img/models/explorer/gallery_2_4.avif", },
+    { image: "/img/models/explorer/gallery_2_5.avif", },
+    { image: "/img/models/explorer/gallery_2_6.avif", },
+    { image: "/img/models/explorer/gallery_2_7.avif", },
+    { image: "/img/models/explorer/gallery_2_8.avif", },
+    { image: "/img/models/explorer/gallery_2_9.jpg", },
+    { image: "/img/models/explorer/gallery_2_10.avif", },
+    { image: "/img/models/explorer/gallery_2_11.avif", },
+    { image: "/img/models/explorer/gallery_2_12.avif", },
+    { image: "/img/models/explorer/gallery_2_13.avif", },
+    { image: "/img/models/explorer/gallery_2_14.avif", },
+    { image: "/img/models/explorer/gallery_2_15.avif", },
+    { image: "/img/models/explorer/gallery_2_16.jpg", },
+    { image: "/img/models/explorer/gallery_2_17.webp", },
+    { image: "/img/models/explorer/gallery_2_18.webp", },
+    { image: "/img/models/explorer/gallery_2_19.webp", },
+    { image: "/img/models/explorer/gallery_2_20.webp", },
+  ]);
+
+  const gallery2 = ref([
+    { image: "/img/models/explorer/2026/gallery1.jpg", },
+    { image: "/img/models/explorer/2026/gallery2.jpg", },
+    { image: "/img/models/explorer/2026/gallery3.jpg", },
+    { image: "/img/models/explorer/2026/gallery4.jpg", },
+    { image: "/img/models/explorer/2026/gallery5.jpg", },
+    { image: "/img/models/explorer/2026/gallery6.jpg", },
+    { image: "/img/models/explorer/2026/gallery7.jpg", },
+    { image: "/img/models/explorer/2026/gallery8.jpg", },
+    { image: "/img/models/explorer/2026/gallery9.jpg", },
+    { image: "/img/models/explorer/2026/gallery10.jpg", },
+    { image: "/img/models/explorer/2026/gallery11.jpg", },
+    { image: "/img/models/explorer/2026/gallery12.jpg", },
+    { image: "/img/models/explorer/2026/gallery13.jpg", },
+    { image: "/img/models/explorer/2026/gallery14.jpg", },
+    { image: "/img/models/explorer/2026/gallery15.jpg", },
+    { image: "/img/models/explorer/2026/gallery16.jpg", },
+    { image: "/img/models/explorer/2026/gallery17.jpg", },
+    { image: "/img/models/explorer/2026/gallery18.jpg", },
+    { image: "/img/models/explorer/2026/gallery19.jpg", },
+    { image: "/img/models/explorer/2026/gallery20.jpg", },
+    { image: "/img/models/explorer/2026/gallery21.jpg", },
+    { image: "/img/models/explorer/2026/gallery22.jpg", },
+    { image: "/img/models/explorer/2026/gallery23.jpg", },
+    { image: "/img/models/explorer/2026/gallery24.jpg", },
+  ]);
+</script>

+ 375 - 0
app/pages/ford/vehicle/explorer2.vue

@@ -0,0 +1,375 @@
+<template>
+  <main>
+    <SwiperBanner
+      class="sticky--banner"
+      :slides="bannerSlides"
+      :autoplay="false"
+      :loop="false"
+      type=""
+    />
+    <div class="outer--wrapper">
+        <div class="trim--spec--wrap">
+        <ul>
+          <li>
+            <span>소비자 가격</span>
+            <div>
+              77,500,000<em>원 부터<i>*</i></em>
+            </div>
+          </li>
+          <li>
+            <span>엔진</span>
+            <div>2.3L Ecoboost  I-4<br />3.0L Ecoboost V6</div>
+          </li>
+          <li>
+            <span>승차 정원(명)</span>
+            <div>6 ~ 7</div>
+          </li>
+        </ul>
+      </div>
+
+      <div class="title--visual">
+        <h2>IT’S ALL IN THE NAME, FORD EXPLORER<br/>익스플로러 시작은 호기심과 확인</h2>
+        <div class="sub--title">
+          국내 대형 SUV를 개척해온 익스플로러는 오랜시간 동안 성장과 진화를 거듭해 왔습니다.<br/>가장 익스플로러다운 그리고 더욱 새로워진 뉴 익스플로러(The new Explorer)를 소개합니다.
+        </div>
+      </div>
+
+      <div class="models--visual--01 type--5 mt--80">
+        <div class="thumb--wrap">
+          <ClientOnly>
+           <video
+              autoplay
+              muted
+              loop
+              playsinline              
+              poster="https://audi.interscope.co.kr/video/flak/flak_26my_web.mp4"
+              preload="metadata"
+            >
+              <source src="https://audi.interscope.co.kr/video/flak/flak_26my_web.mp4" type="video/mp4" />
+            </video>
+            </ClientOnly>
+        </div>
+      </div>
+
+      <div class="car--price--small--pic--wrap mt--120">
+        <div>
+          <h2>Models</h2>
+          <div class="car--list--wrap">
+            <ul>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/explorer/n-explorer-tremor.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">Tremor</h2>
+                  <div class="price--wrap flex--column">
+                    <div>소비자 가격 : <em>88,500,000 원<i>*</i></em></div>
+                    <div>엔진 : <em>3.0L Ecoboost V6</em></div>
+                    <div>승차 정원 (명) : <em>6</em></div>
+                  </div>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/explorer/n-explorer-stl.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">ST-Line</h2>
+                  <div class="price--wrap flex--column">
+                    <div>소비자 가격 : <em>77,500,000 원<i>*</i></em></div>
+                    <div>엔진 : <em>2.3L Ecoboost I-4</em></div>
+                    <div>승차 정원 (명) : <em>6</em></div>
+                  </div>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/explorer/n-explorer-plt.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">Platinum</h2>
+                  <div class="price--wrap flex--column">
+                    <div>소비자 가격 : <em>84,500,000 원<i>*</i></em></div>
+                    <div>엔진 : <em>2.3L Ecoboost I-4</em></div>
+                    <div>승차 정원 (명) : <em>7</em></div>
+                  </div>
+                </div>
+              </li>
+            </ul>
+          </div>
+        </div>
+      </div>
+
+      <div class="car--color--pick--wrap mt--120">
+        <div>
+          <div class="tab">
+            <span
+              :class="{ active: activeColorTab === 0 }"
+              @click="activeColorTab = 0"
+            >Tremor</span>
+            <span
+              :class="{ active: activeColorTab === 1 }"
+              @click="activeColorTab = 1"
+            >ST-Line</span>
+            <span
+              :class="{ active: activeColorTab === 2 }"
+              @click="activeColorTab = 2"
+            >Platinum</span>
+          </div>
+
+          <div v-show="activeColorTab === 0">
+            <ColorPicker
+              title="Tremor"
+              :colors="colorpick3"
+              titleOption
+              class="mt--80"
+            />
+          </div>
+          <div v-show="activeColorTab === 1">
+            <ColorPicker
+              title="st-line"
+              :colors="colorpick2"
+              titleOption
+              class="mt--80"
+            />
+          </div>
+          <div v-show="activeColorTab === 2">
+            <ColorPicker
+              title="platinum"
+              titleOption
+              :colors="colorpick"
+              class="mt--80"
+            />
+          </div>
+        </div>
+      </div>
+
+      <div class="models--visual--tab--type">
+        <h3 class="title--interior">Interior Theme</h3>
+        <div v-show="activeColorTab === 0" class="">
+          <div class="thumb--wrap none--border">
+            <img src="/img/models/explorer/2026/img--tremor2.jpg" alt="">
+          </div>
+        </div>
+        <div v-show="activeColorTab === 1" class="">
+          <div class="thumb--wrap none--border">
+            <img src="/img/models/explorer/2026/img--stline2.jpg" alt="">
+          </div>
+        </div>
+        <div v-show="activeColorTab === 2" class="">
+          <div class="thumb--wrap none--border">
+            <img src="/img/models/explorer/2026/img--platinum2.jpg" alt="">
+          </div>
+        </div>
+      </div>
+
+      <div class="models--visual--3view mt--100">
+        <div class="thumb--wrap">
+          <img src="/img/models/explorer/2026/img--tremor1.jpg" alt="Tremor">
+        </div>
+        <div class="thumb--wrap">
+          <img src="/img/models/explorer/2026/img--stline1.jpg" alt="ST-Line">
+        </div>
+        <div class="thumb--wrap">
+          <img src="/img/models/explorer/2026/img--platinum1.jpg" alt="Platinum">
+        </div>
+      </div>
+
+      <div class="title--visual mt--150">
+        <h2>gallery</h2>
+        <!-- <div class="sub--title">
+          가장 익스플로러 다운 그리고 더욱 새로워진 더 뉴 익스플로러 (The new explorer)를
+          소개합니다.
+        </div> -->
+      </div>
+      <div class="gallery--wrap mt--80">
+        <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery1"
+            :autoplay="false"
+            :loop="false"
+            type=""
+          />
+        </div>
+        <!-- <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery2"
+            :autoplay="false"
+            :loop="false"
+            type="interior"
+          />
+        </div> -->
+      </div>
+      <div class="nav--dis--wrap model mt--150" :class="{ active: isDisOpen }">
+        <div class="dis--btn" @click="isDisOpen = !isDisOpen">disclosures <i class="ico"></i></div>
+        <div class="dis--cont">
+          <div class="container">
+            <p>
+             *본 웹사이트에 공지된 가격정보는 부가세(VAT)를 포함하고 있습니다. 본 가격은 개별소비세 인하에 관한 정부시책을 반영한 것이며 가격정보는 예고없이 변동될 수 있습니다. 
+             가격정보는 고객님의 차량 구입에 도움을 드리고자 함이며 법적 구속력은 없습니다. <br/><br/>
+             사이트에서 제공하는 차량에 대한 모든 이미지 또는 사양은 실제 판매 모델과 다를 수 있습니다. 정확한 정보는 가까운 공식 딜러를 통해 확인하시기 바랍니다.            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </main>
+</template>
+
+<script setup>
+  // disclosures 토글 (기본 열림)
+  const isDisOpen = ref(true);  
+  const activeColorTab = ref(0);
+  const bannerSlides = ref([
+    {
+      image: "/img/models/explorer/2026/banner.png",
+      moImage: "/img/models/explorer/2026/banner_mo.png",
+      alt: "",
+      title: "",
+      subtitle: "",
+      smalldesc: "",
+      // morelink: "/ford/network",
+      // morelinktitle: "시승 신청",
+      morelinktarget: "",
+    },
+  ]);
+
+  const gallery1 = ref([
+    { image: "/img/models/explorer/2026/gallery1.jpg", },
+    { image: "/img/models/explorer/2026/gallery2.jpg", },
+    { image: "/img/models/explorer/2026/gallery3.jpg", },
+    { image: "/img/models/explorer/2026/gallery4.jpg", },
+    { image: "/img/models/explorer/2026/gallery5.jpg", },
+    { image: "/img/models/explorer/2026/gallery6.jpg", },
+    { image: "/img/models/explorer/2026/gallery7.jpg", },
+    { image: "/img/models/explorer/2026/gallery8.jpg", },
+    { image: "/img/models/explorer/2026/gallery9.jpg", },
+    { image: "/img/models/explorer/2026/gallery10.jpg", },
+    { image: "/img/models/explorer/2026/gallery11.jpg", },
+    { image: "/img/models/explorer/2026/gallery12.jpg", },
+    { image: "/img/models/explorer/2026/gallery13.jpg", },
+    { image: "/img/models/explorer/2026/gallery14.jpg", },
+    { image: "/img/models/explorer/2026/gallery15.jpg", },
+    { image: "/img/models/explorer/2026/gallery16.jpg", },
+    { image: "/img/models/explorer/2026/gallery17.jpg", },
+    { image: "/img/models/explorer/2026/gallery18.jpg", },
+    { image: "/img/models/explorer/2026/gallery19.jpg", },
+    { image: "/img/models/explorer/2026/gallery20.jpg", },
+    { image: "/img/models/explorer/2026/gallery21.jpg", },
+    { image: "/img/models/explorer/2026/gallery22.jpg", },
+    { image: "/img/models/explorer/2026/gallery23.jpg", },
+    { image: "/img/models/explorer/2026/gallery24.jpg", },
+  ]);
+
+
+
+
+  const colorpick = ref([
+    {
+      image : "/img/models/explorer/exp_agate_black.png",
+      alt : "",
+      title : "Agate Black",
+      colorValue : "#535353"
+    },
+    {
+      image : "/img/models/explorer/exp_space_white.png",
+      alt : "",
+      title : "Space White",
+      colorValue : "#939BA3"
+    },
+    {
+      image : "/img/models/explorer/exp_carbonized_gray.png",
+      alt : "",
+      title : "Carbonized Gray",
+      colorValue : "#58585B"
+    },
+    {
+      image : "/img/models/explorer/exp_rapid_red.png",
+      alt : "",
+      title : "Rapid Red",
+      colorValue : "#A21E26"
+    },
+    {
+      image : "/img/models/explorer/exp_star_white.png",
+      alt : "",
+      title : "Star White",
+      colorValue : "#CACBC5"
+    }   
+  ])
+  const colorpick2 = ref([
+    {
+      image : "/img/models/explorer/exp_st_agate_black.png",
+      alt : "",
+      title : "Agate Black",
+      colorValue : "#535353"
+    },
+    {
+      image : "/img/models/explorer/exp_st_marsh_gray.png",
+      alt : "",
+      title : "Marsh Gray",
+      colorValue : "#5B534D"
+    },
+    {
+      image : "/img/models/explorer/exp_st_carbonized_gray.png",
+      alt : "",
+      title : "Carbonized Gray",
+      colorValue : "#58585B"
+    },
+    {
+      image : "/img/models/explorer/exp_st_vapor_blue.png",
+      alt : "",
+      title : "Vapor Blue",
+      colorValue : "#384F64"
+    },
+    {
+      image : "/img/models/explorer/exp_st_rapid_red.png",
+      alt : "",
+      title : "Rapid Red",
+      colorValue : "#A21E26"
+    },
+    {
+      image : "/img/models/explorer/exp_st_star_white.png",
+      alt : "",
+      title : "Star White",
+      colorValue : "#CACBC5"
+    }   
+  ])
+  const colorpick3 = ref([
+    {
+      image : "/img/models/explorer/exp_tremor_agate_black.png",
+      alt : "",
+      title : "Agate Black",
+      colorValue : "#535353"
+    },
+    {
+      image : "/img/models/explorer/exp_tremor_marsh_gray.png",
+      alt : "",
+      title : "Marsh Gray",
+      colorValue : "#5B534D"
+    },
+    {
+      image : "/img/models/explorer/exp_tremor_space_white.png",
+      alt : "",
+      title : "Space White",
+      colorValue : "#A0A7B1"
+    },   
+    {
+      image : "/img/models/explorer/exp_tremor_carbonized_gray.png",
+      alt : "",
+      title : "Carbonized Gray",
+      colorValue : "#58585B"
+    },
+    {
+      image : "/img/models/explorer/exp_tremor_vapor_blue.png",
+      alt : "",
+      title : "Vapor Blue",
+      colorValue : "#384F64"
+    },
+    {
+      image : "/img/models/explorer/exp_tremor_star_white.png",
+      alt : "",
+      title : "Star White",
+      colorValue : "#CACBC5"
+    },
+    
+  ])
+</script>

+ 383 - 0
app/pages/ford/vehicle/mustang.vue

@@ -0,0 +1,383 @@
+<template>
+  <main>
+    <SwiperBanner
+      class="sticky--banner"
+      :slides="bannerSlides"
+      :autoplay="false"
+      :loop="false"
+      type=""
+    />
+    <div class="outer--wrapper">
+      <div class="trim--spec--wrap">
+        <ul>
+          <li>
+            <span>소비자 가격</span>
+            <div>
+              59,200,000<em>원 부터<i>*</i></em>
+            </div>
+          </li>
+          <li>
+            <span>엔진</span>
+            <div>EcoBoost® 2.3L<span></span>Ti-VCT V8 5.0L</div>
+          </li>
+          <li>
+            <span>승차 정원(명)</span>
+            <div>4</div>
+          </li>
+        </ul>
+      </div>
+
+      <div class="title--visual">
+        <h2>RULES OF THE OPEN ROAD</h2>
+        <div class="sub--title">
+          2025 FORD MUSTANG 현대적이고 헤리티지에서 영감을 받은 디자인은 마주하는 순간
+          영혼을 사로잡힐 정도의 매력이 있습니다.<br />
+          열린 도로. 닫힌 트랙. 이 전설적인 스포츠카의 아이콘은 모두를 삼켜 버립니다.
+        </div>
+      </div>
+
+      <div class="models--visual--01 type--3 mt--80">
+        <div class="thumb--wrap">
+          <img src="/img/models/mustang/visual-01.jpg" />
+        </div>
+      </div>
+
+      <div class="car--price--small--pic--wrap mt--120">
+        <div>
+          <h2>Mustang Models</h2>
+          <div class="car--list--wrap">
+            <ul>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/mustang/mustang01.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">
+                    Ecoboost Premium<br />
+                    Coupe
+                  </h2>
+                  <div class="price--wrap">
+                    소비자 가격 : <em>59,200,000 원 부터<i>*</i></em>
+                  </div>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/mustang/mustang02.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">
+                    Ecoboost Premium<br />
+                    Convertible
+                  </h2>
+                  <div class="price--wrap">
+                    소비자 가격 : <em>66,200,000 원 부터<i>*</i></em>
+                  </div>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/mustang/mustang03.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">
+                    GT Premium Coupe<br />
+                    Standard
+                  </h2>
+                  <div class="price--wrap">
+                    소비자 가격 : <em>78,900,000 원 부터<i>*</i></em>
+                  </div>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/mustang/mustang04.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">
+                    GT Premium<br />
+                    Convertible Standard
+                  </h2>
+                  <div class="price--wrap">
+                    소비자 가격 : <em>85,000,000 원 부터<i>*</i></em>
+                  </div>
+                </div>
+              </li>
+            </ul>
+          </div>
+        </div>
+      </div>
+
+      <div class="title--visual mt--150">
+        <h2>THE LEGEND THAT KEEPS YOU COMING BACK</h2>
+        <div class="sub--title">
+          강력한 4기통 엔진이 장착된 MUSTANG ECOBOOST PREMIUM 모델은 2.3L 퍼포먼스
+          패키지와 함께하여 만족할만한 성능을 발휘합니다.<br />
+          MUSTANG 5.0L GT 모델은 원하시는 대로 트랙을 마음껏 달릴 수 있도록
+          설계되었습니다.<br />
+          MUSTANG에 올라 버클을 채우십시오. 당신의 삶은 놀라운 속도로 움직일 것입니다.
+        </div>
+      </div>
+      <div class="models--visual--grid mt--80 type--2">
+        <ul>
+          <li>
+            <div class="thumb--wrap">
+              <img src="/img/models/mustang/grid-01.jpg" />
+            </div>
+            <div class="desc--wrap">
+              <h2>OPEN-AIR FREEDOM</h2>
+              <div class="captions">
+                MUSTANG CONVERTIBLE과 함께 하고 계시다면<br />
+                TOP을 제거하여 열어보십시오.<br />
+                코끝으로 느껴지는 신선한 공기가 당신을<br />
+                새롭게 만들어드릴 것입니다.
+              </div>
+            </div>
+          </li>
+          <li>
+            <div class="thumb--wrap">
+              <img src="/img/models/mustang/grid-02.jpg" />
+            </div>
+            <div class="desc--wrap">
+              <h2>HIGH-PERFORMANCE HANDLING</h2>
+              <div class="captions">
+                2025 Ford Mustang에 적용된 Flat bottom<br />
+                스티어링 휠은 모두 4조각의 가죽으로 마감되어<br />
+                있으며 각 부분에 적절하게 조화된 크롬 베젤과도<br />
+                잘 조화를 이룹니다. Mustang의 스티어링 휠은<br />
+                도심, 트랙 어느 곳에서도 경주용 차량과 같은<br />
+                컨트롤이 가능할 수 있도록 도와드립니다.
+              </div>
+            </div>
+          </li>
+          <li>
+            <div class="thumb--wrap">
+              <img src="/img/models/mustang/grid-03.jpg" />
+            </div>
+            <div class="desc--wrap">
+              <h2>IMMERSIVE DIGITAL COCKPIT</h2>
+              <div class="captions">
+                매일 반복되는 일상적인 드라이빙에는 가끔 변화가<br />
+                필요합니다. 사용자가 설정이 가능한 LCD 디지털<br />
+                클러스터 및 센터 터치스크린을 통해 원하시는<br />
+                디자인으로 디스플레이를 변경해 보십시오.
+              </div>
+            </div>
+          </li>
+        </ul>
+      </div>
+
+      <!-- <SwiperBanner3
+        class="mt--100"
+        :slides="bannerSlides2"
+        :autoplay="false"
+        :loop="false"
+        mtitle="design"
+        stitle="2025 Ford Mustang 라인업은 보는 순간 당신의 영혼을 자극할 수 있을 정도로 세심하게 제작되었습니다.<br/>
+  헤리티지에서 영감을 받은 모던한 디자인에는 날렵한 라인과 질주 본능 등 정통 머스탱의 특징이 그대로 반영되어 있습니다.<br/>
+  그리고 보는 이의 눈길을 사로잡는 11가지의 컬러 및 3가지 인테리어 옵션을 통해 어디를 가든지 자신만의 개성을 드러낼 수 있습니다."
+        type=""
+      />
+
+      <SwiperBanner3
+        :slides="bannerSlides3"
+        :autoplay="false"
+        :loop="false"
+        type=""
+        mtitle="technology"
+        stitle="아메리칸 머슬카를 대표하는 머스탱은 장애물이 있는 도로나 깊은 코너링에서도 최적의 성능을 발휘합니다.<br/>
+  이는 60년의 역사를 지닌 머스탱 이기에 가능한 일입니다."
+      /> -->
+
+      <div class="title--visual mt--150">
+        <h2>gallery</h2>
+        <!-- <div class="sub--title">
+          여전히 많은 이들이 MUSTANG이라는 이름만 들어도 가슴이 설레이실 것입니다. 바로
+          퍼포먼스의 모든 것, 포드MUSTANG 입니다.
+        </div> -->
+      </div>
+      <div class="gallery--wrap mt--80">
+        <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery1"
+            :autoplay="false"
+            :loop="false"
+            type=""
+          />
+        </div>
+        <!-- <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery2"
+            :autoplay="false"
+            :loop="false"
+            type="interior"
+          />
+        </div> -->
+      </div>
+      <div class="nav--dis--wrap model mt--150" :class="{ active: isDisOpen }">
+        <div class="dis--btn" @click="isDisOpen = !isDisOpen">disclosures <i class="ico"></i></div>
+        <div class="dis--cont">
+          <div class="container">
+            <p>
+              *본 웹사이트에 공지된 가격정보는 부가세(VAT)를 포함하고 있습니다. 본 가격은 개별소비세 인하에 관한 정부시책을 반영한 것이며 가격정보는 예고없이 변동될 수 있습니다. 
+             가격정보는 고객님의 차량 구입에 도움을 드리고자 함이며 법적 구속력은 없습니다. <br/><br/>
+             사이트에서 제공하는 차량에 대한 모든 이미지 또는 사양은 실제 판매 모델과 다를 수 있습니다. 정확한 정보는 가까운 공식 딜러를 통해 확인하시기 바랍니다.
+            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </main>
+</template>
+
+<script setup>
+  // disclosures 토글 (기본 열림)
+  const isDisOpen = ref(true);
+  const bannerSlides = ref([
+    {
+      image: "/img/models/mustang/mustang_banner01.jpg",
+      moImage: "/img/models/mustang/mustang_banner01_mo.jpg",
+      alt: "Ford mustang",
+      title: "Ford mustang",
+      subtitle: "머스탱을 소유할 최고의 타이밍",
+      smalldesc: "",
+      morelink: "/ford/network",
+      morelinktitle: "시승 신청",
+      morelinktarget: "",
+    },
+  ]);
+
+  const bannerSlides2 = ref([
+    {
+      image: "/img/models/mustang/design-01-1.png",
+      alt: "AN INSTANT CLASSIC STANDOUT STYLE",
+      title: "AN INSTANT CLASSIC STANDOUT STYLE",
+      smalldesc:
+        "2025 Ford Mustang의 특징은 매끈한 보디라인과 시선을 사로잡는 휠과 브레이크 조합에서도 발견하실 수 있습니다.<br/>이는 머스탱 고유의 현대적인 디자인을 유지하면서도 머스탱 헤리티지도 스며들어 있어, 잊을 수 없는 첫인상을 제공해 드립니다.",
+    },
+    {
+      image: "/img/models/mustang/design-01-2.png",
+      alt: "EXPERIENCE A RUSH OF COLOR",
+      title: "EXPERIENCE A RUSH OF COLOR",
+      smalldesc:
+        "2025 Ford Mustang에서는 다양한 외장색을 만나보실 수 있습니다. <br/>Race Red, Oxford White와 같은 클래식한 색상부터 생동감이 넘치는 Vapor Blue 및 Yellow Splash 컬러까지 다양하게 선택 가능합니다.",
+    },
+    {
+      image: "/img/models/mustang/design-01-3.png",
+      alt: "OPEN-AIR FREEDOM",
+      title: "OPEN-AIR FREEDOM",
+      smalldesc:
+        "만약, Mustang Convertible과 함께 하고 계시다면 Top을 제거하여 열어보십시오.<br/>코끝으로 느껴지는 신선한 공기가 당신을 새롭게 만들어드릴 것입니다.",
+    },
+  ]);
+
+  const bannerSlides3 = ref([
+    {
+      image: "/img/models/mustang/design-02-1.png",
+      alt: "GAME CHANGING COCKPIT",
+      title: "GAME CHANGING COCKPIT",
+      smalldesc:
+        "앉는 순간 나를 감싸주는 편안한 시트와 스포티한 디자인의 Flat bottom 스티어링 휠. 이뿐만 아니라 13.2인치 듀얼 스크린 대형 센터 스크린까지.<br/>쿠페 또는 컨버터블, 어떤 종류의 Mustang에 오르시더라도 Mustang과 함께하는 모든 순간에서 진정한 몰입감을 경험적으로,<br/>그리고 본능적으로 느끼실 수 있습니다.",
+    },
+    {
+      image: "/img/models/mustang/design-02-2.png",
+      alt: "IMMERSIVE DIGITAL COCKPIT",
+      title: "IMMERSIVE DIGITAL COCKPIT",
+      smalldesc:
+        "매일 반복되는 일상적인 드라이빙에는 가끔 변화가 필요합니다. 사용자가 설정이 가능한 LCD 디지털 클러스터 및 센터 터치스크린을 통해 원하시는 <br/>디자인으로 디스플레이를 변경해 보십시오.",
+    },
+    {
+      image: "/img/models/mustang/design-02-3.png",
+      alt: "EYE-CATCHING INTERIOR DETAILS",
+      title: "EYE-CATCHING INTERIOR DETAILS",
+      smalldesc:
+        "2025 Ford Mustang 인테리어는 디테일도 놓치지 않았습니다. 실내를 더욱 돋보이게 만드는 정밀한 스티칭과 악센트, 그리고 Mustang만의 유니크한 배지까지. <br/>이와 같은 디테일들이 잘 조화되어 오랫동안 잊히지 않는 Mustang 인테리어가 완성되었습니다.",
+    },
+    {
+      image: "/img/models/mustang/design-02-4.png",
+      alt: "HIGH-PERFORMANCE HANDLING",
+      title: "HIGH-PERFORMANCE HANDLING",
+      smalldesc:
+        "2025 Ford Mustang에 적용된 Flat bottom 스티어링 휠은 모두 4조각의 가죽으로 마감되어 있으며 각 부분에 적절하게 조화된 크롬 베젤과도 잘 조화를 이룹니다. <br/>Mustang의 스티어링 휠은 도심, 트랙 어느 곳에서도 경주용 차량과 같은 컨트롤이 가능할 수 있도록 도와드립니다.",
+    },
+  ]);
+
+  const gallery1 = ref([
+    { image: "/img/models/mustang/gallery_1_1.avif" },
+    { image: "/img/models/mustang/gallery_1_2.avif" },
+    { image: "/img/models/mustang/gallery_1_3.avif" },
+    { image: "/img/models/mustang/gallery_1_4.avif" },
+    { image: "/img/models/mustang/gallery_1_5.avif" },
+    { image: "/img/models/mustang/gallery_1_6.webp" },
+    { image: "/img/models/mustang/gallery_1_7.webp" },
+    { image: "/img/models/mustang/gallery_1_8.avif" },
+    { image: "/img/models/mustang/gallery_1_9.webp" },
+    { image: "/img/models/mustang/gallery_1_10.avif" },
+    { image: "/img/models/mustang/gallery_1_11.avif" },
+    { image: "/img/models/mustang/gallery_1_12.avif" },
+    { image: "/img/models/mustang/gallery_1_13.avif" },
+    { image: "/img/models/mustang/gallery_1_14.jpg" },
+    { image: "/img/models/mustang/gallery_1_15.webp" },
+    { image: "/img/models/mustang/gallery_1_16.jpg" },
+    { image: "/img/models/mustang/gallery_1_17.jpg" },
+    { image: "/img/models/mustang/gallery_1_18.avif" },
+    { image: "/img/models/mustang/gallery_1_19.webp" },
+    { image: "/img/models/mustang/gallery_1_20.avif" },
+    { image: "/img/models/mustang/gallery_1_21.avif" },
+    { image: "/img/models/mustang/gallery_1_22.avif" },
+    { image: "/img/models/mustang/gallery_1_23.avif" },
+    { image: "/img/models/mustang/gallery_1_24.jpg" },
+    { image: "/img/models/mustang/gallery_1_25.avif" },
+    { image: "/img/models/mustang/gallery_1_26.avif" },
+    { image: "/img/models/mustang/gallery_1_27.avif" },
+    { image: "/img/models/mustang/gallery_1_28.jpg" },
+    { image: "/img/models/mustang/gallery_1_29.webp" },
+    { image: "/img/models/mustang/gallery_1_30.avif" },
+    { image: "/img/models/mustang/gallery_1_31.avif" },
+    { image: "/img/models/mustang/gallery_1_32.avif" },
+    { image: "/img/models/mustang/gallery_1_33.avif" },
+    { image: "/img/models/mustang/gallery_1_34.avif" },
+    { image: "/img/models/mustang/gallery_1_35.avif" },
+    { image: "/img/models/mustang/gallery_1_36.jpg" },
+    { image: "/img/models/mustang/gallery_1_37.avif" },
+    { image: "/img/models/mustang/gallery_1_38.avif" },
+     { image: "/img/models/mustang/gallery_2_1.avif" },
+    { image: "/img/models/mustang/gallery_2_2.avif" },
+    { image: "/img/models/mustang/gallery_2_3.avif" },
+    { image: "/img/models/mustang/gallery_2_4.avif" },
+    { image: "/img/models/mustang/gallery_2_5.webp" },
+    { image: "/img/models/mustang/gallery_2_6.avif" },
+    { image: "/img/models/mustang/gallery_2_7.avif" },
+    { image: "/img/models/mustang/gallery_2_8.avif" },
+    { image: "/img/models/mustang/gallery_2_9.avif" },
+    { image: "/img/models/mustang/gallery_2_10.avif" },
+    { image: "/img/models/mustang/gallery_2_11.avif" },
+    { image: "/img/models/mustang/gallery_2_12.webp" },
+    { image: "/img/models/mustang/gallery_2_13.avif" },
+    { image: "/img/models/mustang/gallery_2_14.avif" },
+    { image: "/img/models/mustang/gallery_2_15.avif" },
+    { image: "/img/models/mustang/gallery_2_16.avif" },
+    { image: "/img/models/mustang/gallery_2_17.avif" },
+    { image: "/img/models/mustang/gallery_2_18.avif" },
+  ]);
+  const gallery2 = ref([
+    { image: "/img/models/mustang/gallery_2_1.avif" },
+    { image: "/img/models/mustang/gallery_2_2.avif" },
+    { image: "/img/models/mustang/gallery_2_3.avif" },
+    { image: "/img/models/mustang/gallery_2_4.avif" },
+    { image: "/img/models/mustang/gallery_2_5.webp" },
+    { image: "/img/models/mustang/gallery_2_6.avif" },
+    { image: "/img/models/mustang/gallery_2_7.avif" },
+    { image: "/img/models/mustang/gallery_2_8.avif" },
+    { image: "/img/models/mustang/gallery_2_9.avif" },
+    { image: "/img/models/mustang/gallery_2_10.avif" },
+    { image: "/img/models/mustang/gallery_2_11.avif" },
+    { image: "/img/models/mustang/gallery_2_12.webp" },
+    { image: "/img/models/mustang/gallery_2_13.avif" },
+    { image: "/img/models/mustang/gallery_2_14.avif" },
+    { image: "/img/models/mustang/gallery_2_15.avif" },
+    { image: "/img/models/mustang/gallery_2_16.avif" },
+    { image: "/img/models/mustang/gallery_2_17.avif" },
+    { image: "/img/models/mustang/gallery_2_18.avif" },
+  ]);
+</script>

+ 345 - 0
app/pages/ford/vehicle/ranger.vue

@@ -0,0 +1,345 @@
+<template>
+  <main>
+    <SwiperBanner
+      class="sticky--banner"
+      :slides="bannerSlides"
+      :autoplay="false"
+      :loop="false"
+      type=""
+    />
+    <div class="outer--wrapper">
+      <div class="trim--spec--wrap">
+        <ul>
+          <li>
+            <span>소비자 가격</span>
+            <div>
+              63,500,000<em>원 부터<i>*</i></em>
+            </div>
+          </li>
+          <li>
+            <span>엔진</span>
+            <div>Bi-Turbo Diesel 2.0L</div>
+          </li>
+          <li>
+            <span>승차 정원(명)</span>
+            <div>5</div>
+          </li>
+        </ul>
+      </div>
+
+      <div class="title--visual">
+        <h2>OUR MOST VERSATILE RANGER EVER</h2>
+        <div class="sub--title">
+          지금까지 출시된 FORD RANGER 중 가장 스마트하고 다재다능한 RANGER를 만나보십시오.<br />
+          오랫동안 전 세계적인 명성을 유지해 온 FORD RANGER는 단순히 일차원적인 픽업
+          트럭이 아니라 고객들이 원하는 바를 모두 제공해드리기 위해 설계된 다목적
+          차량입니다.<br />
+          이제, 어떤 상황에서도 퍼포먼스를 발휘하는 파워풀한 엔진과 운전자의 안전을 위한
+          첨단 기능이 탑재된 FORD RANGER와 함께 하십시오.
+        </div>
+      </div>
+
+      <div class="models--visual--01 type--3 mt--80">
+        <div class="thumb--wrap">
+          <img src="/img/models/ranger/visual-01.jpg" />
+        </div>
+      </div>
+
+      <div class="car--price--small--pic--wrap mt--120">
+        <div>
+          <h2>RANGER Models</h2>
+          <div class="car--list--wrap">
+            <ul>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/ranger/ranger01.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">Wildtrak Standard</h2>
+                  <div class="price--wrap">
+                    소비자 가격 : <em>63,500,000 원 부터<i>*</i></em>
+                  </div>
+                </div>
+              </li>
+              <li>
+                <div class="thumb">
+                  <img src="/img/models/ranger/ranger02.png" alt="" />
+                </div>
+                <div class="desc--wrap">
+                  <h2 class="trim--name">Raptor Standard</h2>
+                  <div class="price--wrap">
+                    소비자 가격 : <em>79,900,000 원 부터<i>*</i></em> 
+                  </div>
+                </div>
+              </li>
+            </ul>
+          </div>
+        </div>
+      </div>
+
+      <div class="title--visual mt--150">
+        <h2>TOUGHER. SMARTER. MORE CAPABLE.</h2>
+        <div class="sub--title">
+          지금까지 출시된 포드 레인저 중 가장 스마트하고 다재다능한 레인저를
+          만나보십시오.<br />
+          오랫동안 세계적인 명성을 유지해 온 레인저는 단순한 픽업 트럭이 아니라 고객들이
+          원하는 바를 모두 제공하기 위해 설계된 다재다능한 차량입니다. <br />
+          이제, 어떤 상황에서도 흔들림 없는 퍼포먼스를 발휘하는 파워풀한 엔진과 운전자의
+          안전을 위한 첨단 기능이 탑재된 포드 레인저를 경험해 보십시오.
+        </div>
+      </div>
+      <div class="models--visual--grid mt--80 type--2">
+        <ul>
+          <li>
+            <div class="thumb--wrap">
+              <img src="/img/models/ranger/grid-01.jpg" />
+            </div>
+            <div class="desc--wrap">
+              <h2>FORD RANGER PICK-UP</h2>
+              <div class="captions">
+                포드 레인저 와일드트랙의 넉넉한 카고 스페이스는<br />
+                업무용, 레저용으로 사용하기에 충분합니다.
+              </div>
+            </div>
+          </li>
+          <li>
+            <div class="thumb--wrap">
+              <img src="/img/models/ranger/grid-02.jpg" />
+            </div>
+            <div class="desc--wrap">
+              <h2>SELECTABLE DRIVE MODE</h2>
+              <div class="captions">
+                Ford Ranger Raptor는 간단한 작동만으로<br />
+                지형 및 도로 상황에 맞는 퍼포먼스를 발휘할 수 있도록<br />
+                설계된 Selectable Drive Mode가 포함되어<br />
+                있습니다. 여러가지 모드 중 주행 상황에 가장 적합한<br />
+                모드를 선택하십시오.
+              </div>
+            </div>
+          </li>
+          <li>
+            <div class="thumb--wrap">
+              <img src="/img/models/ranger/grid-03.jpg" />
+            </div>
+            <div class="desc--wrap">
+              <h2>DRIVER ASSISTANCE</h2>
+              <div class="captions">
+                넥스트 제너레이션 레인저에는 일반도로는 물론,<br />
+                오프로드까지 다양한 주행환경에 최적화된<br />
+                드라이브 모드를 제공합니다.
+              </div>
+            </div>
+          </li>
+        </ul>
+      </div>
+
+      <!-- <SwiperBanner3
+        class="mt--100"
+        :slides="bannerSlides2"
+        :autoplay="false"
+        :loop="false"
+        mtitle="design"
+        stitle="검은색 FORD 그릴이 한눈에 들어오는 Ford Ranger Raptor는 강렬한 첫인상을 선사합니다.<br/>
+  고강도 프레임을 바탕으로 제작된 Ford Ranger Raptor는 2.0L Bi-turbo 디젤 엔진과 10단 자동변속기가<br/>
+  차체에 즉각적으로 파워를 전달하여 거친 오프로드 환경에서도 운전자의 의도대로 주행할 수 있도록 도와드립니다."
+        type=""
+      />
+
+      <SwiperBanner3
+        :slides="bannerSlides3"
+        :autoplay="false"
+        :loop="false"
+        type=""
+        mtitle="technology"
+        stitle="포드 레인저의 2.0L 디젤 엔진은 강력한 파워를 바라는 운전자의 기대를 충족시켜드립니다.<br/>
+  흙먼지가 날리는 비포장도로에서도, 무거운 짐을 싣고 달리는 오르막길에서도 레인저는 운전자에게 주행 안정감을 제공합니다."
+      /> -->
+
+      <div class="title--visual mt--150">
+        <h2>gallery</h2>
+        <!-- <div class="sub--title">
+          여전히 많은 이들이 Raptor 라는 이름만 들어도 가슴이 설레이실 것입니다. 바로
+          퍼포먼스의 모든 것, 포드 레인저 랩터 입니다.
+        </div> -->
+      </div>
+      <div class="gallery--wrap mt--80">
+        <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery1"
+            :autoplay="false"
+            :loop="false"
+            type=""
+          />
+        </div>
+        <!-- <div class="thumb--wrap">
+          <SwiperBanner2
+            :slides="gallery2"
+            :autoplay="false"
+            :loop="false"
+            type="interior"
+          />
+        </div> -->
+      </div>
+      <div class="nav--dis--wrap model mt--150" :class="{ active: isDisOpen }">
+        <div class="dis--btn" @click="isDisOpen = !isDisOpen">disclosures <i class="ico"></i></div>
+        <div class="dis--cont">
+          <div class="container">
+            <p>
+            *본 웹사이트에 공지된 가격정보는 부가세(VAT)를 포함하고 있습니다. 본 가격은 개별소비세 인하에 관한 정부시책을 반영한 것이며 가격정보는 예고없이 변동될 수 있습니다. 
+             가격정보는 고객님의 차량 구입에 도움을 드리고자 함이며 법적 구속력은 없습니다. <br/><br/>
+             사이트에서 제공하는 차량에 대한 모든 이미지 또는 사양은 실제 판매 모델과 다를 수 있습니다. 정확한 정보는 가까운 공식 딜러를 통해 확인하시기 바랍니다.
+            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </main>
+</template>
+
+<script setup>
+  // disclosures 토글 (기본 열림)
+  const isDisOpen = ref(true);
+
+  const bannerSlides = ref([
+    {
+      image: "/img/models/ranger/ranger_banner01.jpg",
+      moImage: "/img/models/ranger/ranger_banner01_mo.jpg",
+      alt: "RANGER RAPTOR",
+      title: "RANGER<br/>RAPTOR",
+      subtitle:
+        "흔들림 없는 퍼포먼스를 발휘하는 파워풀한 엔진과 운전자의 안전을 위한 첨단 기능",
+      smalldesc: "",
+      morelink: "/ford/network",
+      morelinktitle: "시승 신청",
+    },
+  ]);
+
+  const bannerSlides2 = ref([
+    {
+      image: "/img/models/ranger/design-01-1.png",
+      alt: "Bold New Design",
+      title: "Bold New Design",
+      smalldesc:
+        "보는 이의 눈길을 사로잡는 차별화된 디자인의 Wildtrak은 기대 이상의 성능을 발휘합니다.",
+    },
+    {
+      image: "/img/models/ranger/design-01-2.png",
+      alt: "Your Space. Roomy and Comfortable.",
+      title: "Your Space. Roomy and Comfortable.",
+      smalldesc:
+        "포드 레인저 와일드트랙의 실내는 일반 주행뿐만 아니라, 장거리 및 아웃도어 주행 상황에서도 운전자의 편의를 생각하는 각종 편의 장치들이 마련되어 있습니다.<br/>레인저의 파워시트를 이용하여 탑승자의 체형에 맞게 시트 포지션을 조절해 보십시오. 또한 와일드트랙의 아이덴티티를 보여주는 독특한 컬러가 포함된 장식들은<br/>실내 인테리어 이미지와 잘 조화를 이루어 탑승자의 눈을 즐겁게 합니다.",
+    },
+    {
+      image: "/img/models/ranger/design-01-3.png",
+      alt: "Selectable Drive Mode",
+      title: "Selectable Drive Mode",
+      smalldesc:
+        "Ford Ranger Raptor는 간단한 작동만으로 지형 및 도로 상황에 맞는 퍼포먼스를 발휘할 수 있도록 설계된 Selectable Drive Mode가 포함되어 있습니다.<br/>여러가지 모드 중 주행 상황에 가장 적합한 모드를 선택하십시오.",
+    },
+  ]);
+
+  const bannerSlides3 = ref([
+    {
+      image: "/img/models/ranger/design-02-1.png",
+      alt: "10-Speed Transmissions",
+      title: "10-Speed Transmissions",
+      smalldesc:
+        "Ford Ranger에 장착된 10단 자동 변속기는 일반 주행은 물론 오프로드 상황에서도 최적의 타이밍에 기어를 변경하여 차량의 성능을 최대로 발휘할 수 있도록 도와줍니다.",
+    },
+    {
+      image: "/img/models/ranger/design-02-2.png",
+      alt: "Fun on Demand",
+      title: "Fun on Demand",
+      smalldesc:
+        "2.0L Bi-TurBo DieseL engine 포드 레인저의 2.0L 디젤 엔진은 강력한 파워를 바라는 운전자의 기대를 충족시켜드립니다.<br/>흙먼지가 날리는 비포장도로에서도, 무거운 짐을 싣고 달리는 오르막길에서도 레인저는 운전자에게 주행 안정감을 제공합니다.",
+    },
+    {
+      image: "/img/models/ranger/design-02-3.png",
+      alt: "Driver Assistance",
+      title: "Driver Assistance",
+      smalldesc:
+        "넥스트 제너레이션 레인저에는 일반도로는 물론, 오프로드까지 다양한 주행환경에 최적화된 드라이브 모드를 제공합니다.<br/>각 모드는 주행 환경에 최적화된 세팅을 통해 기대 이상의 퍼포먼스를 발휘합니다.<br/>또한 레인저에 탑재된 다양한 편의 기능을 통해 어떤 환경에서든 안전하고 편안한 드라이빙을 경험할 수 있습니다.<br/>* 와일드트랙과 랩터에서 제공되는 드라이브 모드는 상이할 수 있으며 각종 편의 기능은 운행에 보조적인 수단으로 안전운행에 대한 주의 책임은 운전자 본인에게 있습니다.",
+    },
+    {
+      image: "/img/models/ranger/design-02-4.png",
+      alt: "Adaptive Cruise Control with Stop & Go, and Lane Centering",
+      title: "Adaptive Cruise Control with Stop & Go, and Lane Centering",
+      smalldesc:
+        "장시간의 고속도로 주행 시 어댑티브 크루즈 컨트롤 기능을 사용해 운전의 피로감을 줄일 수 있습니다. 레인저의 전방에 다른 차량 진입을 감지해 차량 속도를 줄이거나 세팅된 속도로<br/>자동 되돌리며, 레인 센터링 기능으로 차량이 차선 중앙에서 안정적으로 주행할 수 있도록 지원합니다.",
+    },
+    {
+      image: "/img/models/ranger/design-02-5.png",
+      alt: "360-Degree Camera",
+      title: "360-Degree Camera",
+      smalldesc:
+        "좁은 주차장이나 사람과 차들로 붐비는 공간에서 차량 운행 시 360도 카메라를 활용해 주변의 상황을 쉽게 식별해 안전한 운행과 주차를 할 수 있습니다.",
+    },
+  ]);
+
+  const gallery1 = ref([
+    { image: "/img/models/ranger/gallery_1_1.avif" },
+    { image: "/img/models/ranger/gallery_1_2.jpg" },
+    { image: "/img/models/ranger/gallery_1_3.avif" },
+    { image: "/img/models/ranger/gallery_1_4.avif" },
+    { image: "/img/models/ranger/gallery_1_5.avif" },
+    { image: "/img/models/ranger/gallery_1_6.webp" },
+    { image: "/img/models/ranger/gallery_1_7.avif" },
+    { image: "/img/models/ranger/gallery_1_8.jpg" },
+    { image: "/img/models/ranger/gallery_1_9.avif" },
+    { image: "/img/models/ranger/gallery_1_10.jpg" },
+    { image: "/img/models/ranger/gallery_1_11.avif" },
+    { image: "/img/models/ranger/gallery_1_12.avif" },
+    { image: "/img/models/ranger/gallery_1_13.avif" },
+    { image: "/img/models/ranger/gallery_1_14.avif" },
+    { image: "/img/models/ranger/gallery_1_15.avif" },
+    { image: "/img/models/ranger/gallery_1_16.avif" },
+    { image: "/img/models/ranger/gallery_1_17.jpg" },
+    { image: "/img/models/ranger/gallery_1_18.jpg" },
+    { image: "/img/models/ranger/gallery_1_19.avif" },
+    { image: "/img/models/ranger/gallery_1_20.webp" },
+    { image: "/img/models/ranger/gallery_1_21.avif" },
+    { image: "/img/models/ranger/gallery_1_22.avif" },
+    { image: "/img/models/ranger/gallery_1_23.avif" },
+    { image: "/img/models/ranger/gallery_1_24.avif" },
+    { image: "/img/models/ranger/gallery_1_25.avif" },
+    { image: "/img/models/ranger/gallery_1_26.webp" },
+    { image: "/img/models/ranger/gallery_1_27.avif" },
+    { image: "/img/models/ranger/gallery_1_28.avif" },
+    { image: "/img/models/ranger/gallery_1_29.avif" },
+    { image: "/img/models/ranger/gallery_1_30.avif" },
+    { image: "/img/models/ranger/gallery_1_31.avif" },
+    { image: "/img/models/ranger/gallery_2_1.jpg" },
+    { image: "/img/models/ranger/gallery_2_2.avif" },
+    { image: "/img/models/ranger/gallery_2_3.avif" },
+    { image: "/img/models/ranger/gallery_2_4.avif" },
+    { image: "/img/models/ranger/gallery_2_5.avif" },
+    { image: "/img/models/ranger/gallery_2_6.avif" },
+    { image: "/img/models/ranger/gallery_2_7.webp" },
+    { image: "/img/models/ranger/gallery_2_8.avif" },
+    { image: "/img/models/ranger/gallery_2_9.webp" },
+    { image: "/img/models/ranger/gallery_2_10.webp" },
+    { image: "/img/models/ranger/gallery_2_11.avif" },
+    { image: "/img/models/ranger/gallery_2_12.webp" },
+    { image: "/img/models/ranger/gallery_2_13.webp" },
+    { image: "/img/models/ranger/gallery_2_14.avif" },
+    { image: "/img/models/ranger/gallery_2_15.webp" },
+  ]);
+
+  const gallery2 = ref([
+    { image: "/img/models/ranger/gallery_2_1.jpg" },
+    { image: "/img/models/ranger/gallery_2_2.avif" },
+    { image: "/img/models/ranger/gallery_2_3.avif" },
+    { image: "/img/models/ranger/gallery_2_4.avif" },
+    { image: "/img/models/ranger/gallery_2_5.avif" },
+    { image: "/img/models/ranger/gallery_2_6.avif" },
+    { image: "/img/models/ranger/gallery_2_7.webp" },
+    { image: "/img/models/ranger/gallery_2_8.avif" },
+    { image: "/img/models/ranger/gallery_2_9.webp" },
+    { image: "/img/models/ranger/gallery_2_10.webp" },
+    { image: "/img/models/ranger/gallery_2_11.avif" },
+    { image: "/img/models/ranger/gallery_2_12.webp" },
+    { image: "/img/models/ranger/gallery_2_13.webp" },
+    { image: "/img/models/ranger/gallery_2_14.avif" },
+    { image: "/img/models/ranger/gallery_2_15.webp" },
+  ]);
+</script>

+ 20 - 0
app/pages/index.vue

@@ -0,0 +1,20 @@
+<template>
+  <main>
+    <div class="gate--wrap">
+      <div class="gate--container">
+        <NuxtLink to="/ford" class="gate ford">
+          <i class="logo gate--1">포드</i>
+          <h1>정통 아메리칸 라인업</h1>
+          <button>포드 바로가기<i class="ico"></i></button>
+        </NuxtLink>
+        <NuxtLink to="/lincoln" class="gate lincoln">
+          <i class="logo">링컨</i>
+          <h1>평온함, 여유 그리고 링컨</h1>
+          <button>링컨 바로가기<i class="ico"></i></button>
+        </NuxtLink>
+      </div>
+    </div>
+  </main>
+</template>
+
+<script setup></script>

+ 471 - 0
app/pages/site-manager/admins/index.vue

@@ -0,0 +1,471 @@
+<template>
+  <div class="admin--admins">
+    <div class="admin--page-header">
+      <h3>관리자 관리</h3>
+      <button @click="openCreateModal" class="admin--btn-small admin--btn-small-primary">
+        + 관리자 추가
+      </button>
+    </div>
+
+    <!-- 검색 영역 -->
+    <div class="admin--search-box">
+      <div class="admin--search-form">
+        <select v-model="filterRole" @change="loadAdmins" class="admin--form-select admin--search-select">
+          <option value="">전체 역할</option>
+          <option value="super_admin">슈퍼 관리자</option>
+          <option value="admin">일반 관리자</option>
+        </select>
+        <select v-model="filterStatus" @change="loadAdmins" class="admin--form-select admin--search-select">
+          <option value="">전체 상태</option>
+          <option value="active">활성</option>
+          <option value="inactive">비활성</option>
+        </select>
+        <input
+          v-model="searchQuery"
+          type="text"
+          placeholder="아이디, 이름으로 검색"
+          @keyup.enter="loadAdmins"
+          class="admin--form-input admin--search-input"
+        />
+        <button @click="loadAdmins" class="admin--btn-small admin--btn-small-primary">
+          검색
+        </button>
+        <button @click="resetSearch" class="admin--btn-small admin--btn-small-secondary">
+          초기화
+        </button>
+      </div>
+    </div>
+
+    <!-- 관리자 목록 테이블 -->
+    <div class="admin--table-container">
+      <table class="admin--table">
+        <thead>
+          <tr>
+            <th>ID</th>
+            <th>아이디</th>
+            <th>이름</th>
+            <th>이메일</th>
+            <th>부서</th>
+            <th>역할</th>
+            <th>상태</th>
+            <th>생성일</th>
+            <th>관리</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="loading">
+            <td colspan="9" class="admin--loading">로딩 중...</td>
+          </tr>
+          <tr v-else-if="admins.length === 0">
+            <td colspan="9" class="admin--no-data">관리자가 없습니다.</td>
+          </tr>
+          <tr v-else v-for="admin in admins" :key="admin.id">
+            <td>{{ admin.id }}</td>
+            <td>{{ admin.username }}</td>
+            <td>{{ admin.name }}</td>
+            <td>{{ admin.email }}</td>
+            <td>{{ admin.department || '-' }}</td>
+            <td>
+              <span :class="['admin--badge', getRoleBadgeClass(admin.role)]">
+                {{ getRoleLabel(admin.role) }}
+              </span>
+            </td>
+            <td>
+              <div style="display: flex; gap: 4px; align-items: center;">
+                <span :class="['admin--badge', getStatusBadgeClass(admin.status)]">
+                  {{ getStatusLabel(admin.status) }}
+                </span>
+                <span v-if="admin.login_attempts >= 5" class="admin--badge admin--badge-danger" title="로그인 5회 실패로 계정 잠김">
+                  🔒 잠김
+                </span>
+              </div>
+            </td>
+            <td>{{ formatDate(admin.created_at) }}</td>
+            <td>
+              <div class="admin--table-actions admin--table-actions-col">
+                <button @click="openEditModal(admin)" class="admin--btn-small admin--btn-small-primary">
+                  수정
+                </button>
+                <button @click="openPasswordModal(admin)" class="admin--btn-small admin--btn-small-secondary">
+                  비밀번호
+                </button>
+                <button
+                  v-if="admin.login_attempts >= 5"
+                  @click="confirmUnlockAccount(admin)"
+                  class="admin--btn-small admin--btn-small-warning"
+                  title="계정 잠금 해제"
+                >
+                  잠금해제
+                </button>
+                <button
+                  @click="confirmDeleteAdmin(admin)"
+                  class="admin--btn-small admin--btn-small-danger"
+                  :disabled="admin.id === currentAdminId"
+                >
+                  삭제
+                </button>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <!-- 페이지네이션 -->
+    <div class="admin--pagination" v-if="totalPages > 1">
+      <button
+        @click="changePage(currentPage - 1)"
+        :disabled="currentPage === 1"
+        class="admin--btn-page"
+      >
+        이전
+      </button>
+      <span class="admin--page-info">
+        {{ currentPage }} / {{ totalPages }}
+      </span>
+      <button
+        @click="changePage(currentPage + 1)"
+        :disabled="currentPage === totalPages"
+        class="admin--btn-page"
+      >
+        다음
+      </button>
+    </div>
+
+    <!-- 관리자 추가/수정 모달 -->
+    <AdminModal
+      v-if="showModal"
+      :admin="selectedAdmin"
+      @close="closeModal"
+      @saved="handleSaved"
+    />
+
+    <!-- 비밀번호 변경 모달 -->
+    <PasswordModal
+      v-if="showPasswordModal"
+      :admin="selectedAdmin"
+      @close="closePasswordModal"
+      @saved="handlePasswordChanged"
+    />
+
+    <!-- 알림 모달 -->
+    <AdminAlertModal
+      v-if="alertModal.show"
+      :title="alertModal.title"
+      :message="alertModal.message"
+      :type="alertModal.type"
+      @confirm="handleAlertConfirm"
+      @cancel="handleAlertCancel"
+      @close="closeAlertModal"
+    />
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, computed } from 'vue'
+import AdminAlertModal from '~/components/admin/AdminAlertModal.vue'
+import AdminModal from '~/components/admin/AdminModal.vue'
+import PasswordModal from '~/components/admin/PasswordModal.vue'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const { get, del, post } = useApi()
+
+// 현재 로그인한 관리자 ID
+const currentAdminId = computed(() => {
+  if (typeof window === 'undefined') return null
+  const user = localStorage.getItem('admin_user')
+  if (!user) return null
+  try {
+    return JSON.parse(user).id
+  } catch {
+    return null
+  }
+})
+
+// 데이터
+const admins = ref([])
+const loading = ref(false)
+const searchQuery = ref('')
+const filterRole = ref('')
+const filterStatus = ref('')
+
+// 페이지네이션
+const currentPage = ref(1)
+const totalPages = ref(1)
+const perPage = ref(10)
+
+// 모달
+const showModal = ref(false)
+const showPasswordModal = ref(false)
+const selectedAdmin = ref(null)
+
+// 알림 모달
+const alertModal = ref({
+  show: false,
+  title: '알림',
+  message: '',
+  type: 'alert',
+  onConfirm: null
+})
+
+// 알림 모달 표시
+const showAlert = (message, title = '알림') => {
+  alertModal.value = {
+    show: true,
+    title,
+    message,
+    type: 'alert',
+    onConfirm: null
+  }
+}
+
+// 확인 모달 표시
+const showConfirm = (message, onConfirm, title = '확인') => {
+  alertModal.value = {
+    show: true,
+    title,
+    message,
+    type: 'confirm',
+    onConfirm
+  }
+}
+
+// 알림 모달 닫기
+const closeAlertModal = () => {
+  alertModal.value.show = false
+}
+
+// 알림 모달 확인
+const handleAlertConfirm = () => {
+  if (alertModal.value.onConfirm) {
+    alertModal.value.onConfirm()
+  }
+  closeAlertModal()
+}
+
+// 알림 모달 취소
+const handleAlertCancel = () => {
+  closeAlertModal()
+}
+
+// 관리자 목록 로드
+const loadAdmins = async () => {
+  loading.value = true
+  try {
+    const params = {
+      page: currentPage.value,
+      per_page: perPage.value
+    }
+
+    if (searchQuery.value) {
+      params.search = searchQuery.value
+    }
+    if (filterRole.value) {
+      params.role = filterRole.value
+    }
+    if (filterStatus.value) {
+      params.status = filterStatus.value
+    }
+
+    console.log('[Admins] 검색 파라미터:', params)
+
+    const { data, error } = await get('/admin', { params })
+
+    if (error) {
+      console.error('[Admins] 목록 로드 실패:', error)
+      return
+    }
+
+    // API 응답: { success: true, data: { items, total_pages }, message }
+    if (data?.success && data?.data) {
+      admins.value = data.data.items || []
+      totalPages.value = data.data.total_pages || 1
+      console.log('[Admins] 목록 로드 성공:', data.data)
+      console.log('[Admins] 첫번째 관리자 login_attempts:', admins.value[0]?.login_attempts)
+      console.log('[Admins] 잠긴 계정 수:', admins.value.filter(a => a.login_attempts >= 5).length)
+    }
+  } finally {
+    loading.value = false
+  }
+}
+
+// 페이지 변경
+const changePage = (page) => {
+  if (page < 1 || page > totalPages.value) return
+  currentPage.value = page
+  loadAdmins()
+}
+
+// 검색 초기화
+const resetSearch = () => {
+  searchQuery.value = ''
+  filterRole.value = ''
+  filterStatus.value = ''
+  currentPage.value = 1
+  loadAdmins()
+}
+
+// 모달 열기/닫기
+const openCreateModal = () => {
+  selectedAdmin.value = null
+  showModal.value = true
+}
+
+const openEditModal = (admin) => {
+  selectedAdmin.value = { ...admin }
+  showModal.value = true
+}
+
+const closeModal = () => {
+  showModal.value = false
+  selectedAdmin.value = null
+}
+
+const handleSaved = (message) => {
+  closeModal()
+  loadAdmins()
+  if (message) {
+    showAlert(message, '성공')
+  }
+}
+
+// 비밀번호 모달
+const openPasswordModal = (admin) => {
+  selectedAdmin.value = { ...admin }
+  showPasswordModal.value = true
+}
+
+const closePasswordModal = () => {
+  showPasswordModal.value = false
+  selectedAdmin.value = null
+}
+
+const handlePasswordChanged = (message) => {
+  closePasswordModal()
+  if (message) {
+    showAlert(message, '성공')
+  }
+}
+
+// 관리자 삭제 확인
+const confirmDeleteAdmin = (admin) => {
+  if (admin.id === currentAdminId.value) {
+    showAlert('본인 계정은 삭제할 수 없습니다.', '경고')
+    return
+  }
+
+  showConfirm(
+    `${admin.name} (${admin.username}) 관리자를 삭제하시겠습니까?`,
+    () => deleteAdmin(admin),
+    '관리자 삭제'
+  )
+}
+
+// 관리자 삭제
+const deleteAdmin = async (admin) => {
+  const { data, error } = await del(`/admin/${admin.id}`)
+
+  if (error) {
+    showAlert('관리자 삭제에 실패했습니다.', '오류')
+    console.error('[Admins] 삭제 실패:', error)
+    return
+  }
+
+  if (data?.success) {
+    showAlert('관리자가 삭제되었습니다.', '성공')
+    loadAdmins()
+  }
+}
+
+// 계정 잠금 해제 확인
+const confirmUnlockAccount = (admin) => {
+  showConfirm(
+    `${admin.name} (${admin.username}) 계정의 잠금을 해제하시겠습니까?`,
+    () => unlockAccount(admin),
+    '계정 잠금 해제'
+  )
+}
+
+// 계정 잠금 해제
+const unlockAccount = async (admin) => {
+  const { data, error } = await post(`/admin/${admin.id}/unlock`)
+
+  if (error) {
+    showAlert('계정 잠금 해제에 실패했습니다.', '오류')
+    console.error('[Admins] 잠금 해제 실패:', error)
+    return
+  }
+
+  if (data?.success) {
+    showAlert('계정 잠금이 해제되었습니다.', '성공')
+    loadAdmins()
+  }
+}
+
+// 유틸리티 함수
+const getRoleLabel = (role) => {
+  const labels = {
+    super_admin: '슈퍼 관리자',
+    admin: '일반 관리자'
+  }
+  return labels[role] || role
+}
+
+const getRoleBadgeClass = (role) => {
+  return role === 'super_admin' ? 'admin--badge-danger' : 'admin--badge-primary'
+}
+
+const getStatusLabel = (status) => {
+  const labels = {
+    active: '활성',
+    inactive: '비활성'
+  }
+  return labels[status] || status
+}
+
+const getStatusBadgeClass = (status) => {
+  return status === 'active' ? 'admin--badge-success' : 'admin--badge-secondary'
+}
+
+const formatDate = (dateString) => {
+  if (!dateString) return '-'
+  const date = new Date(dateString)
+  return date.toLocaleString('ko-KR', {
+    year: 'numeric',
+    month: '2-digit',
+    day: '2-digit',
+    hour: '2-digit',
+    minute: '2-digit'
+  })
+}
+
+onMounted(() => {
+  loadAdmins()
+})
+</script>
+
+<style scoped>
+.admin--search-box .admin--form-input,
+.admin--search-box .admin--search-input,
+.admin--search-box .admin--form-select,
+.admin--search-box .admin--search-select {
+  border: 1px solid var(--admin-border-color) !important;
+  height: 33px !important;
+  padding: 6px 14px !important;
+  font-size: 13px !important;
+}
+
+.admin--btn-small-warning {
+  background: #ff9800;
+  color: #ffffff;
+  border: none;
+}
+
+.admin--btn-small-warning:hover:not(:disabled) {
+  background: #f57c00;
+}
+</style>

+ 487 - 0
app/pages/site-manager/basic/popup/create.vue

@@ -0,0 +1,487 @@
+<template>
+  <div class="admin--popup-form">
+    <form @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 형태 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >형태 <span class="admin--required">*</span></label
+        >
+        <div class="admin--radio-group">
+          <label class="admin--radio-label">
+            <input v-model="formData.type" type="radio" value="html" name="type" />
+            <span>HTML</span>
+          </label>
+          <label class="admin--radio-label">
+            <input v-model="formData.type" type="radio" value="image" name="type" />
+            <span>단일페이지</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 사이트 선택 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >사이트 <span class="admin--required">*</span></label
+        >
+        <select v-model="formData.site" class="admin--form-select" required>
+          <option value="ford">포드</option>
+          <option value="lincoln">링컨</option>
+        </select>
+      </div>
+
+      <!-- 제목 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >제목 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.title"
+          type="text"
+          class="admin--form-input"
+          placeholder="팝업 제목을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 시작일/종료일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >시작일/종료일 <span class="admin--required">*</span></label
+        >
+        <div class="admin--date-range">
+          <DatePicker
+            v-model="formData.start_date"
+            placeholder="시작일 선택"
+            :max-date="formData.end_date"
+            required
+          />
+          <span class="admin--date-separator">~</span>
+          <DatePicker
+            v-model="formData.end_date"
+            placeholder="종료일 선택"
+            :min-date="formData.start_date"
+            required
+          />
+        </div>
+      </div>
+
+      <!-- 시작시간 (선택) -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">시작시간 <span class="admin--hint">(미입력 시 시작일 00:00부터 바로 출력)</span></label>
+        <div class="admin--time-group">
+          <label class="admin--radio-label">
+            <input v-model="formData.start_period" type="radio" value="AM" name="start_period" />
+            <span>오전</span>
+          </label>
+          <label class="admin--radio-label">
+            <input v-model="formData.start_period" type="radio" value="PM" name="start_period" />
+            <span>오후</span>
+          </label>
+          <input
+            v-model.number="formData.start_hour"
+            type="number"
+            class="admin--form-input admin--time-input"
+            placeholder="시"
+            min="1"
+            max="12"
+          />
+          <span>:</span>
+          <input
+            v-model.number="formData.start_minute"
+            type="number"
+            class="admin--form-input admin--time-input"
+            placeholder="분"
+            min="0"
+            max="59"
+          />
+        </div>
+      </div>
+
+      <!-- 종료시간 (선택) -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">종료시간 <span class="admin--hint">(미입력 시 종료일 자정까지 노출)</span></label>
+        <div class="admin--time-group">
+          <label class="admin--radio-label">
+            <input v-model="formData.end_period" type="radio" value="AM" name="end_period" />
+            <span>오전</span>
+          </label>
+          <label class="admin--radio-label">
+            <input v-model="formData.end_period" type="radio" value="PM" name="end_period" />
+            <span>오후</span>
+          </label>
+          <input
+            v-model.number="formData.end_hour"
+            type="number"
+            class="admin--form-input admin--time-input"
+            placeholder="시"
+            min="1"
+            max="12"
+          />
+          <span>:</span>
+          <input
+            v-model.number="formData.end_minute"
+            type="number"
+            class="admin--form-input admin--time-input"
+            placeholder="분"
+            min="0"
+            max="59"
+          />
+        </div>
+      </div>
+
+      <!-- 팝업창 사이즈 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >팝업창 사이즈 <span class="admin--required">*</span></label
+        >
+        <div class="admin--size-group">
+          <div class="admin--size-item">
+            <label>가로</label>
+            <input
+              v-model.number="formData.width"
+              type="number"
+              class="admin--form-input"
+              placeholder="800"
+              min="100"
+              required
+            />
+            <span>px</span>
+          </div>
+          <div class="admin--size-item">
+            <label>세로</label>
+            <input
+              v-model.number="formData.height"
+              type="number"
+              class="admin--form-input"
+              placeholder="600"
+              min="100"
+              required
+            />
+            <span>px</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 팝업창 위치 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >팝업창 위치 <span class="admin--required">*</span></label
+        >
+        <div class="admin--size-group">
+          <div class="admin--size-item">
+            <label>TOP</label>
+            <input
+              v-model.number="formData.position_top"
+              type="number"
+              class="admin--form-input"
+              placeholder="100"
+              min="0"
+              required
+            />
+            <span>px</span>
+          </div>
+          <div class="admin--size-item">
+            <label>LEFT</label>
+            <input
+              v-model.number="formData.position_left"
+              type="number"
+              class="admin--form-input"
+              placeholder="100"
+              min="0"
+              required
+            />
+            <span>px</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 쿠키설정 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">쿠키설정</label>
+        <div class="admin--radio-group">
+          <label class="admin--radio-label">
+            <input
+              v-model="formData.cookie_setting"
+              type="radio"
+              value="today"
+              name="cookie_setting"
+            />
+            <span>오늘 하루 창 띄우지 않음</span>
+          </label>
+          <label class="admin--radio-label">
+            <input
+              v-model="formData.cookie_setting"
+              type="radio"
+              value="forever"
+              name="cookie_setting"
+            />
+            <span>다시는 창을 띄우지 않음</span>
+          </label>
+          <label class="admin--radio-label">
+            <input
+              v-model="formData.cookie_setting"
+              type="radio"
+              value="none"
+              name="cookie_setting"
+            />
+            <span>사용 안 함</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 출력내용 (HTML) -->
+      <div v-if="formData.type === 'html'" class="admin--form-group">
+        <label class="admin--form-label"
+          >출력내용 <span class="admin--required">*</span></label
+        >
+        <SunEditor
+          v-model="formData.content"
+          height="400px"
+          placeholder="팝업 내용을 입력하세요"
+        />
+      </div>
+
+      <!-- 출력내용 (이미지) -->
+      <div v-if="formData.type === 'image'" class="admin--form-group">
+        <label class="admin--form-label"
+          >이미지 첨부 <span class="admin--required">*</span></label
+        >
+        <input
+          type="file"
+          accept="image/*"
+          class="admin--form-file"
+          @change="handleImageUpload"
+        />
+        <div v-if="imagePreview" class="admin--image-preview">
+          <img :src="imagePreview" alt="미리보기" />
+          <button type="button" class="admin--btn-remove-image" @click="removeImage">
+            삭제
+          </button>
+        </div>
+      </div>
+
+      <!-- 링크 URL (단일페이지일 경우) -->
+      <div v-if="formData.type === 'image'" class="admin--form-group">
+        <label class="admin--form-label">링크 URL</label>
+        <input
+          v-model="formData.link_url"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com"
+        />
+      </div>
+
+      <!-- 링크 타겟 (단일페이지일 경우) -->
+      <div v-if="formData.type === 'image'" class="admin--form-group">
+        <label class="admin--form-label">링크 열기 방식</label>
+        <div class="admin--radio-group">
+          <label class="admin--radio-label">
+            <input
+              v-model="formData.link_target"
+              type="radio"
+              value="_blank"
+              name="link_target"
+            />
+            <span>새창</span>
+          </label>
+          <label class="admin--radio-label">
+            <input
+              v-model="formData.link_target"
+              type="radio"
+              value="_self"
+              name="link_target"
+            />
+            <span>현재창</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
+          {{ isSaving ? "저장 중..." : "확인" }}
+        </button>
+        <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+  import { useRouter } from "vue-router";
+  import SunEditor from "~/components/admin/SunEditor.vue";
+  import DatePicker from "~/components/admin/DatePicker.vue";
+
+  definePageMeta({
+    layout: "admin",
+    middleware: ["auth"],
+  });
+
+  const router = useRouter();
+  const { post, upload } = useApi();
+
+  const isSaving = ref(false);
+  const successMessage = ref("");
+  const errorMessage = ref("");
+  const imagePreview = ref(null);
+  const imageFile = ref(null);
+
+  const formData = ref({
+    type: "html",
+    site: "ford",
+    title: "",
+    start_date: "",
+    end_date: "",
+    start_period: "AM",
+    start_hour: null,
+    start_minute: null,
+    end_period: "AM",
+    end_hour: null,
+    end_minute: null,
+    width: 800,
+    height: 600,
+    position_top: 100,
+    position_left: 100,
+    cookie_setting: "none",
+    content: "",
+    image_url: "",
+    link_url: "",
+    link_target: "_blank",
+  });
+
+  // 12시간제 → 24시간 "HH:MM:SS" (입력이 비어있으면 null)
+  const toTime24 = (period, hour, minute) => {
+    if (hour === null || hour === undefined || hour === "") return null;
+    let h = Number(hour);
+    const m = Number(minute || 0);
+    if (isNaN(h) || h < 1 || h > 12) return null;
+    if (period === "PM" && h < 12) h += 12;
+    if (period === "AM" && h === 12) h = 0;
+    return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:00`;
+  };
+
+  // 이미지 업로드
+  const handleImageUpload = (event) => {
+    const file = event.target.files[0];
+    if (!file) return;
+
+    if (!file.type.startsWith("image/")) {
+      alert("이미지 파일만 업로드 가능합니다.");
+      return;
+    }
+
+    imageFile.value = file;
+
+    // 미리보기
+    const reader = new FileReader();
+    reader.onload = (e) => {
+      imagePreview.value = e.target.result;
+    };
+    reader.readAsDataURL(file);
+  };
+
+  // 이미지 삭제
+  const removeImage = () => {
+    imagePreview.value = null;
+    imageFile.value = null;
+    formData.value.image_url = "";
+  };
+
+  // 폼 제출
+  const handleSubmit = async () => {
+    successMessage.value = "";
+    errorMessage.value = "";
+
+    // 유효성 검사
+    if (!formData.value.title) {
+      errorMessage.value = "제목을 입력하세요.";
+      return;
+    }
+
+    if (!formData.value.start_date || !formData.value.end_date) {
+      errorMessage.value = "시작일과 종료일을 선택하세요.";
+      return;
+    }
+
+    if (formData.value.type === "html" && !formData.value.content) {
+      errorMessage.value = "출력내용을 입력하세요.";
+      return;
+    }
+
+    if (
+      formData.value.type === "image" &&
+      !imageFile.value &&
+      !formData.value.image_url
+    ) {
+      errorMessage.value = "이미지를 첨부하세요.";
+      return;
+    }
+
+    isSaving.value = true;
+
+    try {
+      let imageUrl = formData.value.image_url;
+
+      // 이미지 업로드 (단일페이지인 경우)
+      if (formData.value.type === "image" && imageFile.value) {
+        const formDataImage = new FormData();
+        formDataImage.append("file", imageFile.value);
+
+        const { data: uploadData, error: uploadError } = await upload(
+          "/upload/image",
+          formDataImage
+        );
+
+        console.log("[Popup Create] 이미지 업로드 응답:", { uploadData, uploadError });
+
+        if (uploadError || !uploadData?.success) {
+          errorMessage.value = uploadError?.message || "이미지 업로드에 실패했습니다.";
+          isSaving.value = false;
+          return;
+        }
+
+        imageUrl = uploadData.data?.url || uploadData.data;
+      }
+
+      // 팝업 등록
+      const submitData = {
+        ...formData.value,
+        image_url: imageUrl,
+        start_time: toTime24(formData.value.start_period, formData.value.start_hour, formData.value.start_minute),
+        end_time: toTime24(formData.value.end_period, formData.value.end_hour, formData.value.end_minute),
+      };
+
+      const { data, error } = await post("/basic/popup", submitData);
+
+      if (error || !data?.success) {
+        errorMessage.value = error?.message || data?.message || "등록에 실패했습니다.";
+      } else {
+        successMessage.value = data.message || "팝업이 등록되었습니다.";
+        setTimeout(() => {
+          router.push("/site-manager/basic/popup");
+        }, 1000);
+      }
+    } catch (error) {
+      errorMessage.value = "서버 오류가 발생했습니다.";
+      console.error("Save error:", error);
+    } finally {
+      isSaving.value = false;
+    }
+  };
+
+  // 목록으로 이동
+  const goToList = () => {
+    router.push("/site-manager/basic/popup");
+  };
+</script>

+ 558 - 0
app/pages/site-manager/basic/popup/edit/[id].vue

@@ -0,0 +1,558 @@
+<template>
+  <div class="admin--popup-form">
+    <div v-if="isLoading" class="admin--loading">데이터를 불러오는 중...</div>
+
+    <form v-else @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 형태 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >형태 <span class="admin--required">*</span></label
+        >
+        <div class="admin--radio-group">
+          <label class="admin--radio-label">
+            <input v-model="formData.type" type="radio" value="html" name="type" />
+            <span>HTML</span>
+          </label>
+          <label class="admin--radio-label">
+            <input v-model="formData.type" type="radio" value="image" name="type" />
+            <span>단일페이지</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 사이트 선택 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >사이트 <span class="admin--required">*</span></label
+        >
+        <select v-model="formData.site" class="admin--form-select" required>
+          <option value="ford">포드</option>
+          <option value="lincoln">링컨</option>
+        </select>
+      </div>
+
+      <!-- 제목 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >제목 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.title"
+          type="text"
+          class="admin--form-input"
+          placeholder="팝업 제목을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 시작일/종료일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >시작일/종료일 <span class="admin--required">*</span></label
+        >
+        <div class="admin--date-range">
+          <DatePicker
+            v-model="formData.start_date"
+            placeholder="시작일 선택"
+            :max-date="formData.end_date"
+            required
+          />
+          <span class="admin--date-separator">~</span>
+          <DatePicker
+            v-model="formData.end_date"
+            placeholder="종료일 선택"
+            :min-date="formData.start_date"
+            required
+          />
+        </div>
+      </div>
+
+      <!-- 시작시간 (선택) -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">시작시간 <span class="admin--hint">(미입력 시 시작일 00:00부터 바로 출력)</span></label>
+        <div class="admin--time-group">
+          <label class="admin--radio-label">
+            <input v-model="formData.start_period" type="radio" value="AM" name="start_period" />
+            <span>오전</span>
+          </label>
+          <label class="admin--radio-label">
+            <input v-model="formData.start_period" type="radio" value="PM" name="start_period" />
+            <span>오후</span>
+          </label>
+          <input
+            v-model.number="formData.start_hour"
+            type="number"
+            class="admin--form-input admin--time-input"
+            placeholder="시"
+            min="1"
+            max="12"
+          />
+          <span>:</span>
+          <input
+            v-model.number="formData.start_minute"
+            type="number"
+            class="admin--form-input admin--time-input"
+            placeholder="분"
+            min="0"
+            max="59"
+          />
+        </div>
+      </div>
+
+      <!-- 종료시간 (선택) -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">종료시간 <span class="admin--hint">(미입력 시 종료일 자정까지 노출)</span></label>
+        <div class="admin--time-group">
+          <label class="admin--radio-label">
+            <input v-model="formData.end_period" type="radio" value="AM" name="end_period" />
+            <span>오전</span>
+          </label>
+          <label class="admin--radio-label">
+            <input v-model="formData.end_period" type="radio" value="PM" name="end_period" />
+            <span>오후</span>
+          </label>
+          <input
+            v-model.number="formData.end_hour"
+            type="number"
+            class="admin--form-input admin--time-input"
+            placeholder="시"
+            min="1"
+            max="12"
+          />
+          <span>:</span>
+          <input
+            v-model.number="formData.end_minute"
+            type="number"
+            class="admin--form-input admin--time-input"
+            placeholder="분"
+            min="0"
+            max="59"
+          />
+        </div>
+      </div>
+
+      <!-- 팝업창 사이즈 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >팝업창 사이즈 <span class="admin--required">*</span></label
+        >
+        <div class="admin--size-group">
+          <div class="admin--size-item">
+            <label>가로</label>
+            <input
+              v-model.number="formData.width"
+              type="number"
+              class="admin--form-input"
+              placeholder="800"
+              min="100"
+              required
+            />
+            <span>px</span>
+          </div>
+          <div class="admin--size-item">
+            <label>세로</label>
+            <input
+              v-model.number="formData.height"
+              type="number"
+              class="admin--form-input"
+              placeholder="600"
+              min="100"
+              required
+            />
+            <span>px</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 팝업창 위치 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >팝업창 위치 <span class="admin--required">*</span></label
+        >
+        <div class="admin--size-group">
+          <div class="admin--size-item">
+            <label>TOP</label>
+            <input
+              v-model.number="formData.position_top"
+              type="number"
+              class="admin--form-input"
+              placeholder="100"
+              min="0"
+              required
+            />
+            <span>px</span>
+          </div>
+          <div class="admin--size-item">
+            <label>LEFT</label>
+            <input
+              v-model.number="formData.position_left"
+              type="number"
+              class="admin--form-input"
+              placeholder="100"
+              min="0"
+              required
+            />
+            <span>px</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 쿠키설정 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">쿠키설정</label>
+        <div class="admin--radio-group">
+          <label class="admin--radio-label">
+            <input
+              v-model="formData.cookie_setting"
+              type="radio"
+              value="today"
+              name="cookie_setting"
+            />
+            <span>오늘 하루 창 띄우지 않음</span>
+          </label>
+          <label class="admin--radio-label">
+            <input
+              v-model="formData.cookie_setting"
+              type="radio"
+              value="forever"
+              name="cookie_setting"
+            />
+            <span>다시는 창을 띄우지 않음</span>
+          </label>
+          <label class="admin--radio-label">
+            <input
+              v-model="formData.cookie_setting"
+              type="radio"
+              value="none"
+              name="cookie_setting"
+            />
+            <span>사용 안 함</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 출력내용 (HTML) -->
+      <div v-if="formData.type === 'html'" class="admin--form-group">
+        <label class="admin--form-label"
+          >출력내용 <span class="admin--required">*</span></label
+        >
+        <SunEditor
+          v-model="formData.content"
+          height="400px"
+          placeholder="팝업 내용을 입력하세요"
+        />
+      </div>
+
+      <!-- 출력내용 (이미지) -->
+      <div v-if="formData.type === 'image'" class="admin--form-group">
+        <label class="admin--form-label"
+          >이미지 첨부 <span class="admin--required">*</span></label
+        >
+        <input
+          type="file"
+          accept="image/*"
+          class="admin--form-file"
+          @change="handleImageUpload"
+        />
+        <div v-if="imagePreview || formData.image_url" class="admin--image-preview">
+          <img :src="imagePreview || formData.image_url" alt="미리보기" />
+          <button type="button" class="admin--btn-remove-image" @click="removeImage">
+            삭제
+          </button>
+        </div>
+      </div>
+
+      <!-- 링크 URL (단일페이지일 경우) -->
+      <div v-if="formData.type === 'image'" class="admin--form-group">
+        <label class="admin--form-label">링크 URL</label>
+        <input
+          v-model="formData.link_url"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com"
+        />
+      </div>
+
+      <!-- 링크 타겟 (단일페이지일 경우) -->
+      <div v-if="formData.type === 'image'" class="admin--form-group">
+        <label class="admin--form-label">링크 열기 방식</label>
+        <div class="admin--radio-group">
+          <label class="admin--radio-label">
+            <input
+              v-model="formData.link_target"
+              type="radio"
+              value="_blank"
+              name="link_target"
+            />
+            <span>새창</span>
+          </label>
+          <label class="admin--radio-label">
+            <input
+              v-model="formData.link_target"
+              type="radio"
+              value="_self"
+              name="link_target"
+            />
+            <span>현재창</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
+          {{ isSaving ? "저장 중..." : "확인" }}
+        </button>
+        <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+  import { ref, onMounted } from "vue";
+  import { useRoute, useRouter } from "vue-router";
+  import SunEditor from "~/components/admin/SunEditor.vue";
+  import DatePicker from "~/components/admin/DatePicker.vue";
+
+  definePageMeta({
+    layout: "admin",
+    middleware: ["auth"],
+  });
+
+  const route = useRoute();
+  const router = useRouter();
+  const { get, put, upload } = useApi();
+  const { getImageUrl } = useImage();
+
+  const isLoading = ref(true);
+  const isSaving = ref(false);
+  const successMessage = ref("");
+  const errorMessage = ref("");
+  const imagePreview = ref(null);
+  const imageFile = ref(null);
+
+  const formData = ref({
+    type: "html",
+    site: "ford",
+    title: "",
+    start_date: "",
+    end_date: "",
+    start_period: "AM",
+    start_hour: null,
+    start_minute: null,
+    end_period: "AM",
+    end_hour: null,
+    end_minute: null,
+    width: 800,
+    height: 600,
+    position_top: 100,
+    position_left: 100,
+    cookie_setting: "none",
+    content: "",
+    image_url: "",
+    link_url: "",
+    link_target: "_blank",
+  });
+
+  // 12시간제 ↔ 24시간 "HH:MM:SS" 변환
+  const toTime24 = (period, hour, minute) => {
+    if (hour === null || hour === undefined || hour === "") return null;
+    let h = Number(hour);
+    const m = Number(minute || 0);
+    if (isNaN(h) || h < 1 || h > 12) return null;
+    if (period === "PM" && h < 12) h += 12;
+    if (period === "AM" && h === 12) h = 0;
+    return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:00`;
+  };
+
+  const fromTime24 = (timeStr) => {
+    if (!timeStr) return { period: "AM", hour: null, minute: null };
+    const [hStr, mStr] = String(timeStr).split(":");
+    let h = Number(hStr);
+    const m = Number(mStr || 0);
+    if (isNaN(h)) return { period: "AM", hour: null, minute: null };
+    const period = h >= 12 ? "PM" : "AM";
+    const hour12 = h % 12 === 0 ? 12 : h % 12;
+    return { period, hour: hour12, minute: m };
+  };
+
+  // 데이터 로드
+  const loadPopup = async () => {
+    isLoading.value = true;
+
+    const id = route.params.id;
+    const { data, error } = await get(`/basic/popup/${id}`);
+
+    console.log("[Popup Edit] 데이터 로드 응답:", { data, error });
+
+    // API 응답: { success: true, data: {...}, message }
+    if (data?.success && data?.data) {
+      const popup = data.data;
+      const startParts = fromTime24(popup.start_time);
+      const endParts = fromTime24(popup.end_time);
+      formData.value = {
+        type: popup.type || "html",
+        site: popup.site || "ford",
+        title: popup.title || "",
+        start_date: popup.start_date || "",
+        end_date: popup.end_date || "",
+        start_period: startParts.period,
+        start_hour: startParts.hour,
+        start_minute: startParts.minute,
+        end_period: endParts.period,
+        end_hour: endParts.hour,
+        end_minute: endParts.minute,
+        width: popup.width || 800,
+        height: popup.height || 600,
+        position_top: popup.position_top || 100,
+        position_left: popup.position_left || 100,
+        cookie_setting: popup.cookie_setting || "none",
+        content: popup.content || "",
+        image_url: popup.image_url || "",
+        link_url: popup.link_url || "",
+        link_target: popup.link_target || "_blank",
+      };
+
+      // 이미지 미리보기는 새 이미지 업로드시에만 사용
+      // 기존 이미지는 formData.image_url을 통해 getImageUrl()로 표시
+      imagePreview.value = null;
+      console.log("[Popup Edit] 이미지 URL:", popup.image_url);
+
+      console.log("[Popup Edit] 데이터 로드 성공:", formData.value);
+    } else {
+      console.log("[Popup Edit] 데이터 로드 실패");
+    }
+
+    isLoading.value = false;
+  };
+
+  // 이미지 업로드
+  const handleImageUpload = (event) => {
+    const file = event.target.files[0];
+    if (!file) return;
+
+    if (!file.type.startsWith("image/")) {
+      alert("이미지 파일만 업로드 가능합니다.");
+      return;
+    }
+
+    imageFile.value = file;
+
+    // 미리보기
+    const reader = new FileReader();
+    reader.onload = (e) => {
+      imagePreview.value = e.target.result;
+    };
+    reader.readAsDataURL(file);
+  };
+
+  // 이미지 삭제
+  const removeImage = () => {
+    imagePreview.value = null;
+    imageFile.value = null;
+    formData.value.image_url = "";
+  };
+
+  // 폼 제출
+  const handleSubmit = async () => {
+    successMessage.value = "";
+    errorMessage.value = "";
+
+    // 유효성 검사
+    if (!formData.value.title) {
+      errorMessage.value = "제목을 입력하세요.";
+      return;
+    }
+
+    if (!formData.value.start_date || !formData.value.end_date) {
+      errorMessage.value = "시작일과 종료일을 선택하세요.";
+      return;
+    }
+
+    if (formData.value.type === "html" && !formData.value.content) {
+      errorMessage.value = "출력내용을 입력하세요.";
+      return;
+    }
+
+    if (
+      formData.value.type === "image" &&
+      !imageFile.value &&
+      !formData.value.image_url
+    ) {
+      errorMessage.value = "이미지를 첨부하세요.";
+      return;
+    }
+
+    isSaving.value = true;
+
+    try {
+      let imageUrl = formData.value.image_url;
+
+      // 이미지 업로드 (새로운 이미지가 있는 경우)
+      if (formData.value.type === "image" && imageFile.value) {
+        const formDataImage = new FormData();
+        formDataImage.append("file", imageFile.value);
+
+        const { data: uploadData, error: uploadError } = await upload(
+          "/upload/image",
+          formDataImage
+        );
+
+        console.log("[Popup Edit] 이미지 업로드 응답:", { uploadData, uploadError });
+
+        if (uploadError || !uploadData?.success) {
+          errorMessage.value = uploadError?.message || "이미지 업로드에 실패했습니다.";
+          isSaving.value = false;
+          return;
+        }
+
+        imageUrl = uploadData.data?.url || uploadData.data;
+      }
+
+      // 팝업 수정
+      const submitData = {
+        ...formData.value,
+        image_url: imageUrl,
+        start_time: toTime24(formData.value.start_period, formData.value.start_hour, formData.value.start_minute),
+        end_time: toTime24(formData.value.end_period, formData.value.end_hour, formData.value.end_minute),
+      };
+
+      const id = route.params.id;
+      const { data, error } = await put(`/basic/popup/${id}`, submitData);
+
+      if (error || !data?.success) {
+        errorMessage.value = error?.message || data?.message || "수정에 실패했습니다.";
+      } else {
+        successMessage.value = data.message || "팝업이 수정되었습니다.";
+        setTimeout(() => {
+          router.push("/site-manager/basic/popup");
+        }, 1000);
+      }
+    } catch (error) {
+      errorMessage.value = "서버 오류가 발생했습니다.";
+      console.error("Save error:", error);
+    } finally {
+      isSaving.value = false;
+    }
+  };
+
+  // 목록으로 이동
+  const goToList = () => {
+    router.push("/site-manager/basic/popup");
+  };
+
+  onMounted(() => {
+    loadPopup();
+  });
+</script>

+ 319 - 0
app/pages/site-manager/basic/popup/index.vue

@@ -0,0 +1,319 @@
+<template>
+  <div class="admin--popup-list">
+    <!-- 검색 영역 -->
+    <div class="admin--search-box">
+      <div class="admin--search-form">
+        <select v-model="searchType" class="admin--form-select admin--search-select">
+          <option value="title">제목</option>
+          <option value="status">상태</option>
+        </select>
+        <input
+          v-model="searchKeyword"
+          type="text"
+          class="admin--form-input admin--search-input"
+          placeholder="검색어를 입력하세요"
+          @keyup.enter="handleSearch"
+        >
+        <button class="admin--btn-small admin--btn-small-primary" @click="handleSearch">
+          검색
+        </button>
+        <button class="admin--btn-small admin--btn-small-secondary" @click="handleReset">
+          초기화
+        </button>
+      </div>
+      <div class="admin--search-actions">
+        <button class="admin--btn-small admin--btn-small-primary" @click="goToCreate">
+          + 팝업 등록
+        </button>
+      </div>
+    </div>
+
+    <!-- 테이블 -->
+    <div class="admin--table-wrapper">
+      <table class="admin--table">
+        <thead>
+          <tr>
+            <th>NO</th>
+            <th>출력형태</th>
+            <th>제목</th>
+            <th>시작일</th>
+            <th>종료일</th>
+            <th>상태</th>
+            <th>관리</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="!popups || popups.length === 0">
+            <td colspan="7" class="admin--table-empty">
+              등록된 팝업이 없습니다.
+            </td>
+          </tr>
+          <tr v-else v-for="(popup, index) in popups" :key="popup.id">
+            <td>{{ totalCount - ((currentPage - 1) * perPage + index) }}</td>
+            <td>
+              <span class="admin--badge" :class="`admin--badge-${popup.type}`">
+                {{ popup.type === 'html' ? 'HTML' : '단일페이지' }}
+              </span>
+            </td>
+            <td class="admin--table-title">{{ popup.title }}</td>
+            <td>{{ formatDateTime(popup.start_date, popup.start_time) }}</td>
+            <td>{{ formatDateTime(popup.end_date, popup.end_time) }}</td>
+            <td>
+              <span class="admin--badge" :class="getStatusClass(popup)">
+                {{ getStatusText(popup) }}
+              </span>
+            </td>
+            <td>
+              <div class="admin--table-actions">
+                <button
+                  class="admin--btn-small admin--btn-small-primary"
+                  @click="goToEdit(popup.id)"
+                >
+                  수정
+                </button>
+                <button
+                  class="admin--btn-small admin--btn-small-danger"
+                  @click="handleDelete(popup.id)"
+                >
+                  삭제
+                </button>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <!-- 페이지네이션 -->
+    <div v-if="totalPages > 1" class="admin--pagination">
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(1)"
+        title="처음"
+      >
+        ⏮
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(currentPage - 1)"
+        title="이전"
+      >
+        ◀
+      </button>
+      <button
+        v-for="page in visiblePages"
+        :key="page"
+        class="admin--pagination-btn"
+        :class="{ 'is-active': page === currentPage }"
+        @click="changePage(page)"
+      >
+        {{ page }}
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(currentPage + 1)"
+        title="다음"
+      >
+        ▶
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(totalPages)"
+        title="끝"
+      >
+        ⏭
+      </button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const router = useRouter()
+const { get, del } = useApi()
+const popups = ref([])
+const searchType = ref('title')
+const searchKeyword = ref('')
+const currentPage = ref(1)
+const perPage = ref(10)
+const totalCount = ref(0)
+const totalPages = ref(0)
+
+// 보이는 페이지 번호 계산
+const visiblePages = computed(() => {
+  const pages = []
+  const maxVisible = 5
+  let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2))
+  let end = Math.min(totalPages.value, start + maxVisible - 1)
+
+  if (end - start < maxVisible - 1) {
+    start = Math.max(1, end - maxVisible + 1)
+  }
+
+  for (let i = start; i <= end; i++) {
+    pages.push(i)
+  }
+
+  return pages
+})
+
+// 데이터 로드
+const loadPopups = async () => {
+  const params = {
+    page: currentPage.value,
+    per_page: perPage.value
+  }
+
+  if (searchKeyword.value) {
+    params.search_type = searchType.value
+    params.search_keyword = searchKeyword.value
+  }
+
+  const { data, error } = await get('/basic/popup', { params })
+
+  console.log('[Popup List] API 응답:', { data, error })
+
+  // API 응답: { success: true, data: { items, total, page }, message }
+  if (data?.success && data?.data) {
+    popups.value = data.data.items || []
+    totalCount.value = data.data.total || 0
+    totalPages.value = Math.ceil(totalCount.value / perPage.value)
+    console.log('[Popup List] 로드 성공:', {
+      items: popups.value.length,
+      total: totalCount.value,
+      pages: totalPages.value
+    })
+  } else {
+    console.log('[Popup List] 데이터 없음 또는 에러')
+  }
+}
+
+// 검색
+const handleSearch = () => {
+  currentPage.value = 1
+  loadPopups()
+}
+
+// 초기화
+const handleReset = () => {
+  searchType.value = 'title'
+  searchKeyword.value = ''
+  currentPage.value = 1
+  loadPopups()
+}
+
+// 페이지 변경
+const changePage = (page) => {
+  if (page < 1 || page > totalPages.value) return
+  currentPage.value = page
+  loadPopups()
+  window.scrollTo({ top: 0, behavior: 'smooth' })
+}
+
+// 등록 페이지로 이동
+const goToCreate = () => {
+  router.push('/site-manager/basic/popup/create')
+}
+
+// 수정 페이지로 이동
+const goToEdit = (id) => {
+  router.push(`/site-manager/basic/popup/edit/${id}`)
+}
+
+// 삭제
+const handleDelete = async (id) => {
+  if (!confirm('정말 삭제하시겠습니까?')) return
+
+  const { data, error } = await del(`/basic/popup/${id}`)
+
+  console.log('[Popup Delete] API 응답:', { data, error })
+
+  if (error || !data?.success) {
+    alert(error?.message || '삭제에 실패했습니다.')
+  } else {
+    alert(data.message || '삭제되었습니다.')
+    loadPopups()
+  }
+}
+
+// 날짜 포맷
+const formatDate = (dateString) => {
+  if (!dateString) return '-'
+  const date = new Date(dateString)
+  return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`
+}
+
+// 날짜+시간 포맷 ("HH:MM:SS" → "오전/오후 H:MM")
+const formatDateTime = (dateString, timeString) => {
+  const d = formatDate(dateString)
+  if (!timeString) return d
+  const [hStr, mStr] = String(timeString).split(':')
+  let h = Number(hStr)
+  const m = Number(mStr || 0)
+  if (isNaN(h)) return d
+  const period = h >= 12 ? '오후' : '오전'
+  const hour12 = h % 12 === 0 ? 12 : h % 12
+  return `${d} ${period} ${hour12}:${String(m).padStart(2, '0')}`
+}
+
+// 팝업 시작/종료 Date 계산 (시간 null 처리 포함)
+const buildStart = (popup) => new Date(`${popup.start_date}T${popup.start_time || '00:00:00'}`)
+const buildEnd = (popup) => new Date(`${popup.end_date}T${popup.end_time || '23:59:59'}`)
+
+// 상태 클래스
+const getStatusClass = (popup) => {
+  const now = new Date()
+  const start = buildStart(popup)
+  const end = buildEnd(popup)
+
+  if (now < start) return 'admin--badge-scheduled'
+  if (now > end) return 'admin--badge-ended'
+  return 'admin--badge-active'
+}
+
+// 상태 텍스트
+const getStatusText = (popup) => {
+  const now = new Date()
+  const start = buildStart(popup)
+  const end = buildEnd(popup)
+
+  if (now < start) return '예정'
+  if (now > end) return '종료'
+  return '진행중'
+}
+
+onMounted(() => {
+  loadPopups()
+})
+</script>
+
+<style scoped>
+.admin--search-actions .admin--btn-primary {
+  background: var(--admin-accent-primary);
+  color: white;
+  border-color: var(--admin-accent-primary);
+  font-weight: 500;
+  padding: 8px 18px;
+  font-size: 13px;
+  border-radius: 8px;
+  transition: all 0.3s ease;
+}
+
+.admin--search-actions .admin--btn-primary:hover {
+  background: var(--admin-accent-hover);
+  border-color: var(--admin-accent-hover);
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+</style>

+ 324 - 0
app/pages/site-manager/basic/site-info.vue

@@ -0,0 +1,324 @@
+<template>
+  <div class="admin--site-info">
+    <div v-if="isLoading" class="admin--loading">
+      데이터를 불러오는 중...
+    </div>
+
+    <form v-else @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 사이트명 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">사이트명 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.site_name"
+          type="text"
+          class="admin--form-input"
+          placeholder="사이트명을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 사이트 URL -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">사이트 URL <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.site_url"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com"
+          required
+        >
+      </div>
+
+      <!-- 대표명 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">대표명 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.ceo_name"
+          type="text"
+          class="admin--form-input"
+          placeholder="대표명을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 대표 이메일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">대표 이메일 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.ceo_email"
+          type="email"
+          class="admin--form-input"
+          placeholder="email@example.com"
+          required
+        >
+      </div>
+
+      <!-- 전화번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">전화번호 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.phone"
+          type="tel"
+          class="admin--form-input"
+          placeholder="02-1234-5678"
+          required
+        >
+      </div>
+
+      <!-- FAX번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">FAX번호</label>
+        <input
+          v-model="formData.fax"
+          type="tel"
+          class="admin--form-input"
+          placeholder="02-1234-5679"
+        >
+      </div>
+
+      <!-- 회사주소 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">회사주소 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.address"
+          type="text"
+          class="admin--form-input"
+          placeholder="회사 주소를 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- SMS발신번호 (다중) -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">SMS발신번호</label>
+        <div class="admin--multi-input-wrapper">
+          <div
+            v-for="(item, index) in formData.sms_sender_numbers"
+            :key="index"
+            class="admin--multi-input-item"
+          >
+            <div class="admin--sender-row">
+              <input
+                v-model="formData.sms_sender_numbers[index].name"
+                type="text"
+                class="admin--form-input"
+                placeholder="지점명"
+                style="flex: 1; margin-right: 10px;"
+              >
+              <input
+                v-model="formData.sms_sender_numbers[index].number"
+                type="tel"
+                class="admin--form-input"
+                placeholder="010-1234-5678"
+                style="flex: 1;"
+              >
+            </div>
+            <button
+              v-if="formData.sms_sender_numbers.length > 1"
+              type="button"
+              class="admin--btn-remove"
+              @click="removeSenderNumber(index)"
+            >
+              삭제
+            </button>
+          </div>
+          <button
+            type="button"
+            class="admin--btn-add"
+            @click="addSenderNumber"
+          >
+            + 발신번호 추가
+          </button>
+        </div>
+      </div>
+
+      <!-- SMS수신번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">SMS수신번호</label>
+        <input
+          v-model="formData.sms_receiver_number"
+          type="tel"
+          class="admin--form-input"
+          placeholder="010-9876-5432"
+        >
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button
+          type="submit"
+          class="admin--btn admin--btn-primary"
+          :disabled="isSaving"
+        >
+          {{ isSaving ? '저장 중...' : '저장' }}
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const { get, post } = useApi()
+
+const isLoading = ref(true)
+const isSaving = ref(false)
+const successMessage = ref('')
+const errorMessage = ref('')
+const siteInfoId = ref(null)
+
+const formData = ref({
+  site_name: '',
+  site_url: '',
+  ceo_name: '',
+  ceo_email: '',
+  phone: '',
+  fax: '',
+  address: '',
+  sms_sender_numbers: [{ name: '', number: '' }],
+  sms_receiver_number: ''
+})
+
+// 데이터 로드
+const loadSiteInfo = async () => {
+  isLoading.value = true
+
+  const { data, error } = await get('/basic/site-info')
+
+  console.log('[SiteInfo] API 응답:', { data, error })
+
+  // API 응답: { success: true, data: {...}, message }
+  if (data?.success && data?.data) {
+    const siteData = data.data
+    siteInfoId.value = siteData.id
+
+    // SMS 발신번호 데이터 변환 (기존 문자열 배열 → 객체 배열)
+    let senderNumbers = [{ name: '', number: '' }]
+    if (siteData.sms_sender_numbers && siteData.sms_sender_numbers.length > 0) {
+      senderNumbers = siteData.sms_sender_numbers.map(item => {
+        // 이미 객체 형태인 경우
+        if (typeof item === 'object' && item.name !== undefined) {
+          return item
+        }
+        // 문자열인 경우 (기존 데이터)
+        return { name: '', number: item }
+      })
+    }
+
+    formData.value = {
+      site_name: siteData.site_name || '',
+      site_url: siteData.site_url || '',
+      ceo_name: siteData.ceo_name || '',
+      ceo_email: siteData.ceo_email || '',
+      phone: siteData.phone || '',
+      fax: siteData.fax || '',
+      address: siteData.address || '',
+      sms_sender_numbers: senderNumbers,
+      sms_receiver_number: siteData.sms_receiver_number || ''
+    }
+  }
+
+  isLoading.value = false
+}
+
+// 발신번호 추가
+const addSenderNumber = () => {
+  formData.value.sms_sender_numbers.push({ name: '', number: '' })
+}
+
+// 발신번호 삭제
+const removeSenderNumber = (index) => {
+  formData.value.sms_sender_numbers.splice(index, 1)
+}
+
+// 폼 제출
+const handleSubmit = async () => {
+  successMessage.value = ''
+  errorMessage.value = ''
+  isSaving.value = true
+
+  try {
+    // 빈 발신번호 제거 (지점명 또는 번호가 비어있지 않은 것만)
+    const cleanedSenderNumbers = formData.value.sms_sender_numbers.filter(
+      sender => (sender.name && sender.name.trim() !== '') || (sender.number && sender.number.trim() !== '')
+    )
+
+    const submitData = {
+      ...formData.value,
+      sms_sender_numbers: cleanedSenderNumbers
+    }
+
+    // POST로 통일 (등록/수정 모두)
+    const { data, error } = await post('/basic/site-info', submitData)
+
+    console.log('[SiteInfo] 저장 응답:', { data, error })
+
+    if (error) {
+      errorMessage.value = error.message || '저장에 실패했습니다.'
+    } else if (data?.success) {
+      successMessage.value = data.message || '사이트 정보가 저장되었습니다.'
+
+      // 저장된 데이터로 업데이트
+      if (data.data) {
+        const siteData = data.data
+        siteInfoId.value = siteData.id
+
+        // SMS 발신번호 데이터 변환 (기존 문자열 배열 → 객체 배열)
+        let senderNumbers = [{ name: '', number: '' }]
+        if (siteData.sms_sender_numbers && siteData.sms_sender_numbers.length > 0) {
+          senderNumbers = siteData.sms_sender_numbers.map(item => {
+            // 이미 객체 형태인 경우
+            if (typeof item === 'object' && item.name !== undefined) {
+              return item
+            }
+            // 문자열인 경우 (기존 데이터)
+            return { name: '', number: item }
+          })
+        }
+
+        formData.value = {
+          site_name: siteData.site_name || '',
+          site_url: siteData.site_url || '',
+          ceo_name: siteData.ceo_name || '',
+          ceo_email: siteData.ceo_email || '',
+          phone: siteData.phone || '',
+          fax: siteData.fax || '',
+          address: siteData.address || '',
+          sms_sender_numbers: senderNumbers,
+          sms_receiver_number: siteData.sms_receiver_number || ''
+        }
+      }
+
+      // 3초 후 메시지 자동 제거
+      setTimeout(() => {
+        successMessage.value = ''
+      }, 3000)
+    } else {
+      errorMessage.value = '저장에 실패했습니다.'
+    }
+  } catch (error) {
+    errorMessage.value = '서버 오류가 발생했습니다.'
+    console.error('Save error:', error)
+  } finally {
+    isSaving.value = false
+  }
+}
+
+onMounted(() => {
+  loadSiteInfo()
+})
+</script>

+ 310 - 0
app/pages/site-manager/board/event/create.vue

@@ -0,0 +1,310 @@
+<template>
+  <div class="admin--event-form">
+    <form @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 사이트 선택 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">사이트 <span class="admin--required">*</span></label>
+        <select v-model="formData.site" class="admin--form-select" required>
+          <option value="common">공통</option>
+          <option value="ford">포드</option>
+          <option value="lincoln">링컨</option>
+        </select>
+      </div>
+
+      <!-- 카테고리 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">카테고리 <span class="admin--required">*</span></label>
+        <select v-model="formData.category" class="admin--form-select" required>
+          <option value="">카테고리를 선택하세요</option>
+          <option value="진행중">진행중</option>
+          <option value="완료">완료</option>
+        </select>
+      </div>
+
+      <!-- 댓글허용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">댓글허용</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.allow_comment" type="checkbox">
+            <span>댓글 허용</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 공지 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">공지</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.is_notice" type="checkbox">
+            <span>공지글로 등록</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 이름 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이름 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="이름을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 이메일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이메일 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.email"
+          type="email"
+          class="admin--form-input"
+          placeholder="이메일을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 기간 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">기간 <span class="admin--required">*</span></label>
+        <div class="admin--date-range">
+          <DatePicker
+            v-model="formData.start_date"
+            placeholder="시작일 선택"
+            :max-date="formData.end_date"
+            required
+          />
+          <span class="admin--date-separator">~</span>
+          <DatePicker
+            v-model="formData.end_date"
+            placeholder="종료일 선택"
+            :min-date="formData.start_date"
+            required
+          />
+        </div>
+      </div>
+
+      <!-- 제목 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">제목 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.title"
+          type="text"
+          class="admin--form-input"
+          placeholder="제목을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 내용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">내용 <span class="admin--required">*</span></label>
+        <SunEditor v-model="formData.content" />
+      </div>
+
+      <!-- 파일첨부 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">파일첨부</label>
+        <div class="admin--file-list">
+          <div v-for="(file, index) in attachedFiles" :key="index" class="admin--file-item">
+            <span class="admin--file-name">{{ file.name }}</span>
+            <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
+            <button type="button" class="admin--btn-remove-file" @click="removeFile(index)">
+              삭제
+            </button>
+          </div>
+        </div>
+        <input
+          ref="fileInput"
+          type="file"
+          multiple
+          class="admin--form-file-hidden"
+          @change="handleFileAdd"
+        >
+        <button type="button" class="admin--btn admin--btn-secondary" @click="triggerFileInput">
+          파일 추가
+        </button>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button
+          type="submit"
+          class="admin--btn admin--btn-primary"
+          :disabled="isSaving"
+        >
+          {{ isSaving ? '저장 중...' : '확인' }}
+        </button>
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="goToList"
+        >
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+import SunEditor from '~/components/admin/SunEditor.vue'
+import DatePicker from '~/components/admin/DatePicker.vue'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const router = useRouter()
+const { post, upload } = useApi()
+
+const isSaving = ref(false)
+const successMessage = ref('')
+const errorMessage = ref('')
+const attachedFiles = ref([])
+const fileInput = ref(null)
+
+const formData = ref({
+  site: 'common',
+  category: '',
+  allow_comment: false,
+  is_notice: false,
+  name: '고진',
+  email: 'admin@admin.kr',
+  start_date: '',
+  end_date: '',
+  title: '',
+  content: '',
+  file_urls: []
+})
+
+const triggerFileInput = () => {
+  fileInput.value?.click()
+}
+
+const handleFileAdd = (event) => {
+  const files = Array.from(event.target.files)
+  attachedFiles.value.push(...files)
+  event.target.value = ''
+}
+
+const removeFile = (index) => {
+  attachedFiles.value.splice(index, 1)
+}
+
+const formatFileSize = (bytes) => {
+  if (bytes === 0) return '0 Bytes'
+  const k = 1024
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
+}
+
+const handleSubmit = async () => {
+  successMessage.value = ''
+  errorMessage.value = ''
+
+  if (!formData.value.category) {
+    errorMessage.value = '카테고리를 선택하세요.'
+    return
+  }
+
+  if (!formData.value.title) {
+    errorMessage.value = '제목을 입력하세요.'
+    return
+  }
+
+  if (!formData.value.content) {
+    errorMessage.value = '내용을 입력하세요.'
+    return
+  }
+
+  if (!formData.value.start_date || !formData.value.end_date) {
+    errorMessage.value = '기간을 입력하세요.'
+    return
+  }
+
+  isSaving.value = true
+
+  try {
+    let fileUrls = []
+
+    // 파일 업로드
+    if (attachedFiles.value.length > 0) {
+      for (const file of attachedFiles.value) {
+        const formDataFile = new FormData()
+        formDataFile.append('file', file)
+
+        const { data: uploadData, error: uploadError } = await upload('/upload/event-file', formDataFile)
+
+        if (uploadError) {
+          errorMessage.value = `파일 업로드에 실패했습니다: ${file.name}`
+          isSaving.value = false
+          return
+        }
+
+        if (!uploadData?.success || !uploadData?.data?.url) {
+          errorMessage.value = '파일 업로드 응답이 올바르지 않습니다.'
+          isSaving.value = false
+          return
+        }
+
+        fileUrls.push({
+          name: file.name,
+          url: uploadData.data.url,
+          size: file.size
+        })
+      }
+    }
+
+    // content에서 도메인 제거
+    let contentToSave = formData.value.content
+    if (contentToSave) {
+      // http://도메인 또는 https://도메인 제거
+      contentToSave = contentToSave.replace(/https?:\/\/[^\/]+/g, '')
+    }
+
+    const submitData = {
+      ...formData.value,
+      allow_comment: formData.value.allow_comment ? 1 : 0,
+      is_notice: formData.value.is_notice ? 1 : 0,
+      content: contentToSave,
+      file_urls: fileUrls
+    }
+
+    const { data, error } = await post('/board/event', submitData)
+
+    if (error) {
+      errorMessage.value = error.message || '등록에 실패했습니다.'
+    } else {
+      successMessage.value = '이벤트가 등록되었습니다.'
+      setTimeout(() => {
+        router.push('/site-manager/board/event')
+      }, 1000)
+    }
+  } catch (error) {
+    errorMessage.value = '서버 오류가 발생했습니다.'
+    console.error('Save error:', error)
+  } finally {
+    isSaving.value = false
+  }
+}
+
+const goToList = () => {
+  router.push('/site-manager/board/event')
+}
+</script>

+ 381 - 0
app/pages/site-manager/board/event/edit/[id].vue

@@ -0,0 +1,381 @@
+<template>
+  <div class="admin--event-form">
+    <div v-if="isLoading" class="admin--loading">
+      데이터를 불러오는 중...
+    </div>
+
+    <form v-else @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 사이트 선택 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">사이트 <span class="admin--required">*</span></label>
+        <select v-model="formData.site" class="admin--form-select" required>
+          <option value="common">공통</option>
+          <option value="ford">포드</option>
+          <option value="lincoln">링컨</option>
+        </select>
+      </div>
+
+      <!-- 카테고리 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">카테고리 <span class="admin--required">*</span></label>
+        <select v-model="formData.category" class="admin--form-select" required>
+          <option value="">카테고리를 선택하세요</option>
+          <option value="진행중">진행중</option>
+          <option value="완료">완료</option>
+        </select>
+      </div>
+
+      <!-- 댓글허용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">댓글허용</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.allow_comment" type="checkbox">
+            <span>댓글 허용</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 공지 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">공지</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.is_notice" type="checkbox">
+            <span>공지글로 등록</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 이름 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이름 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="이름을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 이메일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이메일 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.email"
+          type="email"
+          class="admin--form-input"
+          placeholder="이메일을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 기간 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">기간 <span class="admin--required">*</span></label>
+        <div class="admin--date-range">
+          <DatePicker
+            v-model="formData.start_date"
+            placeholder="시작일 선택"
+            :max-date="formData.end_date"
+            required
+          />
+          <span class="admin--date-separator">~</span>
+          <DatePicker
+            v-model="formData.end_date"
+            placeholder="종료일 선택"
+            :min-date="formData.start_date"
+            required
+          />
+        </div>
+      </div>
+
+      <!-- 제목 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">제목 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.title"
+          type="text"
+          class="admin--form-input"
+          placeholder="제목을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 내용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">내용 <span class="admin--required">*</span></label>
+        <SunEditor v-model="formData.content" />
+      </div>
+
+      <!-- 기존 첨부파일 -->
+      <div v-if="existingFiles.length > 0" class="admin--form-group">
+        <label class="admin--form-label">기존 첨부파일</label>
+        <div class="admin--file-list">
+          <div v-for="(file, index) in existingFiles" :key="'existing-' + index" class="admin--file-item">
+            <a :href="file.url" target="_blank" class="admin--file-name">{{ file.name }}</a>
+            <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
+            <button type="button" class="admin--btn-remove-file" @click="removeExistingFile(index)">
+              삭제
+            </button>
+          </div>
+        </div>
+      </div>
+
+      <!-- 새 파일첨부 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">파일첨부</label>
+        <div v-if="attachedFiles.length > 0" class="admin--file-list">
+          <div v-for="(file, index) in attachedFiles" :key="'new-' + index" class="admin--file-item">
+            <span class="admin--file-name">{{ file.name }}</span>
+            <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
+            <button type="button" class="admin--btn-remove-file" @click="removeFile(index)">
+              삭제
+            </button>
+          </div>
+        </div>
+        <input
+          ref="fileInput"
+          type="file"
+          multiple
+          class="admin--form-file-hidden"
+          @change="handleFileAdd"
+        >
+        <button type="button" class="admin--btn admin--btn-secondary" @click="triggerFileInput">
+          파일 추가
+        </button>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button
+          type="submit"
+          class="admin--btn admin--btn-primary"
+          :disabled="isSaving"
+        >
+          {{ isSaving ? '저장 중...' : '확인' }}
+        </button>
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="goToList"
+        >
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import SunEditor from '~/components/admin/SunEditor.vue'
+import DatePicker from '~/components/admin/DatePicker.vue'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const route = useRoute()
+const router = useRouter()
+const { get, put, upload } = useApi()
+const { getImageUrl } = useImage()
+
+const isLoading = ref(true)
+const isSaving = ref(false)
+const successMessage = ref('')
+const errorMessage = ref('')
+const attachedFiles = ref([])
+const existingFiles = ref([])
+const fileInput = ref(null)
+
+const formData = ref({
+  site: 'common',
+  category: '',
+  allow_comment: false,
+  is_notice: false,
+  name: '',
+  email: '',
+  start_date: '',
+  end_date: '',
+  title: '',
+  content: '',
+  file_urls: []
+})
+
+const loadEvent = async () => {
+  isLoading.value = true
+
+  const id = route.params.id
+  const { data, error } = await get(`/board/event/${id}`)
+  console.log('[EventEdit] 데이터 로드:', { data, error })
+
+  if (data?.success && data?.data) {
+    const event = data.data
+
+    // content 내 상대 경로 이미지를 절대 URL로 변환
+    let content = event.content || ''
+    if (content) {
+      // src="/uploads/..." 또는 src='/uploads/...' 패턴을 절대 URL로 변환
+      content = content.replace(/src=["'](?!https?:\/\/)([^"']+)["']/g, (match, path) => {
+        const fullUrl = getImageUrl(path)
+        return `src="${fullUrl}"`
+      })
+    }
+
+    formData.value = {
+      site: event.site || 'common',
+      category: event.category || '',
+      allow_comment: event.allow_comment === 1 || event.allow_comment === '1',
+      is_notice: event.is_notice === 1 || event.is_notice === '1',
+      name: event.name || '',
+      email: event.email || '',
+      start_date: event.start_date || '',
+      end_date: event.end_date || '',
+      title: event.title || '',
+      content: content,
+      file_urls: event.file_urls || []
+    }
+    existingFiles.value = event.file_urls || []
+    console.log('[EventEdit] 로드 성공')
+  }
+
+  isLoading.value = false
+}
+
+const triggerFileInput = () => {
+  fileInput.value?.click()
+}
+
+const handleFileAdd = (event) => {
+  const files = Array.from(event.target.files)
+  attachedFiles.value.push(...files)
+  event.target.value = ''
+}
+
+const removeFile = (index) => {
+  attachedFiles.value.splice(index, 1)
+}
+
+const removeExistingFile = (index) => {
+  existingFiles.value.splice(index, 1)
+}
+
+const formatFileSize = (bytes) => {
+  if (bytes === 0) return '0 Bytes'
+  const k = 1024
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
+}
+
+const handleSubmit = async () => {
+  successMessage.value = ''
+  errorMessage.value = ''
+
+  if (!formData.value.category) {
+    errorMessage.value = '카테고리를 선택하세요.'
+    return
+  }
+
+  if (!formData.value.title) {
+    errorMessage.value = '제목을 입력하세요.'
+    return
+  }
+
+  if (!formData.value.content) {
+    errorMessage.value = '내용을 입력하세요.'
+    return
+  }
+
+  if (!formData.value.start_date || !formData.value.end_date) {
+    errorMessage.value = '기간을 입력하세요.'
+    return
+  }
+
+  isSaving.value = true
+
+  try {
+    let fileUrls = [...existingFiles.value]
+
+    // 새 파일 업로드
+    if (attachedFiles.value.length > 0) {
+      for (const file of attachedFiles.value) {
+        const formDataFile = new FormData()
+        formDataFile.append('file', file)
+
+        const { data: uploadData, error: uploadError } = await upload('/upload/event-file', formDataFile)
+
+        if (uploadError) {
+          errorMessage.value = `파일 업로드에 실패했습니다: ${file.name}`
+          isSaving.value = false
+          return
+        }
+
+        if (!uploadData?.success || !uploadData?.data?.url) {
+          errorMessage.value = '파일 업로드 응답이 올바르지 않습니다.'
+          isSaving.value = false
+          return
+        }
+
+        fileUrls.push({
+          name: file.name,
+          url: uploadData.data.url,
+          size: file.size
+        })
+      }
+    }
+
+    // content에서 도메인 제거
+    let contentToSave = formData.value.content
+    if (contentToSave) {
+      // http://도메인 또는 https://도메인 제거
+      contentToSave = contentToSave.replace(/https?:\/\/[^\/]+/g, '')
+    }
+
+    const submitData = {
+      ...formData.value,
+      allow_comment: formData.value.allow_comment ? 1 : 0,
+      is_notice: formData.value.is_notice ? 1 : 0,
+      content: contentToSave,
+      file_urls: fileUrls
+    }
+
+    const id = route.params.id
+    const { data, error } = await put(`/board/event/${id}`, submitData)
+
+    if (error) {
+      errorMessage.value = error.message || '수정에 실패했습니다.'
+    } else {
+      successMessage.value = '이벤트가 수정되었습니다.'
+      setTimeout(() => {
+        router.push('/site-manager/board/event')
+      }, 1000)
+    }
+  } catch (error) {
+    errorMessage.value = '서버 오류가 발생했습니다.'
+    console.error('Save error:', error)
+  } finally {
+    isSaving.value = false
+  }
+}
+
+const goToList = () => {
+  router.push('/site-manager/board/event')
+}
+
+onMounted(() => {
+  loadEvent()
+})
+</script>

+ 221 - 0
app/pages/site-manager/board/event/index.vue

@@ -0,0 +1,221 @@
+<template>
+  <div class="admin--board-list">
+    <!-- 검색 영역 -->
+    <div class="admin--search-box">
+      <div class="admin--search-form">
+        <select v-model="searchType" class="admin--form-select admin--search-select">
+          <option value="title">제목</option>
+          <option value="name">이름</option>
+          <option value="content">내용</option>
+        </select>
+        <input
+          v-model="searchKeyword"
+          type="text"
+          class="admin--form-input admin--search-input"
+          placeholder="검색어를 입력하세요"
+          @keyup.enter="handleSearch"
+        />
+        <button class="admin--btn-small admin--btn-small-primary" @click="handleSearch">검색</button>
+        <button class="admin--btn-small admin--btn-small-secondary" @click="handleReset">
+          초기화
+        </button>
+      </div>
+      <div class="admin--search-actions">
+        <button class="admin--btn-small admin--btn-small-primary" @click="goToCreate">+ 등록</button>
+      </div>
+    </div>
+
+    <!-- 테이블 -->
+    <div class="admin--table-wrapper">
+      <table class="admin--table">
+        <thead>
+          <tr>
+            <th>NO</th>
+            <th>제목</th>
+            <th>이름</th>
+            <th>등록일</th>
+            <th>조회</th>
+            <th>관리</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="!posts || posts.length === 0">
+            <td colspan="6" class="admin--table-empty">등록된 게시물이 없습니다.</td>
+          </tr>
+          <tr v-else v-for="(post, index) in posts" :key="post.id">
+            <td>
+              {{
+                (post.is_notice === 1 || post.is_notice === '1')
+                  ? "공지"
+                  : totalCount - ((currentPage - 1) * perPage + index)
+              }}
+            </td>
+            <td class="admin--table-title">{{ post.title }}</td>
+            <td>{{ post.name }}</td>
+            <td>{{ formatDate(post.created_at) }}</td>
+            <td>{{ post.views }}</td>
+            <td>
+              <div class="admin--table-actions">
+                <button
+                  class="admin--btn-small admin--btn-small-primary"
+                  @click="goToEdit(post.id)"
+                >
+                  수정
+                </button>
+                <button
+                  class="admin--btn-small admin--btn-small-danger"
+                  @click="handleDelete(post.id)"
+                >
+                  삭제
+                </button>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <!-- 페이지네이션 -->
+    <div v-if="totalPages > 1" class="admin--pagination">
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(1)"
+        title="처음"
+      >
+        ⏮
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(currentPage - 1)"
+        title="이전"
+      >
+        ◀
+      </button>
+      <button
+        v-for="page in visiblePages"
+        :key="page"
+        class="admin--pagination-btn"
+        :class="{ 'is-active': page === currentPage }"
+        @click="changePage(page)"
+      >
+        {{ page }}
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(currentPage + 1)"
+        title="다음"
+      >
+        ▶
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(totalPages)"
+        title="끝"
+      >
+        ⏭
+      </button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref, computed, onMounted } from "vue";
+  import { useRouter } from "vue-router";
+
+  definePageMeta({ layout: "admin", middleware: ["auth"] });
+
+  const router = useRouter();
+  const { get, del } = useApi();
+  const posts = ref([]);
+  const searchType = ref("title");
+  const searchKeyword = ref("");
+  const currentPage = ref(1);
+  const perPage = ref(10);
+  const totalCount = ref(0);
+  const totalPages = ref(0);
+
+  const visiblePages = computed(() => {
+    const pages = [];
+    const maxVisible = 5;
+    let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
+    let end = Math.min(totalPages.value, start + maxVisible - 1);
+    if (end - start < maxVisible - 1) start = Math.max(1, end - maxVisible + 1);
+    for (let i = start; i <= end; i++) pages.push(i);
+    return pages;
+  });
+
+  const loadPosts = async () => {
+    const params = { page: currentPage.value, per_page: perPage.value };
+    if (searchKeyword.value) {
+      params.search_type = searchType.value;
+      params.search_keyword = searchKeyword.value;
+    }
+    const { data, error } = await get("/board/event", { params });
+
+    console.log("[EventBoard] API 응답:", { data, error });
+
+    // API 응답: { success: true, data: { items, total }, message }
+    if (data?.success && data?.data) {
+      posts.value = data.data.items || [];
+      totalCount.value = data.data.total || 0;
+      totalPages.value = Math.ceil(totalCount.value / perPage.value);
+      console.log("[EventBoard] 로드 성공:", posts.value.length);
+    }
+  };
+
+  const handleSearch = () => {
+    currentPage.value = 1;
+    loadPosts();
+  };
+  const handleReset = () => {
+    searchType.value = "title";
+    searchKeyword.value = "";
+    currentPage.value = 1;
+    loadPosts();
+  };
+  const changePage = (page) => {
+    if (page < 1 || page > totalPages.value) return;
+    currentPage.value = page;
+    loadPosts();
+    window.scrollTo({ top: 0, behavior: 'smooth' });
+  };
+  const goToCreate = () => router.push("/site-manager/board/event/create");
+  const goToEdit = (id) => router.push(`/site-manager/board/event/edit/${id}`);
+  const handleDelete = async (id) => {
+    if (!confirm("정말 삭제하시겠습니까?")) return;
+    const { error } = await del(`/board/event/${id}`);
+    if (error) alert("삭제에 실패했습니다.");
+    else {
+      alert("삭제되었습니다.");
+      loadPosts();
+    }
+  };
+  const formatDate = (dateString) => {
+    if (!dateString) return "-";
+    const date = new Date(dateString);
+    return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(
+      2,
+      "0"
+    )}.${String(date.getDate()).padStart(2, "0")}`;
+  };
+
+  onMounted(() => loadPosts());
+</script>
+
+<style scoped>
+  /* 검색 영역 input/select 스타일 통일 */
+  .admin--search-box .admin--form-input,
+  .admin--search-box .admin--search-input,
+  .admin--search-box .admin--form-select,
+  .admin--search-box .admin--search-select {
+    border: 1px solid var(--admin-border-color) !important;
+    border-radius: 4px;
+    height: 33px !important;
+    padding: 6px 14px !important;
+    font-size: 13px !important;
+  }
+</style>

+ 251 - 0
app/pages/site-manager/board/ir/create.vue

@@ -0,0 +1,251 @@
+<template>
+  <div class="admin--ir-form">
+    <form @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 댓글허용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">댓글허용</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.allow_comment" type="checkbox">
+            <span>댓글 허용</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 공지 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">공지</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.is_notice" type="checkbox">
+            <span>공지글로 등록</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 이름 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이름 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="이름을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 이메일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이메일 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.email"
+          type="email"
+          class="admin--form-input"
+          placeholder="이메일을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- URL -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">URL</label>
+        <input
+          v-model="formData.url"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com"
+        >
+      </div>
+
+      <!-- 제목 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">제목 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.title"
+          type="text"
+          class="admin--form-input"
+          placeholder="제목을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 내용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">내용 <span class="admin--required">*</span></label>
+        <SunEditor v-model="formData.content" />
+      </div>
+
+      <!-- 파일첨부 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">파일첨부</label>
+        <div class="admin--file-list">
+          <div v-for="(file, index) in attachedFiles" :key="index" class="admin--file-item">
+            <span class="admin--file-name">{{ file.name }}</span>
+            <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
+            <button type="button" class="admin--btn-remove-file" @click="removeFile(index)">
+              삭제
+            </button>
+          </div>
+        </div>
+        <input
+          ref="fileInput"
+          type="file"
+          multiple
+          class="admin--form-file-hidden"
+          @change="handleFileAdd"
+        >
+        <button type="button" class="admin--btn admin--btn-secondary" @click="triggerFileInput">
+          파일 추가
+        </button>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button
+          type="submit"
+          class="admin--btn admin--btn-primary"
+          :disabled="isSaving"
+        >
+          {{ isSaving ? '저장 중...' : '확인' }}
+        </button>
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="goToList"
+        >
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+import SunEditor from '~/components/admin/SunEditor.vue'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const router = useRouter()
+const { post, upload } = useApi()
+
+const isSaving = ref(false)
+const successMessage = ref('')
+const errorMessage = ref('')
+const attachedFiles = ref([])
+const fileInput = ref(null)
+
+const formData = ref({
+  allow_comment: false,
+  is_notice: false,
+  name: '고진',
+  email: 'admin@admin.kr',
+  url: '',
+  title: '',
+  content: '',
+  file_urls: []
+})
+
+const triggerFileInput = () => {
+  fileInput.value?.click()
+}
+
+const handleFileAdd = (event) => {
+  const files = Array.from(event.target.files)
+  attachedFiles.value.push(...files)
+  event.target.value = ''
+}
+
+const removeFile = (index) => {
+  attachedFiles.value.splice(index, 1)
+}
+
+const formatFileSize = (bytes) => {
+  if (bytes === 0) return '0 Bytes'
+  const k = 1024
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
+}
+
+const handleSubmit = async () => {
+  successMessage.value = ''
+  errorMessage.value = ''
+
+  if (!formData.value.title) {
+    errorMessage.value = '제목을 입력하세요.'
+    return
+  }
+
+  if (!formData.value.content) {
+    errorMessage.value = '내용을 입력하세요.'
+    return
+  }
+
+  isSaving.value = true
+
+  try {
+    let fileUrls = []
+
+    // 파일 업로드
+    if (attachedFiles.value.length > 0) {
+      for (const file of attachedFiles.value) {
+        const formDataFile = new FormData()
+        formDataFile.append('file', file)
+
+        const { data: uploadData, error: uploadError } = await upload('/upload/file', formDataFile)
+
+        if (uploadError) {
+          errorMessage.value = `파일 업로드에 실패했습니다: ${file.name}`
+          isSaving.value = false
+          return
+        }
+
+        fileUrls.push({
+          name: file.name,
+          url: uploadData.url,
+          size: file.size
+        })
+      }
+    }
+
+    const submitData = {
+      ...formData.value,
+      file_urls: fileUrls
+    }
+
+    const { data, error } = await post('/board/ir', submitData)
+
+    if (error) {
+      errorMessage.value = error.message || '등록에 실패했습니다.'
+    } else {
+      successMessage.value = 'IR 자료가 등록되었습니다.'
+      setTimeout(() => {
+        router.push('/site-manager/board/ir')
+      }, 1000)
+    }
+  } catch (error) {
+    errorMessage.value = '서버 오류가 발생했습니다.'
+    console.error('Save error:', error)
+  } finally {
+    isSaving.value = false
+  }
+}
+
+const goToList = () => {
+  router.push('/site-manager/board/ir')
+}
+</script>

+ 307 - 0
app/pages/site-manager/board/ir/edit/[id].vue

@@ -0,0 +1,307 @@
+<template>
+  <div class="admin--ir-form">
+    <div v-if="isLoading" class="admin--loading">
+      데이터를 불러오는 중...
+    </div>
+
+    <form v-else @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 댓글허용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">댓글허용</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.allow_comment" type="checkbox">
+            <span>댓글 허용</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 공지 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">공지</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.is_notice" type="checkbox">
+            <span>공지글로 등록</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 이름 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이름 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="이름을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 이메일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이메일 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.email"
+          type="email"
+          class="admin--form-input"
+          placeholder="이메일을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- URL -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">URL</label>
+        <input
+          v-model="formData.url"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com"
+        >
+      </div>
+
+      <!-- 제목 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">제목 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.title"
+          type="text"
+          class="admin--form-input"
+          placeholder="제목을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 내용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">내용 <span class="admin--required">*</span></label>
+        <SunEditor v-model="formData.content" />
+      </div>
+
+      <!-- 기존 첨부파일 -->
+      <div v-if="existingFiles.length > 0" class="admin--form-group">
+        <label class="admin--form-label">기존 첨부파일</label>
+        <div class="admin--file-list">
+          <div v-for="(file, index) in existingFiles" :key="'existing-' + index" class="admin--file-item">
+            <a :href="file.url" target="_blank" class="admin--file-name">{{ file.name }}</a>
+            <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
+            <button type="button" class="admin--btn-remove-file" @click="removeExistingFile(index)">
+              삭제
+            </button>
+          </div>
+        </div>
+      </div>
+
+      <!-- 새 파일첨부 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">파일첨부</label>
+        <div v-if="attachedFiles.length > 0" class="admin--file-list">
+          <div v-for="(file, index) in attachedFiles" :key="'new-' + index" class="admin--file-item">
+            <span class="admin--file-name">{{ file.name }}</span>
+            <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
+            <button type="button" class="admin--btn-remove-file" @click="removeFile(index)">
+              삭제
+            </button>
+          </div>
+        </div>
+        <input
+          ref="fileInput"
+          type="file"
+          multiple
+          class="admin--form-file-hidden"
+          @change="handleFileAdd"
+        >
+        <button type="button" class="admin--btn admin--btn-secondary" @click="triggerFileInput">
+          파일 추가
+        </button>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button
+          type="submit"
+          class="admin--btn admin--btn-primary"
+          :disabled="isSaving"
+        >
+          {{ isSaving ? '저장 중...' : '확인' }}
+        </button>
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="goToList"
+        >
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import SunEditor from '~/components/admin/SunEditor.vue'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const route = useRoute()
+const router = useRouter()
+const { get, put, upload } = useApi()
+
+const isLoading = ref(true)
+const isSaving = ref(false)
+const successMessage = ref('')
+const errorMessage = ref('')
+const attachedFiles = ref([])
+const existingFiles = ref([])
+const fileInput = ref(null)
+
+const formData = ref({
+  allow_comment: false,
+  is_notice: false,
+  name: '',
+  email: '',
+  url: '',
+  title: '',
+  content: '',
+  file_urls: []
+})
+
+const loadIR = async () => {
+  isLoading.value = true
+
+  const id = route.params.id
+  const { data, error } = await get(`/board/ir/${id}`)
+  console.log('[IREdit] 데이터 로드:', { data, error })
+
+  if (data?.success && data?.data) {
+    const ir = data.data
+    formData.value = {
+      allow_comment: ir.allow_comment || false,
+      is_notice: ir.is_notice || false,
+      name: ir.name || '',
+      email: ir.email || '',
+      url: ir.url || '',
+      title: ir.title || '',
+      content: ir.content || '',
+      file_urls: ir.file_urls || []
+    }
+    existingFiles.value = ir.file_urls || []
+    console.log('[IREdit] 로드 성공')
+  }
+
+  isLoading.value = false
+}
+
+const triggerFileInput = () => {
+  fileInput.value?.click()
+}
+
+const handleFileAdd = (event) => {
+  const files = Array.from(event.target.files)
+  attachedFiles.value.push(...files)
+  event.target.value = ''
+}
+
+const removeFile = (index) => {
+  attachedFiles.value.splice(index, 1)
+}
+
+const removeExistingFile = (index) => {
+  existingFiles.value.splice(index, 1)
+}
+
+const formatFileSize = (bytes) => {
+  if (bytes === 0) return '0 Bytes'
+  const k = 1024
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
+}
+
+const handleSubmit = async () => {
+  successMessage.value = ''
+  errorMessage.value = ''
+
+  if (!formData.value.title) {
+    errorMessage.value = '제목을 입력하세요.'
+    return
+  }
+
+  if (!formData.value.content) {
+    errorMessage.value = '내용을 입력하세요.'
+    return
+  }
+
+  isSaving.value = true
+
+  try {
+    let fileUrls = [...existingFiles.value]
+
+    // 새 파일 업로드
+    if (attachedFiles.value.length > 0) {
+      for (const file of attachedFiles.value) {
+        const formDataFile = new FormData()
+        formDataFile.append('file', file)
+
+        const { data: uploadData, error: uploadError } = await upload('/upload/file', formDataFile)
+
+        if (uploadError) {
+          errorMessage.value = `파일 업로드에 실패했습니다: ${file.name}`
+          isSaving.value = false
+          return
+        }
+
+        fileUrls.push({
+          name: file.name,
+          url: uploadData.url,
+          size: file.size
+        })
+      }
+    }
+
+    const submitData = {
+      ...formData.value,
+      file_urls: fileUrls
+    }
+
+    const id = route.params.id
+    const { data, error } = await put(`/board/ir/${id}`, submitData)
+
+    if (error) {
+      errorMessage.value = error.message || '수정에 실패했습니다.'
+    } else {
+      successMessage.value = 'IR 자료가 수정되었습니다.'
+      setTimeout(() => {
+        router.push('/site-manager/board/ir')
+      }, 1000)
+    }
+  } catch (error) {
+    errorMessage.value = '서버 오류가 발생했습니다.'
+    console.error('Save error:', error)
+  } finally {
+    isSaving.value = false
+  }
+}
+
+const goToList = () => {
+  router.push('/site-manager/board/ir')
+}
+
+onMounted(() => {
+  loadIR()
+})
+</script>

+ 221 - 0
app/pages/site-manager/board/ir/index.vue

@@ -0,0 +1,221 @@
+<template>
+  <div class="admin--board-list">
+    <!-- 검색 영역 -->
+    <div class="admin--search-box">
+      <div class="admin--search-form">
+        <select v-model="searchType" class="admin--form-select admin--search-select">
+          <option value="title">제목</option>
+          <option value="name">이름</option>
+          <option value="content">내용</option>
+        </select>
+        <input
+          v-model="searchKeyword"
+          type="text"
+          class="admin--form-input admin--search-input"
+          placeholder="검색어를 입력하세요"
+          @keyup.enter="handleSearch"
+        />
+        <button class="admin--btn-small admin--btn-small-primary" @click="handleSearch">검색</button>
+        <button class="admin--btn-small admin--btn-small-secondary" @click="handleReset">
+          초기화
+        </button>
+      </div>
+      <div class="admin--search-actions">
+        <button class="admin--btn-small admin--btn-small-primary" @click="goToCreate">+ 등록</button>
+      </div>
+    </div>
+
+    <!-- 테이블 -->
+    <div class="admin--table-wrapper">
+      <table class="admin--table">
+        <thead>
+          <tr>
+            <th>NO</th>
+            <th>제목</th>
+            <th>이름</th>
+            <th>등록일</th>
+            <th>조회</th>
+            <th>관리</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="!posts || posts.length === 0">
+            <td colspan="6" class="admin--table-empty">등록된 게시물이 없습니다.</td>
+          </tr>
+          <tr v-else v-for="(post, index) in posts" :key="post.id">
+            <td>
+              {{
+                post.is_notice
+                  ? "공지"
+                  : totalCount - ((currentPage - 1) * perPage + index)
+              }}
+            </td>
+            <td class="admin--table-title">{{ post.title }}</td>
+            <td>{{ post.name }}</td>
+            <td>{{ formatDate(post.created_at) }}</td>
+            <td>{{ post.views }}</td>
+            <td>
+              <div class="admin--table-actions">
+                <button
+                  class="admin--btn-small admin--btn-small-primary"
+                  @click="goToEdit(post.id)"
+                >
+                  수정
+                </button>
+                <button
+                  class="admin--btn-small admin--btn-small-danger"
+                  @click="handleDelete(post.id)"
+                >
+                  삭제
+                </button>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <!-- 페이지네이션 -->
+    <div v-if="totalPages > 1" class="admin--pagination">
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(1)"
+        title="처음"
+      >
+        ⏮
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(currentPage - 1)"
+        title="이전"
+      >
+        ◀
+      </button>
+      <button
+        v-for="page in visiblePages"
+        :key="page"
+        class="admin--pagination-btn"
+        :class="{ 'is-active': page === currentPage }"
+        @click="changePage(page)"
+      >
+        {{ page }}
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(currentPage + 1)"
+        title="다음"
+      >
+        ▶
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(totalPages)"
+        title="끝"
+      >
+        ⏭
+      </button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref, computed, onMounted } from "vue";
+  import { useRouter } from "vue-router";
+
+  definePageMeta({ layout: "admin", middleware: ["auth"] });
+
+  const router = useRouter();
+  const { get, del } = useApi();
+  const posts = ref([]);
+  const searchType = ref("title");
+  const searchKeyword = ref("");
+  const currentPage = ref(1);
+  const perPage = ref(10);
+  const totalCount = ref(0);
+  const totalPages = ref(0);
+
+  const visiblePages = computed(() => {
+    const pages = [];
+    const maxVisible = 5;
+    let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
+    let end = Math.min(totalPages.value, start + maxVisible - 1);
+    if (end - start < maxVisible - 1) start = Math.max(1, end - maxVisible + 1);
+    for (let i = start; i <= end; i++) pages.push(i);
+    return pages;
+  });
+
+  const loadPosts = async () => {
+    const params = { page: currentPage.value, per_page: perPage.value };
+    if (searchKeyword.value) {
+      params.search_type = searchType.value;
+      params.search_keyword = searchKeyword.value;
+    }
+    const { data, error } = await get("/board/ir", { params });
+
+    console.log("[IRBoard] API 응답:", { data, error });
+
+    // API 응답: { success: true, data: { items, total }, message }
+    if (data?.success && data?.data) {
+      posts.value = data.data.items || [];
+      totalCount.value = data.data.total || 0;
+      totalPages.value = Math.ceil(totalCount.value / perPage.value);
+      console.log("[IRBoard] 로드 성공:", posts.value.length);
+    }
+  };
+
+  const handleSearch = () => {
+    currentPage.value = 1;
+    loadPosts();
+  };
+  const handleReset = () => {
+    searchType.value = "title";
+    searchKeyword.value = "";
+    currentPage.value = 1;
+    loadPosts();
+  };
+  const changePage = (page) => {
+    if (page < 1 || page > totalPages.value) return;
+    currentPage.value = page;
+    loadPosts();
+    window.scrollTo({ top: 0, behavior: 'smooth' });
+  };
+  const goToCreate = () => router.push("/site-manager/board/ir/create");
+  const goToEdit = (id) => router.push(`/site-manager/board/ir/edit/${id}`);
+  const handleDelete = async (id) => {
+    if (!confirm("정말 삭제하시겠습니까?")) return;
+    const { error } = await del(`/board/ir/${id}`);
+    if (error) alert("삭제에 실패했습니다.");
+    else {
+      alert("삭제되었습니다.");
+      loadPosts();
+    }
+  };
+  const formatDate = (dateString) => {
+    if (!dateString) return "-";
+    const date = new Date(dateString);
+    return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(
+      2,
+      "0"
+    )}.${String(date.getDate()).padStart(2, "0")}`;
+  };
+
+  onMounted(() => loadPosts());
+</script>
+
+<style scoped>
+  /* 검색 영역 input/select 스타일 통일 */
+  .admin--search-box .admin--form-input,
+  .admin--search-box .admin--search-input,
+  .admin--search-box .admin--form-select,
+  .admin--search-box .admin--search-select {
+    border: 1px solid var(--admin-border-color) !important;
+    border-radius: 4px;
+    height: 33px !important;
+    padding: 6px 14px !important;
+    font-size: 13px !important;
+  }
+</style>

+ 261 - 0
app/pages/site-manager/board/news/create.vue

@@ -0,0 +1,261 @@
+<template>
+  <div class="admin--news-form">
+    <form @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 댓글허용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">댓글허용</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.allow_comment" type="checkbox">
+            <span>댓글 허용</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 공지 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">공지</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.is_notice" type="checkbox">
+            <span>공지글로 등록</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 이름 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이름 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="이름을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 이메일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이메일 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.email"
+          type="email"
+          class="admin--form-input"
+          placeholder="이메일을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- URL -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">URL</label>
+        <input
+          v-model="formData.url"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com"
+        >
+      </div>
+
+      <!-- 제목 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">제목 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.title"
+          type="text"
+          class="admin--form-input"
+          placeholder="제목을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 내용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">내용 <span class="admin--required">*</span></label>
+        <SunEditor v-model="formData.content" />
+      </div>
+
+      <!-- 파일첨부 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">파일첨부</label>
+        <div class="admin--file-list">
+          <div v-for="(file, index) in attachedFiles" :key="index" class="admin--file-item">
+            <span class="admin--file-name">{{ file.name }}</span>
+            <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
+            <button type="button" class="admin--btn-remove-file" @click="removeFile(index)">
+              삭제
+            </button>
+          </div>
+        </div>
+        <input
+          ref="fileInput"
+          type="file"
+          multiple
+          class="admin--form-file-hidden"
+          @change="handleFileAdd"
+        >
+        <button type="button" class="admin--btn admin--btn-secondary" @click="triggerFileInput">
+          파일 추가
+        </button>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button
+          type="submit"
+          class="admin--btn admin--btn-primary"
+          :disabled="isSaving"
+        >
+          {{ isSaving ? '저장 중...' : '확인' }}
+        </button>
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="goToList"
+        >
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+import SunEditor from '~/components/admin/SunEditor.vue'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const router = useRouter()
+const { post, upload } = useApi()
+
+const isSaving = ref(false)
+const successMessage = ref('')
+const errorMessage = ref('')
+const attachedFiles = ref([])
+const fileInput = ref(null)
+
+const formData = ref({
+  allow_comment: false,
+  is_notice: false,
+  name: '고진',
+  email: 'admin@admin.kr',
+  url: '',
+  title: '',
+  content: '',
+  file_urls: []
+})
+
+const triggerFileInput = () => {
+  fileInput.value?.click()
+}
+
+const handleFileAdd = (event) => {
+  const files = Array.from(event.target.files)
+  attachedFiles.value.push(...files)
+  event.target.value = ''
+}
+
+const removeFile = (index) => {
+  attachedFiles.value.splice(index, 1)
+}
+
+const formatFileSize = (bytes) => {
+  if (bytes === 0) return '0 Bytes'
+  const k = 1024
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
+}
+
+const handleSubmit = async () => {
+  successMessage.value = ''
+  errorMessage.value = ''
+
+  if (!formData.value.title) {
+    errorMessage.value = '제목을 입력하세요.'
+    return
+  }
+
+  if (!formData.value.content) {
+    errorMessage.value = '내용을 입력하세요.'
+    return
+  }
+
+  isSaving.value = true
+
+  try {
+    let fileUrls = []
+
+    // 파일 업로드
+    if (attachedFiles.value.length > 0) {
+      for (const file of attachedFiles.value) {
+        const formDataFile = new FormData()
+        formDataFile.append('file', file)
+
+        const { data: uploadData, error: uploadError } = await upload('/upload/news-file', formDataFile)
+
+        if (uploadError) {
+          errorMessage.value = `파일 업로드에 실패했습니다: ${file.name}`
+          isSaving.value = false
+          return
+        }
+
+        fileUrls.push({
+          name: file.name,
+          url: uploadData.data.url,
+          size: file.size
+        })
+      }
+    }
+
+    // content에서 도메인 제거
+    let contentToSave = formData.value.content
+    if (contentToSave) {
+      // http://도메인 또는 https://도메인 제거
+      contentToSave = contentToSave.replace(/https?:\/\/[^\/]+/g, '')
+    }
+
+    const submitData = {
+      ...formData.value,
+      allow_comment: formData.value.allow_comment ? 1 : 0,
+      is_notice: formData.value.is_notice ? 1 : 0,
+      content: contentToSave,
+      file_urls: fileUrls
+    }
+
+    const { data, error } = await post('/board/news', submitData)
+
+    if (error) {
+      errorMessage.value = error.message || '등록에 실패했습니다.'
+    } else {
+      successMessage.value = '뉴스가 등록되었습니다.'
+      setTimeout(() => {
+        router.push('/site-manager/board/news')
+      }, 1000)
+    }
+  } catch (error) {
+    errorMessage.value = '서버 오류가 발생했습니다.'
+    console.error('Save error:', error)
+  } finally {
+    isSaving.value = false
+  }
+}
+
+const goToList = () => {
+  router.push('/site-manager/board/news')
+}
+</script>

+ 317 - 0
app/pages/site-manager/board/news/edit/[id].vue

@@ -0,0 +1,317 @@
+<template>
+  <div class="admin--news-form">
+    <div v-if="isLoading" class="admin--loading">
+      데이터를 불러오는 중...
+    </div>
+
+    <form v-else @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 댓글허용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">댓글허용</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.allow_comment" type="checkbox">
+            <span>댓글 허용</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 공지 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">공지</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.is_notice" type="checkbox">
+            <span>공지글로 등록</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 이름 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이름 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="이름을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 이메일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이메일 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.email"
+          type="email"
+          class="admin--form-input"
+          placeholder="이메일을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- URL -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">URL</label>
+        <input
+          v-model="formData.url"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com"
+        >
+      </div>
+
+      <!-- 제목 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">제목 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.title"
+          type="text"
+          class="admin--form-input"
+          placeholder="제목을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 내용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">내용 <span class="admin--required">*</span></label>
+        <SunEditor v-model="formData.content" />
+      </div>
+
+      <!-- 기존 첨부파일 -->
+      <div v-if="existingFiles.length > 0" class="admin--form-group">
+        <label class="admin--form-label">기존 첨부파일</label>
+        <div class="admin--file-list">
+          <div v-for="(file, index) in existingFiles" :key="'existing-' + index" class="admin--file-item">
+            <a :href="file.url" target="_blank" class="admin--file-name">{{ file.name }}</a>
+            <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
+            <button type="button" class="admin--btn-remove-file" @click="removeExistingFile(index)">
+              삭제
+            </button>
+          </div>
+        </div>
+      </div>
+
+      <!-- 새 파일첨부 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">파일첨부</label>
+        <div v-if="attachedFiles.length > 0" class="admin--file-list">
+          <div v-for="(file, index) in attachedFiles" :key="'new-' + index" class="admin--file-item">
+            <span class="admin--file-name">{{ file.name }}</span>
+            <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
+            <button type="button" class="admin--btn-remove-file" @click="removeFile(index)">
+              삭제
+            </button>
+          </div>
+        </div>
+        <input
+          ref="fileInput"
+          type="file"
+          multiple
+          class="admin--form-file-hidden"
+          @change="handleFileAdd"
+        >
+        <button type="button" class="admin--btn admin--btn-secondary" @click="triggerFileInput">
+          파일 추가
+        </button>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button
+          type="submit"
+          class="admin--btn admin--btn-primary"
+          :disabled="isSaving"
+        >
+          {{ isSaving ? '저장 중...' : '확인' }}
+        </button>
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="goToList"
+        >
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import SunEditor from '~/components/admin/SunEditor.vue'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const route = useRoute()
+const router = useRouter()
+const { get, put, upload } = useApi()
+
+const isLoading = ref(true)
+const isSaving = ref(false)
+const successMessage = ref('')
+const errorMessage = ref('')
+const attachedFiles = ref([])
+const existingFiles = ref([])
+const fileInput = ref(null)
+
+const formData = ref({
+  allow_comment: false,
+  is_notice: false,
+  name: '',
+  email: '',
+  url: '',
+  title: '',
+  content: '',
+  file_urls: []
+})
+
+const loadNews = async () => {
+  isLoading.value = true
+
+  const id = route.params.id
+  const { data, error } = await get(`/board/news/${id}`)
+  console.log('[NewsEdit] 데이터 로드:', { data, error })
+
+  if (data?.success && data?.data) {
+    const news = data.data
+    formData.value = {
+      allow_comment: news.allow_comment === 1 || news.allow_comment === '1',
+      is_notice: news.is_notice === 1 || news.is_notice === '1',
+      name: news.name || '',
+      email: news.email || '',
+      url: news.url || '',
+      title: news.title || '',
+      content: news.content || '',
+      file_urls: news.file_urls || []
+    }
+    existingFiles.value = news.file_urls || []
+    console.log('[NewsEdit] 로드 성공')
+  }
+
+  isLoading.value = false
+}
+
+const triggerFileInput = () => {
+  fileInput.value?.click()
+}
+
+const handleFileAdd = (event) => {
+  const files = Array.from(event.target.files)
+  attachedFiles.value.push(...files)
+  event.target.value = ''
+}
+
+const removeFile = (index) => {
+  attachedFiles.value.splice(index, 1)
+}
+
+const removeExistingFile = (index) => {
+  existingFiles.value.splice(index, 1)
+}
+
+const formatFileSize = (bytes) => {
+  if (bytes === 0) return '0 Bytes'
+  const k = 1024
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
+}
+
+const handleSubmit = async () => {
+  successMessage.value = ''
+  errorMessage.value = ''
+
+  if (!formData.value.title) {
+    errorMessage.value = '제목을 입력하세요.'
+    return
+  }
+
+  if (!formData.value.content) {
+    errorMessage.value = '내용을 입력하세요.'
+    return
+  }
+
+  isSaving.value = true
+
+  try {
+    let fileUrls = [...existingFiles.value]
+
+    // 새 파일 업로드
+    if (attachedFiles.value.length > 0) {
+      for (const file of attachedFiles.value) {
+        const formDataFile = new FormData()
+        formDataFile.append('file', file)
+
+        const { data: uploadData, error: uploadError } = await upload('/upload/news-file', formDataFile)
+
+        if (uploadError) {
+          errorMessage.value = `파일 업로드에 실패했습니다: ${file.name}`
+          isSaving.value = false
+          return
+        }
+
+        fileUrls.push({
+          name: file.name,
+          url: uploadData.data.url,
+          size: file.size
+        })
+      }
+    }
+
+    // content에서 도메인 제거
+    let contentToSave = formData.value.content
+    if (contentToSave) {
+      // http://도메인 또는 https://도메인 제거
+      contentToSave = contentToSave.replace(/https?:\/\/[^\/]+/g, '')
+    }
+
+    const submitData = {
+      ...formData.value,
+      allow_comment: formData.value.allow_comment ? 1 : 0,
+      is_notice: formData.value.is_notice ? 1 : 0,
+      content: contentToSave,
+      file_urls: fileUrls
+    }
+
+    const id = route.params.id
+    const { data, error } = await put(`/board/news/${id}`, submitData)
+
+    if (error) {
+      errorMessage.value = error.message || '수정에 실패했습니다.'
+    } else {
+      successMessage.value = '뉴스가 수정되었습니다.'
+      setTimeout(() => {
+        router.push('/site-manager/board/news')
+      }, 1000)
+    }
+  } catch (error) {
+    errorMessage.value = '서버 오류가 발생했습니다.'
+    console.error('Save error:', error)
+  } finally {
+    isSaving.value = false
+  }
+}
+
+const goToList = () => {
+  router.push('/site-manager/board/news')
+}
+
+onMounted(() => {
+  loadNews()
+})
+</script>

+ 221 - 0
app/pages/site-manager/board/news/index.vue

@@ -0,0 +1,221 @@
+<template>
+  <div class="admin--board-list">
+    <!-- 검색 영역 -->
+    <div class="admin--search-box">
+      <div class="admin--search-form">
+        <select v-model="searchType" class="admin--form-select admin--search-select">
+          <option value="title">제목</option>
+          <option value="name">이름</option>
+          <option value="content">내용</option>
+        </select>
+        <input
+          v-model="searchKeyword"
+          type="text"
+          class="admin--form-input admin--search-input"
+          placeholder="검색어를 입력하세요"
+          @keyup.enter="handleSearch"
+        />
+        <button class="admin--btn-small admin--btn-small-primary" @click="handleSearch">검색</button>
+        <button class="admin--btn-small admin--btn-small-secondary" @click="handleReset">
+          초기화
+        </button>
+      </div>
+      <div class="admin--search-actions">
+        <button class="admin--btn-small admin--btn-small-primary" @click="goToCreate">+ 등록</button>
+      </div>
+    </div>
+
+    <!-- 테이블 -->
+    <div class="admin--table-wrapper">
+      <table class="admin--table">
+        <thead>
+          <tr>
+            <th>NO</th>
+            <th>제목</th>
+            <th>이름</th>
+            <th>등록일</th>
+            <th>조회</th>
+            <th>관리</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="!posts || posts.length === 0">
+            <td colspan="6" class="admin--table-empty">등록된 게시물이 없습니다.</td>
+          </tr>
+          <tr v-else v-for="(post, index) in posts" :key="post.id">
+            <td>
+              {{
+                (post.is_notice === 1 || post.is_notice === '1')
+                  ? "공지"
+                  : totalCount - ((currentPage - 1) * perPage + index)
+              }}
+            </td>
+            <td class="admin--table-title">{{ post.title }}</td>
+            <td>{{ post.name }}</td>
+            <td>{{ formatDate(post.created_at) }}</td>
+            <td>{{ post.views }}</td>
+            <td>
+              <div class="admin--table-actions">
+                <button
+                  class="admin--btn-small admin--btn-small-primary"
+                  @click="goToEdit(post.id)"
+                >
+                  수정
+                </button>
+                <button
+                  class="admin--btn-small admin--btn-small-danger"
+                  @click="handleDelete(post.id)"
+                >
+                  삭제
+                </button>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <!-- 페이지네이션 -->
+    <div v-if="totalPages > 1" class="admin--pagination">
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(1)"
+        title="처음"
+      >
+        ⏮
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(currentPage - 1)"
+        title="이전"
+      >
+        ◀
+      </button>
+      <button
+        v-for="page in visiblePages"
+        :key="page"
+        class="admin--pagination-btn"
+        :class="{ 'is-active': page === currentPage }"
+        @click="changePage(page)"
+      >
+        {{ page }}
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(currentPage + 1)"
+        title="다음"
+      >
+        ▶
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(totalPages)"
+        title="끝"
+      >
+        ⏭
+      </button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref, computed, onMounted } from "vue";
+  import { useRouter } from "vue-router";
+
+  definePageMeta({ layout: "admin", middleware: ["auth"] });
+
+  const router = useRouter();
+  const { get, del } = useApi();
+  const posts = ref([]);
+  const searchType = ref("title");
+  const searchKeyword = ref("");
+  const currentPage = ref(1);
+  const perPage = ref(10);
+  const totalCount = ref(0);
+  const totalPages = ref(0);
+
+  const visiblePages = computed(() => {
+    const pages = [];
+    const maxVisible = 5;
+    let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
+    let end = Math.min(totalPages.value, start + maxVisible - 1);
+    if (end - start < maxVisible - 1) start = Math.max(1, end - maxVisible + 1);
+    for (let i = start; i <= end; i++) pages.push(i);
+    return pages;
+  });
+
+  const loadPosts = async () => {
+    const params = { page: currentPage.value, per_page: perPage.value };
+    if (searchKeyword.value) {
+      params.search_type = searchType.value;
+      params.search_keyword = searchKeyword.value;
+    }
+    const { data, error } = await get("/board/news", { params });
+
+    console.log("[NewsBoard] API 응답:", { data, error });
+
+    // API 응답: { success: true, data: { items, total }, message }
+    if (data?.success && data?.data) {
+      posts.value = data.data.items || [];
+      totalCount.value = data.data.total || 0;
+      totalPages.value = Math.ceil(totalCount.value / perPage.value);
+      console.log("[NewsBoard] 로드 성공:", posts.value.length);
+    }
+  };
+
+  const handleSearch = () => {
+    currentPage.value = 1;
+    loadPosts();
+  };
+  const handleReset = () => {
+    searchType.value = "title";
+    searchKeyword.value = "";
+    currentPage.value = 1;
+    loadPosts();
+  };
+  const changePage = (page) => {
+    if (page < 1 || page > totalPages.value) return;
+    currentPage.value = page;
+    loadPosts();
+    window.scrollTo({ top: 0, behavior: 'smooth' });
+  };
+  const goToCreate = () => router.push("/site-manager/board/news/create");
+  const goToEdit = (id) => router.push(`/site-manager/board/news/edit/${id}`);
+  const handleDelete = async (id) => {
+    if (!confirm("정말 삭제하시겠습니까?")) return;
+    const { error } = await del(`/board/news/${id}`);
+    if (error) alert("삭제에 실패했습니다.");
+    else {
+      alert("삭제되었습니다.");
+      loadPosts();
+    }
+  };
+  const formatDate = (dateString) => {
+    if (!dateString) return "-";
+    const date = new Date(dateString);
+    return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(
+      2,
+      "0"
+    )}.${String(date.getDate()).padStart(2, "0")}`;
+  };
+
+  onMounted(() => loadPosts());
+</script>
+
+<style scoped>
+  /* 검색 영역 input/select 스타일 통일 */
+  .admin--search-box .admin--form-input,
+  .admin--search-box .admin--search-input,
+  .admin--search-box .admin--form-select,
+  .admin--search-box .admin--search-select {
+    border: 1px solid var(--admin-border-color) !important;
+    border-radius: 4px;
+    height: 33px !important;
+    padding: 6px 14px !important;
+    font-size: 13px !important;
+  }
+</style>

+ 276 - 0
app/pages/site-manager/board/notice/create.vue

@@ -0,0 +1,276 @@
+<template>
+  <div class="admin--news-form">
+    <form @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 댓글허용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">댓글허용</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.allow_comment" type="checkbox" />
+            <span>댓글 허용</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 공지 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">공지</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.is_notice" type="checkbox" />
+            <span>공지글로 등록</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 이름 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >이름 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="이름을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 이메일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >이메일 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.email"
+          type="email"
+          class="admin--form-input"
+          placeholder="이메일을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- URL -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">URL</label>
+        <input
+          v-model="formData.url"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com"
+        />
+      </div>
+
+      <!-- 제목 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >제목 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.title"
+          type="text"
+          class="admin--form-input"
+          placeholder="제목을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 내용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >내용 <span class="admin--required">*</span></label
+        >
+        <SunEditor v-model="formData.content" />
+      </div>
+
+      <!-- 파일첨부 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">파일첨부</label>
+        <div class="admin--file-list">
+          <div
+            v-for="(file, index) in attachedFiles"
+            :key="index"
+            class="admin--file-item"
+          >
+            <span class="admin--file-name">{{ file.name }}</span>
+            <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
+            <button
+              type="button"
+              class="admin--btn-remove-file"
+              @click="removeFile(index)"
+            >
+              삭제
+            </button>
+          </div>
+        </div>
+        <input
+          ref="fileInput"
+          type="file"
+          multiple
+          class="admin--form-file-hidden"
+          @change="handleFileAdd"
+        />
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="triggerFileInput"
+        >
+          파일 추가
+        </button>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
+          {{ isSaving ? "저장 중..." : "확인" }}
+        </button>
+        <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+  import { useRouter } from "vue-router";
+  import SunEditor from "~/components/admin/SunEditor.vue";
+
+  definePageMeta({
+    layout: "admin",
+    middleware: ["auth"],
+  });
+
+  const router = useRouter();
+  const { post, upload } = useApi();
+
+  const isSaving = ref(false);
+  const successMessage = ref("");
+  const errorMessage = ref("");
+  const attachedFiles = ref([]);
+  const fileInput = ref(null);
+
+  const formData = ref({
+    allow_comment: false,
+    is_notice: false,
+    name: "고진",
+    email: "admin@admin.kr",
+    url: "",
+    title: "",
+    content: "",
+    file_urls: [],
+  });
+
+  const triggerFileInput = () => {
+    fileInput.value?.click();
+  };
+
+  const handleFileAdd = (event) => {
+    const files = Array.from(event.target.files);
+    attachedFiles.value.push(...files);
+    event.target.value = "";
+  };
+
+  const removeFile = (index) => {
+    attachedFiles.value.splice(index, 1);
+  };
+
+  const formatFileSize = (bytes) => {
+    if (bytes === 0) return "0 Bytes";
+    const k = 1024;
+    const sizes = ["Bytes", "KB", "MB", "GB"];
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+    return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
+  };
+
+  const handleSubmit = async () => {
+    successMessage.value = "";
+    errorMessage.value = "";
+
+    if (!formData.value.title) {
+      errorMessage.value = "제목을 입력하세요.";
+      return;
+    }
+
+    if (!formData.value.content) {
+      errorMessage.value = "내용을 입력하세요.";
+      return;
+    }
+
+    isSaving.value = true;
+
+    try {
+      let fileUrls = [];
+
+      // 파일 업로드
+      if (attachedFiles.value.length > 0) {
+        for (const file of attachedFiles.value) {
+          const formDataFile = new FormData();
+          formDataFile.append("file", file);
+
+          const { data: uploadData, error: uploadError } = await upload(
+            "/upload/news-file",
+            formDataFile
+          );
+
+          if (uploadError) {
+            errorMessage.value = `파일 업로드에 실패했습니다: ${file.name}`;
+            isSaving.value = false;
+            return;
+          }
+
+          fileUrls.push({
+            name: file.name,
+            url: uploadData.data.url,
+            size: file.size,
+          });
+        }
+      }
+
+      // content에서 도메인 제거
+      let contentToSave = formData.value.content;
+      if (contentToSave) {
+        // http://도메인 또는 https://도메인 제거
+        contentToSave = contentToSave.replace(/https?:\/\/[^\/]+/g, "");
+      }
+
+      const submitData = {
+        ...formData.value,
+        allow_comment: formData.value.allow_comment ? 1 : 0,
+        is_notice: formData.value.is_notice ? 1 : 0,
+        content: contentToSave,
+        file_urls: fileUrls,
+      };
+
+      const { data, error } = await post("/board/notice", submitData);
+
+      if (error) {
+        errorMessage.value = error.message || "등록에 실패했습니다.";
+      } else {
+        successMessage.value = "뉴스가 등록되었습니다.";
+        setTimeout(() => {
+          router.push("/site-manager/board/notice");
+        }, 1000);
+      }
+    } catch (error) {
+      errorMessage.value = "서버 오류가 발생했습니다.";
+      console.error("Save error:", error);
+    } finally {
+      isSaving.value = false;
+    }
+  };
+
+  const goToList = () => {
+    router.push("/site-manager/board/notice");
+  };
+</script>

+ 340 - 0
app/pages/site-manager/board/notice/edit/[id].vue

@@ -0,0 +1,340 @@
+<template>
+  <div class="admin--news-form">
+    <div v-if="isLoading" class="admin--loading">데이터를 불러오는 중...</div>
+
+    <form v-else @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 댓글허용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">댓글허용</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.allow_comment" type="checkbox" />
+            <span>댓글 허용</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 공지 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">공지</label>
+        <div class="admin--checkbox-group">
+          <label class="admin--checkbox-label">
+            <input v-model="formData.is_notice" type="checkbox" />
+            <span>공지글로 등록</span>
+          </label>
+        </div>
+      </div>
+
+      <!-- 이름 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >이름 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="이름을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 이메일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >이메일 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.email"
+          type="email"
+          class="admin--form-input"
+          placeholder="이메일을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- URL -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">URL</label>
+        <input
+          v-model="formData.url"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com"
+        />
+      </div>
+
+      <!-- 제목 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >제목 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.title"
+          type="text"
+          class="admin--form-input"
+          placeholder="제목을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 내용 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >내용 <span class="admin--required">*</span></label
+        >
+        <SunEditor v-model="formData.content" />
+      </div>
+
+      <!-- 기존 첨부파일 -->
+      <div v-if="existingFiles.length > 0" class="admin--form-group">
+        <label class="admin--form-label">기존 첨부파일</label>
+        <div class="admin--file-list">
+          <div
+            v-for="(file, index) in existingFiles"
+            :key="'existing-' + index"
+            class="admin--file-item"
+          >
+            <a :href="file.url" target="_blank" class="admin--file-name">{{
+              file.name
+            }}</a>
+            <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
+            <button
+              type="button"
+              class="admin--btn-remove-file"
+              @click="removeExistingFile(index)"
+            >
+              삭제
+            </button>
+          </div>
+        </div>
+      </div>
+
+      <!-- 새 파일첨부 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">파일첨부</label>
+        <div v-if="attachedFiles.length > 0" class="admin--file-list">
+          <div
+            v-for="(file, index) in attachedFiles"
+            :key="'new-' + index"
+            class="admin--file-item"
+          >
+            <span class="admin--file-name">{{ file.name }}</span>
+            <span class="admin--file-size">({{ formatFileSize(file.size) }})</span>
+            <button
+              type="button"
+              class="admin--btn-remove-file"
+              @click="removeFile(index)"
+            >
+              삭제
+            </button>
+          </div>
+        </div>
+        <input
+          ref="fileInput"
+          type="file"
+          multiple
+          class="admin--form-file-hidden"
+          @change="handleFileAdd"
+        />
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="triggerFileInput"
+        >
+          파일 추가
+        </button>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
+          {{ isSaving ? "저장 중..." : "확인" }}
+        </button>
+        <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+  import { ref, onMounted } from "vue";
+  import { useRoute, useRouter } from "vue-router";
+  import SunEditor from "~/components/admin/SunEditor.vue";
+
+  definePageMeta({
+    layout: "admin",
+    middleware: ["auth"],
+  });
+
+  const route = useRoute();
+  const router = useRouter();
+  const { get, put, upload } = useApi();
+
+  const isLoading = ref(true);
+  const isSaving = ref(false);
+  const successMessage = ref("");
+  const errorMessage = ref("");
+  const attachedFiles = ref([]);
+  const existingFiles = ref([]);
+  const fileInput = ref(null);
+
+  const formData = ref({
+    allow_comment: false,
+    is_notice: false,
+    name: "",
+    email: "",
+    url: "",
+    title: "",
+    content: "",
+    file_urls: [],
+  });
+
+  const loadNews = async () => {
+    isLoading.value = true;
+
+    const id = route.params.id;
+    const { data, error } = await get(`/board/notice/${id}`);
+    console.log("[NewsEdit] 데이터 로드:", { data, error });
+
+    if (data?.success && data?.data) {
+      const news = data.data;
+      formData.value = {
+        allow_comment: news.allow_comment === 1 || news.allow_comment === '1',
+        is_notice: news.is_notice === 1 || news.is_notice === '1',
+        name: news.name || "",
+        email: news.email || "",
+        url: news.url || "",
+        title: news.title || "",
+        content: news.content || "",
+        file_urls: news.file_urls || [],
+      };
+      existingFiles.value = news.file_urls || [];
+      console.log("[NewsEdit] 로드 성공");
+    }
+
+    isLoading.value = false;
+  };
+
+  const triggerFileInput = () => {
+    fileInput.value?.click();
+  };
+
+  const handleFileAdd = (event) => {
+    const files = Array.from(event.target.files);
+    attachedFiles.value.push(...files);
+    event.target.value = "";
+  };
+
+  const removeFile = (index) => {
+    attachedFiles.value.splice(index, 1);
+  };
+
+  const removeExistingFile = (index) => {
+    existingFiles.value.splice(index, 1);
+  };
+
+  const formatFileSize = (bytes) => {
+    if (bytes === 0) return "0 Bytes";
+    const k = 1024;
+    const sizes = ["Bytes", "KB", "MB", "GB"];
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+    return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
+  };
+
+  const handleSubmit = async () => {
+    successMessage.value = "";
+    errorMessage.value = "";
+
+    if (!formData.value.title) {
+      errorMessage.value = "제목을 입력하세요.";
+      return;
+    }
+
+    if (!formData.value.content) {
+      errorMessage.value = "내용을 입력하세요.";
+      return;
+    }
+
+    isSaving.value = true;
+
+    try {
+      let fileUrls = [...existingFiles.value];
+
+      // 새 파일 업로드
+      if (attachedFiles.value.length > 0) {
+        for (const file of attachedFiles.value) {
+          const formDataFile = new FormData();
+          formDataFile.append("file", file);
+
+          const { data: uploadData, error: uploadError } = await upload(
+            "/upload/news-file",
+            formDataFile
+          );
+
+          if (uploadError) {
+            errorMessage.value = `파일 업로드에 실패했습니다: ${file.name}`;
+            isSaving.value = false;
+            return;
+          }
+
+          fileUrls.push({
+            name: file.name,
+            url: uploadData.data.url,
+            size: file.size,
+          });
+        }
+      }
+
+      // content에서 도메인 제거
+      let contentToSave = formData.value.content;
+      if (contentToSave) {
+        // http://도메인 또는 https://도메인 제거
+        contentToSave = contentToSave.replace(/https?:\/\/[^\/]+/g, "");
+      }
+
+      const submitData = {
+        ...formData.value,
+        allow_comment: formData.value.allow_comment ? 1 : 0,
+        is_notice: formData.value.is_notice ? 1 : 0,
+        content: contentToSave,
+        file_urls: fileUrls,
+      };
+
+      const id = route.params.id;
+      const { data, error } = await put(`/board/notice/${id}`, submitData);
+
+      if (error) {
+        errorMessage.value = error.message || "수정에 실패했습니다.";
+      } else {
+        successMessage.value = "뉴스가 수정되었습니다.";
+        setTimeout(() => {
+          router.push("/site-manager/board/notice");
+        }, 1000);
+      }
+    } catch (error) {
+      errorMessage.value = "서버 오류가 발생했습니다.";
+      console.error("Save error:", error);
+    } finally {
+      isSaving.value = false;
+    }
+  };
+
+  const goToList = () => {
+    router.push("/site-manager/board/notice");
+  };
+
+  onMounted(() => {
+    loadNews();
+  });
+</script>

+ 225 - 0
app/pages/site-manager/board/notice/index.vue

@@ -0,0 +1,225 @@
+<template>
+  <div class="admin--board-list">
+    <!-- 검색 영역 -->
+    <div class="admin--search-box">
+      <div class="admin--search-form">
+        <select v-model="searchType" class="admin--form-select admin--search-select">
+          <option value="title">제목</option>
+          <option value="name">이름</option>
+          <option value="content">내용</option>
+        </select>
+        <input
+          v-model="searchKeyword"
+          type="text"
+          class="admin--form-input admin--search-input"
+          placeholder="검색어를 입력하세요"
+          @keyup.enter="handleSearch"
+        />
+        <button class="admin--btn-small admin--btn-small-primary" @click="handleSearch">
+          검색
+        </button>
+        <button class="admin--btn-small admin--btn-small-secondary" @click="handleReset">
+          초기화
+        </button>
+      </div>
+      <div class="admin--search-actions">
+        <button class="admin--btn-small admin--btn-small-primary" @click="goToCreate">
+          + 등록
+        </button>
+      </div>
+    </div>
+
+    <!-- 테이블 -->
+    <div class="admin--table-wrapper">
+      <table class="admin--table">
+        <thead>
+          <tr>
+            <th>NO</th>
+            <th>제목</th>
+            <th>이름</th>
+            <th>등록일</th>
+            <th>조회</th>
+            <th>관리</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="!posts || posts.length === 0">
+            <td colspan="6" class="admin--table-empty">등록된 게시물이 없습니다.</td>
+          </tr>
+          <tr v-else v-for="(post, index) in posts" :key="post.id">
+            <td>
+              {{
+                (post.is_notice === 1 || post.is_notice === '1')
+                  ? "공지"
+                  : totalCount - ((currentPage - 1) * perPage + index)
+              }}
+            </td>
+            <td class="admin--table-title">{{ post.title }}</td>
+            <td>{{ post.name }}</td>
+            <td>{{ formatDate(post.created_at) }}</td>
+            <td>{{ post.views }}</td>
+            <td>
+              <div class="admin--table-actions">
+                <button
+                  class="admin--btn-small admin--btn-small-primary"
+                  @click="goToEdit(post.id)"
+                >
+                  수정
+                </button>
+                <button
+                  class="admin--btn-small admin--btn-small-danger"
+                  @click="handleDelete(post.id)"
+                >
+                  삭제
+                </button>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <!-- 페이지네이션 -->
+    <div v-if="totalPages > 1" class="admin--pagination">
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(1)"
+        title="처음"
+      >
+        ⏮
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(currentPage - 1)"
+        title="이전"
+      >
+        ◀
+      </button>
+      <button
+        v-for="page in visiblePages"
+        :key="page"
+        class="admin--pagination-btn"
+        :class="{ 'is-active': page === currentPage }"
+        @click="changePage(page)"
+      >
+        {{ page }}
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(currentPage + 1)"
+        title="다음"
+      >
+        ▶
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(totalPages)"
+        title="끝"
+      >
+        ⏭
+      </button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref, computed, onMounted } from "vue";
+  import { useRouter } from "vue-router";
+
+  definePageMeta({ layout: "admin", middleware: ["auth"] });
+
+  const router = useRouter();
+  const { get, del } = useApi();
+  const posts = ref([]);
+  const searchType = ref("title");
+  const searchKeyword = ref("");
+  const currentPage = ref(1);
+  const perPage = ref(10);
+  const totalCount = ref(0);
+  const totalPages = ref(0);
+
+  const visiblePages = computed(() => {
+    const pages = [];
+    const maxVisible = 5;
+    let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
+    let end = Math.min(totalPages.value, start + maxVisible - 1);
+    if (end - start < maxVisible - 1) start = Math.max(1, end - maxVisible + 1);
+    for (let i = start; i <= end; i++) pages.push(i);
+    return pages;
+  });
+
+  const loadPosts = async () => {
+    const params = { page: currentPage.value, per_page: perPage.value };
+    if (searchKeyword.value) {
+      params.search_type = searchType.value;
+      params.search_keyword = searchKeyword.value;
+    }
+    const { data, error } = await get("/board/notice", { params });
+
+    console.log("[NoticeBoard] API 응답:", { data, error });
+
+    // API 응답: { success: true, data: { items, total }, message }
+    if (data?.success && data?.data) {
+      posts.value = data.data.items || [];
+      totalCount.value = data.data.total || 0;
+      totalPages.value = Math.ceil(totalCount.value / perPage.value);
+      console.log("[NoticeBoard] 로드 성공:", posts.value.length);
+    }
+  };
+
+  const handleSearch = () => {
+    currentPage.value = 1;
+    loadPosts();
+  };
+  const handleReset = () => {
+    searchType.value = "title";
+    searchKeyword.value = "";
+    currentPage.value = 1;
+    loadPosts();
+  };
+  const changePage = (page) => {
+    if (page < 1 || page > totalPages.value) return;
+    currentPage.value = page;
+    loadPosts();
+    window.scrollTo({ top: 0, behavior: "smooth" });
+  };
+  const goToCreate = () => router.push("/site-manager/board/notice/create");
+  const goToEdit = (id) => router.push(`/site-manager/board/notice/edit/${id}`);
+  const handleDelete = async (id) => {
+    if (!confirm("정말 삭제하시겠습니까?")) return;
+    const { error } = await del(`/board/notice/${id}`);
+    if (error) alert("삭제에 실패했습니다.");
+    else {
+      alert("삭제되었습니다.");
+      loadPosts();
+    }
+  };
+  const formatDate = (dateString) => {
+    if (!dateString) return "-";
+    const date = new Date(dateString);
+    return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(
+      2,
+      "0"
+    )}.${String(date.getDate()).padStart(2, "0")}`;
+  };
+
+  onMounted(() => loadPosts());
+</script>
+
+<style scoped>
+  /* 검색 영역 input/select 스타일 통일 */
+  .admin--search-box .admin--form-input,
+  .admin--search-box .admin--search-input,
+  .admin--search-box .admin--form-select,
+  .admin--search-box .admin--search-select {
+    border: 1px solid var(--admin-border-color) !important;
+    border-radius: 4px;
+    height: 33px !important;
+    padding: 6px 14px !important;
+    font-size: 13px !important;
+  }
+</style>

+ 202 - 0
app/pages/site-manager/branch/create.vue

@@ -0,0 +1,202 @@
+<template>
+  <div class="admin--branch-form">
+    <form @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 지점명 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >딜러사 명 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="지점명을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 대표번호 -->
+      <!-- <div class="admin--form-group">
+        <label class="admin--form-label">대표번호 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.phone"
+          type="tel"
+          class="admin--form-input"
+          placeholder="02-1234-5678"
+          required
+        >
+      </div> -->
+
+      <!-- 주소 -->
+      <!-- <div class="admin--form-group">
+        <label class="admin--form-label">주소 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.address"
+          type="text"
+          class="admin--form-input"
+          placeholder="주소를 입력하세요"
+          required
+        >
+      </div> -->
+
+      <!-- 상세주소 -->
+      <!-- <div class="admin--form-group">
+        <label class="admin--form-label">상세주소</label>
+        <input
+          v-model="formData.detail_address"
+          type="text"
+          class="admin--form-input"
+          placeholder="상세주소를 입력하세요"
+        >
+      </div> -->
+
+      <!-- 위도/경도 -->
+      <!-- <div class="admin--form-group">
+        <label class="admin--form-label">위치 좌표</label>
+        <div class="admin--coordinate-group">
+          <div class="admin--coordinate-item">
+            <label>위도</label>
+            <input
+              v-model.number="formData.latitude"
+              type="number"
+              step="any"
+              class="admin--form-input"
+              placeholder="37.5665"
+            >
+          </div>
+          <div class="admin--coordinate-item">
+            <label>경도</label>
+            <input
+              v-model.number="formData.longitude"
+              type="number"
+              step="any"
+              class="admin--form-input"
+              placeholder="126.9780"
+            >
+          </div>
+        </div>
+      </div> -->
+
+      <!-- 영업시간 -->
+      <!-- <div class="admin--form-group">
+        <label class="admin--form-label">영업시간</label>
+        <textarea
+          v-model="formData.business_hours"
+          class="admin--form-textarea"
+          rows="3"
+          placeholder="평일: 09:00 - 18:00&#10;주말: 10:00 - 17:00"
+        ></textarea>
+      </div> -->
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
+          {{ isSaving ? "저장 중..." : "확인" }}
+        </button>
+        <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+  import { useRouter } from "vue-router";
+
+  definePageMeta({
+    layout: "admin",
+    middleware: ["auth"],
+  });
+
+  const router = useRouter();
+  const { post } = useApi();
+
+  const isSaving = ref(false);
+  const successMessage = ref("");
+  const errorMessage = ref("");
+
+  const formData = ref({
+    name: "",
+    // phone: "",
+    // address: "",
+    // detail_address: "",
+    //latitude: null,
+    //longitude: null,
+    //business_hours: "",
+  });
+
+  // 폼 제출
+  const handleSubmit = async () => {
+    successMessage.value = "";
+    errorMessage.value = "";
+
+    // 유효성 검사
+    if (!formData.value.name) {
+      errorMessage.value = "딜러사 명을 입력하세요.";
+      return;
+    }
+
+    // if (!formData.value.phone) {
+    //   errorMessage.value = "대표번호를 입력하세요.";
+    //   return;
+    // }
+
+    // if (!formData.value.address) {
+    //   errorMessage.value = "주소를 입력하세요.";
+    //   return;
+    // }
+
+    isSaving.value = true;
+
+    try {
+      const { data, error } = await post("/branch", formData.value);
+
+      if (error || !data?.success) {
+        errorMessage.value = error?.message || data?.message || "등록에 실패했습니다.";
+      } else {
+        successMessage.value = data.message || "지점이 등록되었습니다.";
+        setTimeout(() => {
+          router.push("/site-manager/branch/list");
+        }, 1000);
+      }
+    } catch (error) {
+      errorMessage.value = "서버 오류가 발생했습니다.";
+      console.error("Save error:", error);
+    } finally {
+      isSaving.value = false;
+    }
+  };
+
+  // 목록으로 이동
+  const goToList = () => {
+    router.push("/site-manager/branch/list");
+  };
+</script>
+
+<style scoped>
+  .admin--coordinate-group {
+    display: flex;
+    gap: 16px;
+  }
+
+  .admin--coordinate-item {
+    flex: 1;
+  }
+
+  .admin--coordinate-item label {
+    display: block;
+    margin-bottom: 8px;
+    font-size: 14px;
+    color: #666;
+  }
+</style>

+ 238 - 0
app/pages/site-manager/branch/edit/[id].vue

@@ -0,0 +1,238 @@
+<template>
+  <div class="admin--branch-form">
+    <div v-if="isLoading" class="admin--loading">데이터를 불러오는 중...</div>
+
+    <form v-else @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 지점명 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >지점명 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="지점명을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 대표번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >대표번호 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.main_phone"
+          type="tel"
+          class="admin--form-input"
+          placeholder="02-1234-5678"
+          required
+        />
+      </div>
+
+      <!-- 주소 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >주소 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.address"
+          type="text"
+          class="admin--form-input"
+          placeholder="주소를 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 상세주소 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">상세주소</label>
+        <input
+          v-model="formData.detail_address"
+          type="text"
+          class="admin--form-input"
+          placeholder="상세주소를 입력하세요"
+        />
+      </div>
+
+      <!-- 위도/경도 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">위치 좌표</label>
+        <div class="admin--coordinate-group">
+          <div class="admin--coordinate-item">
+            <label>위도</label>
+            <input
+              v-model.number="formData.latitude"
+              type="number"
+              step="any"
+              class="admin--form-input"
+              placeholder="37.5665"
+            />
+          </div>
+          <div class="admin--coordinate-item">
+            <label>경도</label>
+            <input
+              v-model.number="formData.longitude"
+              type="number"
+              step="any"
+              class="admin--form-input"
+              placeholder="126.9780"
+            />
+          </div>
+        </div>
+      </div>
+
+      <!-- 영업시간 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">영업시간</label>
+        <textarea
+          v-model="formData.business_hours"
+          class="admin--form-textarea"
+          rows="3"
+          placeholder="평일: 09:00 - 18:00&#10;주말: 10:00 - 17:00"
+        ></textarea>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
+          {{ isSaving ? "저장 중..." : "확인" }}
+        </button>
+        <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+  import { ref, onMounted } from "vue";
+  import { useRoute, useRouter } from "vue-router";
+
+  definePageMeta({
+    layout: "admin",
+    middleware: ["auth"],
+  });
+
+  const route = useRoute();
+  const router = useRouter();
+  const { get, put } = useApi();
+
+  const isLoading = ref(true);
+  const isSaving = ref(false);
+  const successMessage = ref("");
+  const errorMessage = ref("");
+
+  const formData = ref({
+    name: "",
+    main_phone: "",
+    address: "",
+    detail_address: "",
+    latitude: null,
+    longitude: null,
+    business_hours: "",
+  });
+
+  // 데이터 로드
+  const loadBranch = async () => {
+    isLoading.value = true;
+
+    const id = route.params.id;
+    const { data, error } = await get(`/branch/${id}`);
+
+    if (data?.success && data?.data) {
+      const branch = data.data;
+      formData.value = {
+        name: branch.name || "",
+        main_phone: branch.main_phone || "",
+        address: branch.address || "",
+        detail_address: branch.detail_address || "",
+        latitude: branch.latitude || null,
+        longitude: branch.longitude || null,
+        business_hours: branch.business_hours || "",
+      };
+    }
+
+    isLoading.value = false;
+  };
+
+  // 폼 제출
+  const handleSubmit = async () => {
+    successMessage.value = "";
+    errorMessage.value = "";
+
+    // 유효성 검사
+    if (!formData.value.name) {
+      errorMessage.value = "지점명을 입력하세요.";
+      return;
+    }
+
+    if (!formData.value.main_phone) {
+      errorMessage.value = "대표번호를 입력하세요.";
+      return;
+    }
+
+    if (!formData.value.address) {
+      errorMessage.value = "주소를 입력하세요.";
+      return;
+    }
+
+    isSaving.value = true;
+
+    try {
+      const id = route.params.id;
+      const { data, error } = await put(`/branch/${id}`, formData.value);
+
+      if (error || !data?.success) {
+        errorMessage.value = error?.message || data?.message || "수정에 실패했습니다.";
+      } else {
+        successMessage.value = data.message || "지점이 수정되었습니다.";
+        setTimeout(() => {
+          router.push("/site-manager/branch/list");
+        }, 1000);
+      }
+    } catch (error) {
+      errorMessage.value = "서버 오류가 발생했습니다.";
+      console.error("Save error:", error);
+    } finally {
+      isSaving.value = false;
+    }
+  };
+
+  // 목록으로 이동
+  const goToList = () => {
+    router.push("/site-manager/branch/list");
+  };
+
+  onMounted(() => {
+    loadBranch();
+  });
+</script>
+
+<style scoped>
+  .admin--coordinate-group {
+    display: flex;
+    gap: 16px;
+  }
+
+  .admin--coordinate-item {
+    flex: 1;
+  }
+
+  .admin--coordinate-item label {
+    display: block;
+    margin-bottom: 8px;
+    font-size: 14px;
+    color: #666;
+  }
+</style>

+ 336 - 0
app/pages/site-manager/branch/list.vue

@@ -0,0 +1,336 @@
+<template>
+  <div class="admin--branch-list">
+    <!-- 상단 버튼 -->
+    <div class="admin--search-box">
+      <div class="admin--search-form"></div>
+      <div class="admin--search-actions">
+        <button class="admin--btn admin--btn-primary" @click="goToCreate">
+          + 지점 등록
+        </button>
+      </div>
+    </div>
+
+    <!-- 테이블 -->
+    <div class="admin--table-wrapper">
+      <table class="admin--table">
+        <thead>
+          <tr>
+            <th>NO</th>
+            <th style="width: 70%">딜러사 명</th>
+            <!-- <th>대표번호</th>
+            <th>주소</th> -->
+            <th>상태</th>
+            <th>관리</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="isLoading">
+            <td colspan="6" class="admin--table-loading">데이터를 불러오는 중...</td>
+          </tr>
+          <tr v-else-if="!branches || branches.length === 0">
+            <td colspan="6" class="admin--table-empty">등록된 지점이 없습니다.</td>
+          </tr>
+          <tr v-else v-for="(branch, index) in branches" :key="branch.id">
+            <td>{{ totalCount - ((currentPage - 1) * perPage + index) }}</td>
+            <td class="admin--table-title">{{ branch.name }}</td>
+            <!-- <td>{{ branch.main_phone }}</td>
+            <td>{{ branch.address }}</td> -->
+            <td>
+              <button
+                class="admin--toggle-btn"
+                :class="{ 'is-active': branch.is_active == 1 }"
+                @click="toggleActive(branch.id, branch.is_active)"
+              >
+                {{ branch.is_active == 1 ? "사용" : "비사용" }}
+              </button>
+            </td>
+            <td>
+              <div class="admin--table-actions">
+                <button
+                  class="admin--btn-small admin--btn-small-primary"
+                  @click="goToEdit(branch.id)"
+                >
+                  수정
+                </button>
+                <button
+                  class="admin--btn-small admin--btn-small-danger"
+                  @click="deleteBranch(branch.id)"
+                >
+                  삭제
+                </button>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <!-- 페이지네이션 -->
+    <div v-if="totalPages > 1" class="admin--pagination">
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(currentPage - 1)"
+      >
+        이전
+      </button>
+      <button
+        v-for="page in visiblePages"
+        :key="page"
+        class="admin--pagination-btn"
+        :class="{ 'is-active': page === currentPage }"
+        @click="changePage(page)"
+      >
+        {{ page }}
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(currentPage + 1)"
+      >
+        다음
+      </button>
+    </div>
+
+    <!-- 알림 모달 -->
+    <AdminAlertModal
+      v-if="alertModal.show"
+      :title="alertModal.title"
+      :message="alertModal.message"
+      :type="alertModal.type"
+      @confirm="handleAlertConfirm"
+      @cancel="handleAlertCancel"
+      @close="closeAlertModal"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { ref, computed, onMounted } from "vue";
+  import { useRouter } from "vue-router";
+  import AdminAlertModal from "~/components/admin/AdminAlertModal.vue";
+
+  definePageMeta({
+    layout: "admin",
+    middleware: ["auth"],
+  });
+
+  const router = useRouter();
+  const { get, del, post } = useApi();
+
+  const isLoading = ref(false);
+  const branches = ref([]);
+  const currentPage = ref(1);
+  const perPage = ref(10);
+  const totalCount = ref(0);
+  const totalPages = ref(0);
+
+  // 알림 모달
+  const alertModal = ref({
+    show: false,
+    title: "알림",
+    message: "",
+    type: "alert",
+    onConfirm: null,
+  });
+
+  // 알림 모달 표시
+  const showAlert = (message, title = "알림") => {
+    alertModal.value = {
+      show: true,
+      title,
+      message,
+      type: "alert",
+      onConfirm: null,
+    };
+  };
+
+  // 확인 모달 표시
+  const showConfirm = (message, onConfirm, title = "확인") => {
+    alertModal.value = {
+      show: true,
+      title,
+      message,
+      type: "confirm",
+      onConfirm,
+    };
+  };
+
+  // 알림 모달 닫기
+  const closeAlertModal = () => {
+    alertModal.value.show = false;
+  };
+
+  // 알림 모달 확인
+  const handleAlertConfirm = () => {
+    if (alertModal.value.onConfirm) {
+      alertModal.value.onConfirm();
+    }
+    closeAlertModal();
+  };
+
+  // 알림 모달 취소
+  const handleAlertCancel = () => {
+    closeAlertModal();
+  };
+
+  // 보이는 페이지 번호 계산
+  const visiblePages = computed(() => {
+    const pages = [];
+    const maxVisible = 5;
+    let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
+    let end = Math.min(totalPages.value, start + maxVisible - 1);
+
+    if (end - start < maxVisible - 1) {
+      start = Math.max(1, end - maxVisible + 1);
+    }
+
+    for (let i = start; i <= end; i++) {
+      pages.push(i);
+    }
+
+    return pages;
+  });
+
+  // 데이터 로드
+  const loadBranches = async () => {
+    isLoading.value = true;
+
+    const params = {
+      page: currentPage.value,
+      per_page: perPage.value,
+    };
+
+    const { data, error } = await get("/branch/list", { params });
+
+    console.log("[BranchList] API 응답:", { data, error });
+
+    // API 응답: { success: true, data: { items, total }, message }
+    if (data?.success && data?.data) {
+      branches.value = data.data.items || [];
+      totalCount.value = data.data.total || 0;
+      totalPages.value = Math.ceil(totalCount.value / perPage.value);
+      console.log("[BranchList] 로드 성공:", branches.value.length);
+    }
+
+    isLoading.value = false;
+  };
+
+  // 페이지 변경
+  const changePage = (page) => {
+    if (page < 1 || page > totalPages.value) return;
+    currentPage.value = page;
+    loadBranches();
+    window.scrollTo({ top: 0, behavior: "smooth" });
+  };
+
+  // 지점 등록 페이지로 이동
+  const goToCreate = () => {
+    router.push("/site-manager/branch/create");
+  };
+
+  // 지점 수정 페이지로 이동
+  const goToEdit = (id) => {
+    router.push(`/site-manager/branch/edit/${id}`);
+  };
+
+  // 지점 삭제
+  const deleteBranch = (id) => {
+    showConfirm(
+      "정말 삭제하시겠습니까?",
+      async () => {
+        const { data, error } = await del(`/branch/${id}`);
+
+        if (error || !data?.success) {
+          showAlert(error?.message || data?.message || "삭제에 실패했습니다.", "오류");
+        } else {
+          showAlert(data.message || "지점이 삭제되었습니다.", "성공");
+          loadBranches();
+        }
+      },
+      "지점 삭제"
+    );
+  };
+
+  // 사용/비사용 토글
+  const toggleActive = (id, currentStatus) => {
+    console.log("[toggleActive] 호출:", { id, currentStatus });
+    const statusText = currentStatus == 1 ? "비사용" : "사용";
+
+    showConfirm(
+      `지점을 ${statusText} 상태로 변경하시겠습니까?`,
+      async () => {
+        console.log("[toggleActive] API 호출:", `/branch/${id}/toggle-active`);
+
+        // POST 방식으로 변경 (PATCH 대신)
+        const { data, error } = await post(`/branch/${id}/toggle-active`);
+
+        console.log("[toggleActive] API 응답:", { data, error });
+
+        if (error || !data?.success) {
+          console.error("[toggleActive] 에러 상세:", error);
+          showAlert(
+            error?.message || data?.message || "상태 변경에 실패했습니다.",
+            "오류"
+          );
+        } else {
+          showAlert(data.message || "지점 상태가 변경되었습니다.", "성공");
+          loadBranches();
+        }
+      },
+      "상태 변경"
+    );
+  };
+
+  onMounted(() => {
+    loadBranches();
+  });
+</script>
+
+<style scoped>
+  .admin--search-actions .admin--btn-primary {
+    background: var(--admin-accent-primary);
+    color: white;
+    border-color: var(--admin-accent-primary);
+    font-weight: 500;
+    padding: 8px 18px;
+    font-size: 13px;
+    border-radius: 8px;
+    transition: all 0.3s ease;
+  }
+
+  .admin--search-actions .admin--btn-primary:hover {
+    background: var(--admin-accent-hover);
+    border-color: var(--admin-accent-hover);
+    transform: translateY(-1px);
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  }
+
+  .admin--toggle-btn {
+    padding: 6px 16px;
+    font-size: 12px;
+    border-radius: 20px;
+    border: 1px solid #ddd;
+    background: #f5f5f5;
+    color: #666;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    font-weight: 500;
+  }
+
+  .admin--toggle-btn:hover {
+    border-color: #bbb;
+    background: #e8e8e8;
+  }
+
+  .admin--toggle-btn.is-active {
+    background: var(--admin-accent-primary);
+    color: white;
+    border-color: var(--admin-accent-primary);
+  }
+
+  .admin--toggle-btn.is-active:hover {
+    background: var(--admin-accent-hover);
+    border-color: var(--admin-accent-hover);
+  }
+</style>

+ 400 - 0
app/pages/site-manager/branch/manager/create.vue

@@ -0,0 +1,400 @@
+<template>
+  <div class="admin--manager-form">
+    <form @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 지점명 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">지점명 <span class="admin--required">*</span></label>
+        <select
+          v-model="formData.branch_id"
+          class="admin--form-select"
+          required
+        >
+          <option value="">지점을 선택하세요</option>
+          <option
+            v-for="branch in branches"
+            :key="branch.id"
+            :value="branch.id"
+            :disabled="branch.is_active !== 1 && branch.is_active !== '1'"
+          >
+            {{ branch.name }}{{ (branch.is_active !== 1 && branch.is_active !== '1') ? ' (비활성화)' : '' }}
+          </option>
+        </select>
+      </div>
+
+      <!-- 아이디 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">아이디 <span class="admin--required">*</span></label>
+        <div class="admin--input-with-button">
+          <input
+            v-model="formData.user_id"
+            type="text"
+            class="admin--form-input"
+            placeholder="아이디를 입력하세요"
+            required
+            @input="handleUserIdInput"
+          >
+          <button
+            type="button"
+            class="admin--btn admin--btn-check"
+            @click="checkDuplicateUserId"
+            :disabled="!formData.user_id || isCheckingUserId"
+          >
+            {{ isCheckingUserId ? '확인 중...' : '중복 체크' }}
+          </button>
+        </div>
+        <p v-if="userIdCheckResult === 'available'" class="admin--form-help admin--text-success">
+          사용 가능한 아이디입니다.
+        </p>
+        <p v-if="userIdCheckResult === 'duplicate'" class="admin--form-help admin--text-error">
+          이미 사용 중인 아이디입니다.
+        </p>
+      </div>
+
+      <!-- 비밀번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">비밀번호 <span class="admin--required">*</span></label>
+        <div class="admin--password-input-wrapper">
+          <input
+            v-model="formData.password"
+            :type="showPassword ? 'text' : 'password'"
+            class="admin--form-input"
+            placeholder="비밀번호를 입력하세요"
+            required
+          >
+          <button
+            type="button"
+            class="admin--password-toggle"
+            @click="showPassword = !showPassword"
+          >
+            {{ showPassword ? '👁️' : '👁️‍🗨️' }}
+          </button>
+        </div>
+      </div>
+
+      <!-- 비밀번호 확인 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">비밀번호 확인 <span class="admin--required">*</span></label>
+        <div class="admin--password-input-wrapper">
+          <input
+            v-model="formData.password_confirm"
+            :type="showPasswordConfirm ? 'text' : 'password'"
+            class="admin--form-input"
+            placeholder="비밀번호를 다시 입력하세요"
+            required
+          >
+          <button
+            type="button"
+            class="admin--password-toggle"
+            @click="showPasswordConfirm = !showPasswordConfirm"
+          >
+            {{ showPasswordConfirm ? '👁️' : '👁️‍🗨️' }}
+          </button>
+        </div>
+      </div>
+
+      <!-- 관리자명 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">관리자명 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="이름을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 이메일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이메일 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.email"
+          type="email"
+          class="admin--form-input"
+          placeholder="email@example.com"
+          required
+        >
+      </div>
+
+      <!-- 인사말 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">인사말</label>
+        <textarea
+          v-model="formData.greeting"
+          class="admin--form-textarea"
+          placeholder="인사말을 입력하세요 (엔터로 줄바꿈 가능)"
+          rows="5"
+        ></textarea>
+        <p class="admin--form-help">영업사원 목록 페이지 상단에 표시됩니다.</p>
+      </div>
+
+      <!-- 사진 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">사진</label>
+        <input
+          type="file"
+          accept="image/*"
+          class="admin--form-file"
+          @change="handlePhotoUpload"
+        >
+        <div v-if="photoPreview" class="admin--image-preview">
+          <img :src="photoPreview" alt="미리보기">
+          <button type="button" class="admin--btn-remove-image" @click="removePhoto">
+            삭제
+          </button>
+        </div>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button
+          type="submit"
+          class="admin--btn admin--btn-primary"
+          :disabled="isSaving"
+        >
+          {{ isSaving ? '저장 중...' : '확인' }}
+        </button>
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="goToList"
+        >
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const router = useRouter()
+const { get, post, upload } = useApi()
+const { getImageUrl } = useImage()
+
+const isSaving = ref(false)
+const successMessage = ref('')
+const errorMessage = ref('')
+const showPassword = ref(false)
+const showPasswordConfirm = ref(false)
+const branches = ref([])
+const photoPreview = ref(null)
+const photoFile = ref(null)
+const isCheckingUserId = ref(false)
+const userIdCheckResult = ref('') // 'available', 'duplicate', ''
+
+const formData = ref({
+  branch_id: '',
+  user_id: '',
+  password: '',
+  password_confirm: '',
+  name: '',
+  email: '',
+  greeting: '',
+  photo_url: ''
+})
+
+// 지점 목록 로드
+const loadBranches = async () => {
+  const { data, error } = await get('/branch/list', { per_page: 1000 })
+  console.log('[BranchManagerCreate] API 응답:', { data, error })
+
+  if (data?.success && data?.data) {
+    branches.value = data.data.items || []
+    console.log('[BranchManagerCreate] 지점 목록 로드 성공')
+  }
+}
+
+// 아이디 입력 시 중복 체크 결과 초기화
+const handleUserIdInput = () => {
+  userIdCheckResult.value = ''
+}
+
+// 아이디 중복 체크
+const checkDuplicateUserId = async () => {
+  if (!formData.value.user_id) {
+    alert('아이디를 입력하세요.')
+    return
+  }
+
+  isCheckingUserId.value = true
+  userIdCheckResult.value = ''
+
+  try {
+    const { data, error } = await get('/branch/manager/check-userid', {
+      params: {
+        user_id: formData.value.user_id
+      }
+    })
+
+    console.log('[BranchManagerCreate] 중복 체크 응답:', { data, error })
+
+    if (error) {
+      alert('중복 체크에 실패했습니다.')
+      return
+    }
+
+    if (data?.success) {
+      if (data?.data?.available) {
+        userIdCheckResult.value = 'available'
+      } else {
+        userIdCheckResult.value = 'duplicate'
+      }
+    }
+  } catch (error) {
+    console.error('중복 체크 오류:', error)
+    alert('중복 체크 중 오류가 발생했습니다.')
+  } finally {
+    isCheckingUserId.value = false
+  }
+}
+
+// 사진 업로드
+const handlePhotoUpload = (event) => {
+  const file = event.target.files[0]
+  if (!file) return
+
+  if (!file.type.startsWith('image/')) {
+    alert('이미지 파일만 업로드 가능합니다.')
+    return
+  }
+
+  photoFile.value = file
+
+  const reader = new FileReader()
+  reader.onload = (e) => {
+    photoPreview.value = e.target.result
+  }
+  reader.readAsDataURL(file)
+}
+
+// 사진 삭제
+const removePhoto = () => {
+  photoPreview.value = null
+  photoFile.value = null
+  formData.value.photo_url = ''
+}
+
+// 폼 제출
+const handleSubmit = async () => {
+  successMessage.value = ''
+  errorMessage.value = ''
+
+  // 유효성 검사
+  if (!formData.value.branch_id) {
+    errorMessage.value = '지점을 선택하세요.'
+    return
+  }
+
+  if (!formData.value.user_id) {
+    errorMessage.value = '아이디를 입력하세요.'
+    return
+  }
+
+  if (userIdCheckResult.value !== 'available') {
+    errorMessage.value = '아이디 중복 체크를 해주세요.'
+    return
+  }
+
+  if (!formData.value.password) {
+    errorMessage.value = '비밀번호를 입력하세요.'
+    return
+  }
+
+  if (formData.value.password !== formData.value.password_confirm) {
+    errorMessage.value = '비밀번호가 일치하지 않습니다.'
+    return
+  }
+
+  if (!formData.value.name) {
+    errorMessage.value = '관리자명을 입력하세요.'
+    return
+  }
+
+  if (!formData.value.email) {
+    errorMessage.value = '이메일을 입력하세요.'
+    return
+  }
+
+  isSaving.value = true
+
+  try {
+    let photoUrl = formData.value.photo_url
+
+    // 새 사진 업로드
+    if (photoFile.value) {
+      const formDataImage = new FormData()
+      formDataImage.append('file', photoFile.value)
+
+      const { data: uploadData, error: uploadError } = await upload('/upload/bmanager-image', formDataImage)
+
+      console.log('[BranchManagerCreate] 이미지 업로드 응답:', { data: uploadData, error: uploadError })
+
+      if (uploadError) {
+        errorMessage.value = '사진 업로드에 실패했습니다: ' + (uploadError.message || uploadError)
+        isSaving.value = false
+        return
+      }
+
+      if (!uploadData?.success || !uploadData?.data?.url) {
+        errorMessage.value = '사진 업로드 응답이 올바르지 않습니다.'
+        isSaving.value = false
+        return
+      }
+
+      photoUrl = uploadData.data.url
+      console.log('[BranchManagerCreate] 업로드된 이미지 URL:', photoUrl)
+    }
+
+    const submitData = {
+      branch_id: formData.value.branch_id,
+      user_id: formData.value.user_id,
+      password: formData.value.password,
+      name: formData.value.name,
+      email: formData.value.email,
+      greeting: formData.value.greeting,
+      photo_url: photoUrl
+    }
+
+    const { data, error } = await post('/branch/manager', submitData)
+
+    if (error) {
+      errorMessage.value = error.message || '등록에 실패했습니다.'
+    } else {
+      successMessage.value = '지점장이 등록되었습니다.'
+      setTimeout(() => {
+        router.push('/site-manager/branch/manager')
+      }, 1000)
+    }
+  } catch (error) {
+    errorMessage.value = '서버 오류가 발생했습니다.'
+    console.error('Save error:', error)
+  } finally {
+    isSaving.value = false
+  }
+}
+
+// 목록으로 이동
+const goToList = () => {
+  router.push('/site-manager/branch/manager')
+}
+
+onMounted(() => {
+  loadBranches()
+})
+</script>

+ 362 - 0
app/pages/site-manager/branch/manager/edit/[id].vue

@@ -0,0 +1,362 @@
+<template>
+  <div class="admin--manager-form">
+    <div v-if="isLoading" class="admin--loading">
+      데이터를 불러오는 중...
+    </div>
+
+    <form v-else @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 지점명 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">지점명 <span class="admin--required">*</span></label>
+        <select
+          v-model="formData.branch_id"
+          class="admin--form-select"
+          required
+        >
+          <option value="">지점을 선택하세요</option>
+          <option
+            v-for="branch in branches"
+            :key="branch.id"
+            :value="branch.id"
+            :disabled="branch.is_active !== 1 && branch.is_active !== '1'"
+          >
+            {{ branch.name }}{{ (branch.is_active !== 1 && branch.is_active !== '1') ? ' (비활성화)' : '' }}
+          </option>
+        </select>
+      </div>
+
+      <!-- 아이디 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">아이디 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.user_id"
+          type="text"
+          class="admin--form-input"
+          placeholder="아이디를 입력하세요"
+          required
+          disabled
+        >
+        <p class="admin--form-help">아이디는 수정할 수 없습니다.</p>
+      </div>
+
+      <!-- 비밀번호 (선택사항) -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">비밀번호</label>
+        <div class="admin--password-input-wrapper">
+          <input
+            v-model="formData.password"
+            :type="showPassword ? 'text' : 'password'"
+            class="admin--form-input"
+            placeholder="변경할 비밀번호를 입력하세요"
+          >
+          <button
+            type="button"
+            class="admin--password-toggle"
+            @click="showPassword = !showPassword"
+          >
+            {{ showPassword ? '👁️' : '👁️‍🗨️' }}
+          </button>
+        </div>
+        <p class="admin--form-help">비밀번호를 변경하지 않으려면 비워두세요.</p>
+      </div>
+
+      <!-- 비밀번호 확인 -->
+      <div v-if="formData.password" class="admin--form-group">
+        <label class="admin--form-label">비밀번호 확인 <span class="admin--required">*</span></label>
+        <div class="admin--password-input-wrapper">
+          <input
+            v-model="formData.password_confirm"
+            :type="showPasswordConfirm ? 'text' : 'password'"
+            class="admin--form-input"
+            placeholder="비밀번호를 다시 입력하세요"
+            required
+          >
+          <button
+            type="button"
+            class="admin--password-toggle"
+            @click="showPasswordConfirm = !showPasswordConfirm"
+          >
+            {{ showPasswordConfirm ? '👁️' : '👁️‍🗨️' }}
+          </button>
+        </div>
+      </div>
+
+      <!-- 관리자명 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">관리자명 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="이름을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 이메일 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이메일 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.email"
+          type="email"
+          class="admin--form-input"
+          placeholder="email@example.com"
+          required
+        >
+      </div>
+
+      <!-- 인사말 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">인사말</label>
+        <textarea
+          v-model="formData.greeting"
+          class="admin--form-textarea"
+          placeholder="인사말을 입력하세요 (엔터로 줄바꿈 가능)"
+          rows="5"
+        ></textarea>
+        <p class="admin--form-help">영업사원 목록 페이지 상단에 표시됩니다.</p>
+      </div>
+
+      <!-- 사진 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">사진</label>
+        <input
+          type="file"
+          accept="image/*"
+          class="admin--form-file"
+          @change="handlePhotoUpload"
+        >
+        <div v-if="photoPreview || formData.photo_url" class="admin--image-preview">
+          <img :src="photoPreview || getImageUrl(formData.photo_url)" alt="미리보기">
+          <button type="button" class="admin--btn-remove-image" @click="removePhoto">
+            삭제
+          </button>
+        </div>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button
+          type="submit"
+          class="admin--btn admin--btn-primary"
+          :disabled="isSaving"
+        >
+          {{ isSaving ? '저장 중...' : '확인' }}
+        </button>
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="goToList"
+        >
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const route = useRoute()
+const router = useRouter()
+const { get, put, upload } = useApi()
+const { getImageUrl } = useImage()
+
+const isLoading = ref(true)
+const isSaving = ref(false)
+const successMessage = ref('')
+const errorMessage = ref('')
+const showPassword = ref(false)
+const showPasswordConfirm = ref(false)
+const branches = ref([])
+const photoPreview = ref(null)
+const photoFile = ref(null)
+
+const formData = ref({
+  branch_id: '',
+  user_id: '',
+  password: '',
+  password_confirm: '',
+  name: '',
+  email: '',
+  greeting: '',
+  photo_url: ''
+})
+
+// 지점 목록 로드
+const loadBranches = async () => {
+  const { data, error } = await get('/branch/list', { per_page: 1000 })
+  console.log('[BranchManagerEdit] API 응답:', { data, error })
+
+  if (data?.success && data?.data) {
+    branches.value = data.data.items || []
+    console.log('[BranchManagerEdit] 지점 목록 로드 성공')
+  }
+}
+
+// 데이터 로드
+const loadManager = async () => {
+  isLoading.value = true
+
+  const id = route.params.id
+  const { data, error } = await get(`/branch/manager/${id}`)
+  console.log('[BranchManagerEdit] 데이터 로드:', { data, error })
+
+  if (data?.success && data?.data) {
+    const manager = data.data
+    formData.value = {
+      branch_id: manager.branch_id || '',
+      user_id: manager.user_id || '',
+      password: '',
+      password_confirm: '',
+      name: manager.name || '',
+      email: manager.email || '',
+      greeting: manager.greeting || '',
+      photo_url: manager.photo_url || ''
+    }
+    photoPreview.value = null
+    console.log('[BranchManagerEdit] 로드 성공')
+  }
+
+  isLoading.value = false
+}
+
+// 사진 업로드
+const handlePhotoUpload = (event) => {
+  const file = event.target.files[0]
+  if (!file) return
+
+  if (!file.type.startsWith('image/')) {
+    alert('이미지 파일만 업로드 가능합니다.')
+    return
+  }
+
+  photoFile.value = file
+
+  const reader = new FileReader()
+  reader.onload = (e) => {
+    photoPreview.value = e.target.result
+  }
+  reader.readAsDataURL(file)
+}
+
+// 사진 삭제
+const removePhoto = () => {
+  photoPreview.value = null
+  photoFile.value = null
+  formData.value.photo_url = ''
+}
+
+// 폼 제출
+const handleSubmit = async () => {
+  successMessage.value = ''
+  errorMessage.value = ''
+
+  // 유효성 검사
+  if (!formData.value.branch_id) {
+    errorMessage.value = '지점을 선택하세요.'
+    return
+  }
+
+  if (formData.value.password && formData.value.password !== formData.value.password_confirm) {
+    errorMessage.value = '비밀번호가 일치하지 않습니다.'
+    return
+  }
+
+  if (!formData.value.name) {
+    errorMessage.value = '관리자명을 입력하세요.'
+    return
+  }
+
+  if (!formData.value.email) {
+    errorMessage.value = '이메일을 입력하세요.'
+    return
+  }
+
+  isSaving.value = true
+
+  try {
+    let photoUrl = formData.value.photo_url
+
+    // 새 사진 업로드
+    if (photoFile.value) {
+      const formDataImage = new FormData()
+      formDataImage.append('file', photoFile.value)
+
+      const { data: uploadData, error: uploadError } = await upload('/upload/bmanager-image', formDataImage)
+
+      console.log('[BranchManagerEdit] 이미지 업로드 응답:', { data: uploadData, error: uploadError })
+
+      if (uploadError) {
+        errorMessage.value = '사진 업로드에 실패했습니다: ' + (uploadError.message || uploadError)
+        isSaving.value = false
+        return
+      }
+
+      if (!uploadData?.success || !uploadData?.data?.url) {
+        errorMessage.value = '사진 업로드 응답이 올바르지 않습니다.'
+        isSaving.value = false
+        return
+      }
+
+      photoUrl = uploadData.data.url
+      console.log('[BranchManagerEdit] 업로드된 이미지 URL:', photoUrl)
+    }
+
+    const submitData = {
+      branch_id: formData.value.branch_id,
+      name: formData.value.name,
+      email: formData.value.email,
+      greeting: formData.value.greeting,
+      photo_url: photoUrl
+    }
+
+    // 비밀번호가 입력된 경우에만 포함
+    if (formData.value.password) {
+      submitData.password = formData.value.password
+    }
+
+    const id = route.params.id
+    const { data, error } = await put(`/branch/manager/${id}`, submitData)
+
+    if (error) {
+      errorMessage.value = error.message || '수정에 실패했습니다.'
+    } else {
+      successMessage.value = '지점장 정보가 수정되었습니다.'
+      setTimeout(() => {
+        router.push('/site-manager/branch/manager')
+      }, 1000)
+    }
+  } catch (error) {
+    errorMessage.value = '서버 오류가 발생했습니다.'
+    console.error('Save error:', error)
+  } finally {
+    isSaving.value = false
+  }
+}
+
+// 목록으로 이동
+const goToList = () => {
+  router.push('/site-manager/branch/manager')
+}
+
+onMounted(async () => {
+  await loadBranches()
+  await loadManager()
+})
+</script>

+ 257 - 0
app/pages/site-manager/branch/manager/index.vue

@@ -0,0 +1,257 @@
+<template>
+  <div class="admin--manager-list">
+    <!-- 검색 영역 -->
+    <div class="admin--search-box">
+      <div class="admin--search-form">
+        <select v-model="searchType" class="admin--form-select admin--search-select">
+          <option value="branch_name">지점명</option>
+          <option value="name">이름</option>
+          <option value="user_id">아이디</option>
+          <option value="email">이메일</option>
+        </select>
+        <input
+          v-model="searchKeyword"
+          type="text"
+          class="admin--form-input admin--search-input"
+          placeholder="검색어를 입력하세요"
+          @keyup.enter="handleSearch"
+        >
+        <button class="admin--btn-small admin--btn-small-primary" @click="handleSearch">
+          검색
+        </button>
+        <button class="admin--btn-small admin--btn-small-secondary" @click="handleReset">
+          초기화
+        </button>
+      </div>
+      <div class="admin--search-actions">
+        <button class="admin--btn-small admin--btn-small-primary" @click="goToCreate">
+          + 지점장 등록
+        </button>
+      </div>
+    </div>
+
+    <!-- 테이블 -->
+    <div class="admin--table-wrapper">
+      <table class="admin--table">
+        <thead>
+          <tr>
+            <th>NO</th>
+            <th>지점명</th>
+            <th>이름</th>
+            <th>아이디</th>
+            <th>이메일</th>
+            <th>관리</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="!managers || managers.length === 0">
+            <td colspan="6" class="admin--table-empty">
+              등록된 지점장이 없습니다.
+            </td>
+          </tr>
+          <tr v-else v-for="(manager, index) in managers" :key="manager.id">
+            <td>{{ totalCount - ((currentPage - 1) * perPage + index) }}</td>
+            <td>{{ manager.branch_name }}</td>
+            <td class="admin--table-title">{{ manager.name }}</td>
+            <td>{{ manager.username }}</td>
+            <td>{{ manager.email }}</td>
+            <td>
+              <div class="admin--table-actions">
+                <button
+                  class="admin--btn-small admin--btn-small-primary"
+                  @click="goToEdit(manager.id)"
+                >
+                  수정
+                </button>
+                <button
+                  class="admin--btn-small admin--btn-small-danger"
+                  @click="handleDelete(manager.id)"
+                >
+                  삭제
+                </button>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <!-- 페이지네이션 -->
+    <div v-if="totalPages > 1" class="admin--pagination">
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(1)"
+        title="처음"
+      >
+        ⏮
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(currentPage - 1)"
+        title="이전"
+      >
+        ◀
+      </button>
+      <button
+        v-for="page in visiblePages"
+        :key="page"
+        class="admin--pagination-btn"
+        :class="{ 'is-active': page === currentPage }"
+        @click="changePage(page)"
+      >
+        {{ page }}
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(currentPage + 1)"
+        title="다음"
+      >
+        ▶
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(totalPages)"
+        title="끝"
+      >
+        ⏭
+      </button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const router = useRouter()
+const { get, del } = useApi()
+const managers = ref([])
+const searchType = ref('branch_name')
+const searchKeyword = ref('')
+const currentPage = ref(1)
+const perPage = ref(10)
+const totalCount = ref(0)
+const totalPages = ref(0)
+
+// 보이는 페이지 번호 계산
+const visiblePages = computed(() => {
+  const pages = []
+  const maxVisible = 5
+  let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2))
+  let end = Math.min(totalPages.value, start + maxVisible - 1)
+
+  if (end - start < maxVisible - 1) {
+    start = Math.max(1, end - maxVisible + 1)
+  }
+
+  for (let i = start; i <= end; i++) {
+    pages.push(i)
+  }
+
+  return pages
+})
+
+// 데이터 로드
+const loadManagers = async () => {
+  const params = {
+    page: currentPage.value,
+    per_page: perPage.value
+  }
+
+  if (searchKeyword.value) {
+    params.search_type = searchType.value
+    params.search_keyword = searchKeyword.value
+  }
+
+  const { data, error } = await get('/branch/manager', { params })
+
+  console.log('[BranchManager] API 응답:', { data, error })
+
+  // API 응답: { success: true, data: { items, total }, message }
+  if (data?.success && data?.data) {
+    managers.value = data.data.items || []
+    totalCount.value = data.data.total || 0
+    totalPages.value = Math.ceil(totalCount.value / perPage.value)
+    console.log('[BranchManager] 로드 성공:', managers.value.length)
+  }
+}
+
+// 검색
+const handleSearch = () => {
+  currentPage.value = 1
+  loadManagers()
+}
+
+// 초기화
+const handleReset = () => {
+  searchType.value = 'branch_name'
+  searchKeyword.value = ''
+  currentPage.value = 1
+  loadManagers()
+}
+
+// 페이지 변경
+const changePage = (page) => {
+  if (page < 1 || page > totalPages.value) return
+  currentPage.value = page
+  loadManagers()
+  window.scrollTo({ top: 0, behavior: 'smooth' })
+}
+
+// 등록 페이지로 이동
+const goToCreate = () => {
+  router.push('/site-manager/branch/manager/create')
+}
+
+// 수정 페이지로 이동
+const goToEdit = (id) => {
+  router.push(`/site-manager/branch/manager/edit/${id}`)
+}
+
+// 삭제
+const handleDelete = async (id) => {
+  if (!confirm('정말 삭제하시겠습니까?')) return
+
+  const { error } = await del(`/branch/manager/${id}`)
+
+  if (error) {
+    alert('삭제에 실패했습니다.')
+  } else {
+    alert('삭제되었습니다.')
+    loadManagers()
+  }
+}
+
+onMounted(() => {
+  loadManagers()
+})
+</script>
+
+<style scoped>
+.admin--search-actions .admin--btn-primary {
+  background: var(--admin-accent-primary);
+  color: white;
+  border-color: var(--admin-accent-primary);
+  font-weight: 500;
+  padding: 8px 18px;
+  font-size: 13px;
+  border-radius: 8px;
+  transition: all 0.3s ease;
+}
+
+.admin--search-actions .admin--btn-primary:hover {
+  background: var(--admin-accent-hover);
+  border-color: var(--admin-accent-hover);
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+</style>

+ 83 - 0
app/pages/site-manager/dashboard.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="admin--dashboard">
+    <div class="admin--dashboard-stats">
+      <div class="admin--stat-card">
+        <div class="admin--stat-icon">📊</div>
+        <div class="admin--stat-content">
+          <h4>총 팝업</h4>
+          <p class="admin--stat-number">{{ stats.popups }}</p>
+        </div>
+      </div>
+
+      <div class="admin--stat-card">
+        <div class="admin--stat-icon">🏢</div>
+        <div class="admin--stat-content">
+          <h4>총 지점</h4>
+          <p class="admin--stat-number">{{ stats.branches }}</p>
+        </div>
+      </div>
+
+      <div class="admin--stat-card">
+        <div class="admin--stat-icon">👥</div>
+        <div class="admin--stat-content">
+          <h4>총 직원</h4>
+          <p class="admin--stat-number">{{ stats.employees }}</p>
+        </div>
+      </div>
+
+      <div class="admin--stat-card">
+        <div class="admin--stat-icon">📝</div>
+        <div class="admin--stat-content">
+          <h4>브로셔 요청</h4>
+          <p class="admin--stat-number">{{ stats.brochures }}</p>
+        </div>
+      </div>
+    </div>
+
+    <div class="admin--dashboard-recent">
+      <h4>최근 활동</h4>
+      <div class="admin--recent-list">
+        <p class="admin--no-data">최근 활동 내역이 없습니다.</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const stats = ref({
+  popups: 0,
+  branches: 0,
+  employees: 0,
+  brochures: 0
+})
+
+const { get } = useApi()
+
+const loadDashboardStats = async () => {
+  const { data, error } = await get('/dashboard/stats')
+
+  console.log('[Dashboard] API 응답:', { data, error })
+
+  if (error) {
+    console.error('[Dashboard] 통계 로드 실패:', error)
+    return
+  }
+
+  // API 응답: { success: true, data: { popups, branches, employees, brochures } }
+  if (data?.success && data?.data) {
+    stats.value = data.data
+    console.log('[Dashboard] 통계 로드 성공:', stats.value)
+  }
+}
+
+onMounted(() => {
+  loadDashboardStats()
+})
+</script>

+ 216 - 0
app/pages/site-manager/index.vue

@@ -0,0 +1,216 @@
+<template>
+  <div class="admin--login">
+    <div class="login--container">
+      <div class="login--layout">
+        <div class="login--layout-inner">
+          <div class="login--brand">
+            <div class="login--brand--logo">
+              🏴‍☠️
+            </div>
+            <div class="login--brand--txt">
+              <h1 class="login--brand-title">파이럿존</h1>
+              <p class="login--brand-tagline">ADMIN SYSTEM</p>
+            </div>
+          </div>
+          <div class="login--deco--line">
+            <span class="circle1"></span>
+            <span class="circle2"></span>
+            <span class="circle3">🧭</span>
+          </div>
+          <div class="login--intro">
+            <h2 class="login--intro-title">낚시를 모험으로</h2>
+            <p class="login--intro-desc">
+              Fishing as Adventure
+            </p>
+            <ul class="login--intro-list">
+              파이럿존 관리자 시스템 v1.0 <br />챌린지 · 퀘스트 · 회원 · 진출권을 관리합니다.
+            </ul>
+          </div>
+        </div>
+      </div>
+      <div class="login--panel">
+        <div class="login--box">
+          <div class="login--logo">
+            <span>환영합니다 👋</span>
+            <h1>어드민 로그인</h1>
+            <p class="subtitle">관리자 권한이 있는 계정으로 로그인하세요.</p>
+          </div>
+
+          <form @submit.prevent="handleLogin" class="login--form">
+          <div v-if="errorMessage" class="login--error">
+            {{ errorMessage }}
+          </div>
+
+          <div class="form--group">
+            <input
+              v-model="username"
+              type="text"
+              placeholder="아이디"
+              class="form--input"
+              required
+              :disabled="isLoading"
+            />
+          </div>
+
+          <div class="form--group">
+            <input
+              v-model="password"
+              type="password"
+              placeholder="비밀번호"
+              class="form--input"
+              required
+              :disabled="isLoading"
+            />
+          </div>
+
+          <div class="form--options">
+            <label class="checkbox--label">
+              <input type="checkbox" v-model="rememberMe" :disabled="isLoading" />
+              <span>로그인 상태 유지</span>
+            </label>
+            <!-- <a href="#" class="forgot--password">비밀번호 찾기</a> -->
+          </div>
+
+          <button type="submit" class="login--button" :disabled="isLoading">
+            {{ isLoading ? "로그인 중..." : "로그인 →" }}
+          </button>
+          </form>
+        </div>
+      </div>
+    </div>
+
+    <!-- 비밀번호 변경 권장 모달 -->
+    <PasswordChangeRecommendModal
+      v-if="showPasswordChangeModal"
+      :admin-id="loggedInAdminId"
+      @close="closePasswordChangeModal"
+      @changed="handlePasswordChanged"
+    />
+
+    <!-- 계정 잠금 모달 -->
+    <AccountLockedModal v-if="showAccountLockedModal" @close="closeAccountLockedModal" />
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+  import PasswordChangeRecommendModal from "~/components/admin/PasswordChangeRecommendModal.vue";
+  import AccountLockedModal from "~/components/admin/AccountLockedModal.vue";
+
+  definePageMeta({
+    layout: false,
+    middleware: ["auth"],
+  });
+
+  const { post } = useApi();
+
+  const username = ref("");
+  const password = ref("");
+  const rememberMe = ref(false);
+  const isLoading = ref(false);
+  const errorMessage = ref("");
+  const showPasswordChangeModal = ref(false);
+  const showAccountLockedModal = ref(false);
+  const loggedInAdminId = ref(null);
+
+  const handleLogin = async () => {
+    if (!username.value || !password.value) {
+      errorMessage.value = "아이디와 비밀번호를 입력해주세요.";
+      return;
+    }
+
+    isLoading.value = true;
+    errorMessage.value = "";
+
+    try {
+      console.log("[Login] 로그인 시도:", username.value);
+
+      const { data, error } = await post("/auth/login", {
+        username: username.value,
+        password: password.value,
+        remember: rememberMe.value,
+      });
+
+      console.log("[Login] API 응답:", { data, error });
+
+      if (error) {
+        console.error("[Login] 에러 발생:", error);
+        const errorMsg = error.message || "로그인에 실패했습니다.";
+
+        // 403 에러이고 계정 잠금 메시지인 경우 모달 표시
+        if (
+          error.statusCode === 403 ||
+          errorMsg.includes("계정이 잠겼습니다") ||
+          errorMsg.includes("슈퍼 관리자에게 문의")
+        ) {
+          showAccountLockedModal.value = true;
+          errorMessage.value = "";
+        } else {
+          errorMessage.value = errorMsg;
+        }
+        return;
+      }
+
+      // API 응답: { success: true, data: { token, admin, password_change_needed }, message }
+      if (data?.success && data?.data?.token) {
+        console.log(
+          "[Login] 로그인 성공, 토큰 저장:",
+          data.data.token.substring(0, 20) + "..."
+        );
+
+        localStorage.setItem("admin_token", data.data.token);
+
+        if (data.data.admin) {
+          localStorage.setItem("admin_user", JSON.stringify(data.data.admin));
+          loggedInAdminId.value = data.data.admin.id;
+        }
+
+        console.log(
+          "[Login] localStorage 확인:",
+          localStorage.getItem("admin_token") ? "저장됨" : "저장 실패"
+        );
+
+        // 비밀번호 변경이 필요한 경우 모달 표시
+        if (data.data.password_change_needed) {
+          console.log("[Login] 비밀번호 변경 필요, 모달 표시");
+          showPasswordChangeModal.value = true;
+        } else {
+          // 비밀번호 변경이 필요 없으면 바로 dashboard로 이동
+          console.log("[Login] dashboard로 이동 시도...");
+          await navigateTo("/site-manager/dashboard");
+          console.log("[Login] navigateTo 완료");
+        }
+      } else {
+        console.error("[Login] 잘못된 응답 형식:", data);
+        errorMessage.value = data?.message || "로그인 정보가 올바르지 않습니다.";
+      }
+    } catch (error) {
+      errorMessage.value = "서버 오류가 발생했습니다.";
+      console.error("[Login] Exception:", error);
+    } finally {
+      isLoading.value = false;
+    }
+  };
+
+  const closePasswordChangeModal = async () => {
+    showPasswordChangeModal.value = false;
+    // 모달을 닫으면 dashboard로 이동
+    await navigateTo("/site-manager/dashboard");
+  };
+
+  const handlePasswordChanged = async (message) => {
+    showPasswordChangeModal.value = false;
+    // 비밀번호 변경 성공 후 dashboard로 이동
+    if (message) {
+      console.log("[Login] 비밀번호 변경 성공:", message);
+    }
+    await navigateTo("/site-manager/dashboard");
+  };
+
+  const closeAccountLockedModal = () => {
+    showAccountLockedModal.value = false;
+    // 계정 잠금 모달 닫으면 에러 메시지 지우고 로그인 폼 초기화
+    errorMessage.value = "";
+    password.value = "";
+  };
+</script>

+ 293 - 0
app/pages/site-manager/service-center/create.vue

@@ -0,0 +1,293 @@
+<template>
+  <div class="admin--service-center-form">
+    <form @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 서비스센터명 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >서비스센터명 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="서비스센터명을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 소속명 선택 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >딜러사 명 <span class="admin--required">*</span></label
+        >
+        <select v-model="formData.branch_id" class="admin--form-select" required>
+          <option value="">소속 지점을 선택하세요</option>
+          <option v-for="branch in branches" :key="branch.id" :value="branch.id">
+            {{ branch.name }}
+          </option>
+        </select>
+      </div>
+
+      <!-- 대표번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >대표번호 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.phone"
+          type="tel"
+          class="admin--form-input"
+          placeholder="02-1234-5678"
+          required
+        />
+      </div>
+
+      <!-- 주소 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >주소 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.address"
+          type="text"
+          class="admin--form-input"
+          placeholder="주소를 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 상세주소 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">상세주소</label>
+        <input
+          v-model="formData.detail_address"
+          type="text"
+          class="admin--form-input"
+          placeholder="상세주소를 입력하세요"
+        />
+      </div>
+
+      <!-- 위도/경도 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">위치 좌표</label>
+        <div class="admin--coordinate-group">
+          <div class="admin--coordinate-item">
+            <label>위도</label>
+            <input
+              v-model.number="formData.latitude"
+              type="text"
+              step="any"
+              class="admin--form-input"
+              placeholder="37.5665"
+            />
+          </div>
+          <div class="admin--coordinate-item">
+            <label>경도</label>
+            <input
+              v-model.number="formData.longitude"
+              type="text"
+              step="any"
+              class="admin--form-input"
+              placeholder="126.9780"
+            />
+          </div>
+        </div>
+      </div>
+
+      <!-- 영업시간 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">영업시간</label>
+        <textarea
+          v-model="formData.business_hours"
+          class="admin--form-textarea"
+          rows="3"
+          placeholder="평일: 09:00 - 18:00&#10;주말: 10:00 - 17:00"
+        ></textarea>
+      </div>
+
+      <!-- 서비스예약 링크 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">서비스예약 링크</label>
+        <input
+          v-model="formData.service_reservation_link"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com/service-reservation"
+        />
+      </div>
+
+      <!-- 링크 관리 -->
+      <!-- <div class="admin--form-group">
+        <label class="admin--form-label">관련 링크</label>
+        <div class="admin--multi-input-wrapper">
+          <div
+            v-for="(link, index) in formData.links"
+            :key="index"
+            class="admin--multi-input-item"
+          >
+            <input
+              v-model="formData.links[index]"
+              type="url"
+              class="admin--form-input"
+              placeholder="https://example.com"
+            />
+            <button type="button" class="admin--btn-remove" @click="removeLink(index)">
+              삭제
+            </button>
+          </div>
+          <button type="button" class="admin--btn-add" @click="addLink">
+            + 링크 추가
+          </button>
+        </div>
+      </div> -->
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
+          {{ isSaving ? "저장 중..." : "확인" }}
+        </button>
+        <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+  import { ref, onMounted } from "vue";
+  import { useRouter } from "vue-router";
+
+  definePageMeta({
+    layout: "admin",
+    middleware: ["auth"],
+  });
+
+  const router = useRouter();
+  const { get, post } = useApi();
+
+  const isSaving = ref(false);
+  const successMessage = ref("");
+  const errorMessage = ref("");
+  const branches = ref([]);
+
+  const formData = ref({
+    name: "",
+    branch_id: "",
+    phone: "",
+    address: "",
+    detail_address: "",
+    latitude: null,
+    longitude: null,
+    business_hours: "",
+    service_reservation_link: "",
+    links: [""], // 초기 링크 하나
+  });
+
+  // 지점 목록 로드
+  const loadBranches = async () => {
+    const { data, error } = await get("/branch/list", { params: { per_page: 1000 } });
+
+    if (data?.success && data?.data?.items) {
+      branches.value = data.data.items.filter((branch) => branch.is_active == 1);
+    }
+  };
+
+  // 링크 추가
+  const addLink = () => {
+    formData.value.links.push("");
+  };
+
+  // 링크 삭제
+  const removeLink = (index) => {
+    formData.value.links.splice(index, 1);
+  };
+
+  // 폼 제출
+  const handleSubmit = async () => {
+    successMessage.value = "";
+    errorMessage.value = "";
+
+    // 유효성 검사
+    if (!formData.value.name) {
+      errorMessage.value = "서비스센터명을 입력하세요.";
+      return;
+    }
+
+    if (!formData.value.branch_id) {
+      errorMessage.value = "소속 지점을 선택하세요.";
+      return;
+    }
+
+    if (!formData.value.phone) {
+      errorMessage.value = "대표번호를 입력하세요.";
+      return;
+    }
+
+    if (!formData.value.address) {
+      errorMessage.value = "주소를 입력하세요.";
+      return;
+    }
+
+    isSaving.value = true;
+
+    try {
+      // 빈 링크 제거
+      const submitData = {
+        ...formData.value,
+        links: formData.value.links.filter((link) => link.trim() !== ""),
+      };
+
+      const { data, error } = await post("/service-center", submitData);
+
+      if (error || !data?.success) {
+        errorMessage.value = error?.message || data?.message || "등록에 실패했습니다.";
+      } else {
+        successMessage.value = data.message || "서비스센터가 등록되었습니다.";
+        setTimeout(() => {
+          router.push("/site-manager/service-center/list");
+        }, 1000);
+      }
+    } catch (error) {
+      errorMessage.value = "서버 오류가 발생했습니다.";
+      console.error("Save error:", error);
+    } finally {
+      isSaving.value = false;
+    }
+  };
+
+  // 목록으로 이동
+  const goToList = () => {
+    router.push("/site-manager/service-center/list");
+  };
+
+  onMounted(() => {
+    loadBranches();
+  });
+</script>
+
+<style scoped>
+  .admin--coordinate-group {
+    display: flex;
+    gap: 16px;
+  }
+
+  .admin--coordinate-item {
+    flex: 1;
+  }
+
+  .admin--coordinate-item label {
+    display: block;
+    margin-bottom: 8px;
+    font-size: 14px;
+    color: #666;
+  }
+</style>

+ 327 - 0
app/pages/site-manager/service-center/edit/[id].vue

@@ -0,0 +1,327 @@
+<template>
+  <div class="admin--service-center-form">
+    <div v-if="isLoading" class="admin--loading">데이터를 불러오는 중...</div>
+    <form v-else @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 서비스센터명 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >서비스센터명 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="서비스센터명을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 소속명 선택 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >딜러사 명 <span class="admin--required">*</span></label
+        >
+        <select v-model="formData.branch_id" class="admin--form-select" required>
+          <option value="">소속 지점을 선택하세요</option>
+          <option v-for="branch in branches" :key="branch.id" :value="branch.id">
+            {{ branch.name }}
+          </option>
+        </select>
+      </div>
+
+      <!-- 대표번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >대표번호 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.phone"
+          type="tel"
+          class="admin--form-input"
+          placeholder="02-1234-5678"
+          required
+        />
+      </div>
+
+      <!-- 주소 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >주소 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.address"
+          type="text"
+          class="admin--form-input"
+          placeholder="주소를 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 상세주소 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">상세주소</label>
+        <input
+          v-model="formData.detail_address"
+          type="text"
+          class="admin--form-input"
+          placeholder="상세주소를 입력하세요"
+        />
+      </div>
+
+      <!-- 위도/경도 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">위치 좌표</label>
+        <div class="admin--coordinate-group">
+          <div class="admin--coordinate-item">
+            <label>위도</label>
+            <input
+              v-model.number="formData.latitude"
+              type="text"
+              step="any"
+              class="admin--form-input"
+              placeholder="37.5665"
+            />
+          </div>
+          <div class="admin--coordinate-item">
+            <label>경도</label>
+            <input
+              v-model.number="formData.longitude"
+              type="text"
+              step="any"
+              class="admin--form-input"
+              placeholder="126.9780"
+            />
+          </div>
+        </div>
+      </div>
+
+      <!-- 영업시간 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">영업시간</label>
+        <textarea
+          v-model="formData.business_hours"
+          class="admin--form-textarea"
+          rows="3"
+          placeholder="평일: 09:00 - 18:00&#10;주말: 10:00 - 17:00"
+        ></textarea>
+      </div>
+
+      <!-- 서비스예약 링크 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">서비스예약 링크</label>
+        <input
+          v-model="formData.service_reservation_link"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com/service-reservation"
+        />
+      </div>
+
+      <!-- 링크 관리 -->
+      <!-- <div class="admin--form-group">
+        <label class="admin--form-label">관련 링크</label>
+        <div class="admin--multi-input-wrapper">
+          <div
+            v-for="(link, index) in formData.links"
+            :key="index"
+            class="admin--multi-input-item"
+          >
+            <input
+              v-model="formData.links[index]"
+              type="url"
+              class="admin--form-input"
+              placeholder="https://example.com"
+            />
+            <button type="button" class="admin--btn-remove" @click="removeLink(index)">
+              삭제
+            </button>
+          </div>
+          <button type="button" class="admin--btn-add" @click="addLink">
+            + 링크 추가
+          </button>
+        </div>
+      </div> -->
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
+          {{ isSaving ? "저장 중..." : "확인" }}
+        </button>
+        <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+  import { ref, onMounted } from "vue";
+  import { useRouter, useRoute } from "vue-router";
+
+  definePageMeta({
+    layout: "admin",
+    middleware: ["auth"],
+  });
+
+  const router = useRouter();
+  const route = useRoute();
+  const { get, put } = useApi();
+
+  const isLoading = ref(true);
+  const isSaving = ref(false);
+  const successMessage = ref("");
+  const errorMessage = ref("");
+  const branches = ref([]);
+
+  const formData = ref({
+    name: "",
+    branch_id: "",
+    phone: "",
+    address: "",
+    detail_address: "",
+    latitude: null,
+    longitude: null,
+    business_hours: "",
+    service_reservation_link: "",
+    links: [],
+  });
+
+  // 지점 목록 로드
+  const loadBranches = async () => {
+    const { data, error } = await get("/branch/list", { params: { per_page: 1000 } });
+
+    if (data?.success && data?.data?.items) {
+      branches.value = data.data.items.filter((branch) => branch.is_active == 1);
+    }
+  };
+
+  // 서비스센터 데이터 로드
+  const loadServiceCenter = async () => {
+    const id = route.params.id;
+
+    const { data, error } = await get(`/service-center/${id}`);
+
+    if (data?.success && data?.data) {
+      const serviceCenter = data.data;
+      formData.value = {
+        name: serviceCenter.name || "",
+        branch_id: serviceCenter.branch_id || "",
+        phone: serviceCenter.main_phone || "",
+        address: serviceCenter.address || "",
+        detail_address: serviceCenter.detail_address || "",
+        latitude: serviceCenter.latitude || null,
+        longitude: serviceCenter.longitude || null,
+        business_hours: serviceCenter.business_hours || "",
+        service_reservation_link: serviceCenter.service_reservation_link || "",
+        links:
+          serviceCenter.links && serviceCenter.links.length > 0
+            ? serviceCenter.links
+            : [""],
+      };
+    }
+
+    isLoading.value = false;
+  };
+
+  // 링크 추가
+  const addLink = () => {
+    formData.value.links.push("");
+  };
+
+  // 링크 삭제
+  const removeLink = (index) => {
+    formData.value.links.splice(index, 1);
+  };
+
+  // 폼 제출
+  const handleSubmit = async () => {
+    successMessage.value = "";
+    errorMessage.value = "";
+
+    // 유효성 검사
+    if (!formData.value.name) {
+      errorMessage.value = "서비스센터명을 입력하세요.";
+      return;
+    }
+
+    if (!formData.value.branch_id) {
+      errorMessage.value = "소속 지점을 선택하세요.";
+      return;
+    }
+
+    if (!formData.value.phone) {
+      errorMessage.value = "대표번호를 입력하세요.";
+      return;
+    }
+
+    if (!formData.value.address) {
+      errorMessage.value = "주소를 입력하세요.";
+      return;
+    }
+
+    isSaving.value = true;
+
+    try {
+      const id = route.params.id;
+
+      // 빈 링크 제거
+      const submitData = {
+        ...formData.value,
+        links: formData.value.links.filter((link) => link.trim() !== ""),
+      };
+
+      const { data, error } = await put(`/service-center/${id}`, submitData);
+
+      if (error || !data?.success) {
+        errorMessage.value = error?.message || data?.message || "수정에 실패했습니다.";
+      } else {
+        successMessage.value = data.message || "서비스센터가 수정되었습니다.";
+        setTimeout(() => {
+          router.push("/site-manager/service-center/list");
+        }, 1000);
+      }
+    } catch (error) {
+      errorMessage.value = "서버 오류가 발생했습니다.";
+      console.error("Save error:", error);
+    } finally {
+      isSaving.value = false;
+    }
+  };
+
+  // 목록으로 이동
+  const goToList = () => {
+    router.push("/site-manager/service-center/list");
+  };
+
+  onMounted(async () => {
+    await loadBranches();
+    await loadServiceCenter();
+  });
+</script>
+
+<style scoped>
+  .admin--coordinate-group {
+    display: flex;
+    gap: 16px;
+  }
+
+  .admin--coordinate-item {
+    flex: 1;
+  }
+
+  .admin--coordinate-item label {
+    display: block;
+    margin-bottom: 8px;
+    font-size: 14px;
+    color: #666;
+  }
+</style>

+ 337 - 0
app/pages/site-manager/service-center/list.vue

@@ -0,0 +1,337 @@
+<template>
+  <div class="admin--service-center-list">
+    <!-- 상단 버튼 -->
+    <div class="admin--search-box">
+      <div class="admin--search-form"></div>
+      <div class="admin--search-actions">
+        <button class="admin--btn admin--btn-primary" @click="goToCreate">
+          + 서비스센터 등록
+        </button>
+      </div>
+    </div>
+
+    <!-- 테이블 -->
+    <div class="admin--table-wrapper">
+      <table class="admin--table">
+        <thead>
+          <tr>
+            <th>NO</th>
+            <th>서비스센터명</th>
+            <th>소속명</th>
+            <th>대표번호</th>
+            <th>주소</th>
+            <th>상태</th>
+            <th>관리</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="isLoading">
+            <td colspan="7" class="admin--table-loading">
+              데이터를 불러오는 중...
+            </td>
+          </tr>
+          <tr v-else-if="!serviceCenters || serviceCenters.length === 0">
+            <td colspan="7" class="admin--table-empty">
+              등록된 서비스센터가 없습니다.
+            </td>
+          </tr>
+          <tr v-else v-for="(serviceCenter, index) in serviceCenters" :key="serviceCenter.id">
+            <td>{{ totalCount - ((currentPage - 1) * perPage + index) }}</td>
+            <td class="admin--table-title">{{ serviceCenter.name }}</td>
+            <td>{{ serviceCenter.branch_name || '-' }}</td>
+            <td>{{ serviceCenter.main_phone }}</td>
+            <td>{{ serviceCenter.address }}</td>
+            <td>
+              <button
+                class="admin--toggle-btn"
+                :class="{ 'is-active': serviceCenter.is_active == 1 }"
+                @click="toggleActive(serviceCenter.id, serviceCenter.is_active)"
+              >
+                {{ serviceCenter.is_active == 1 ? '사용' : '비사용' }}
+              </button>
+            </td>
+            <td>
+              <div class="admin--table-actions">
+                <button
+                  class="admin--btn-small admin--btn-small-primary"
+                  @click="goToEdit(serviceCenter.id)"
+                >
+                  수정
+                </button>
+                <button
+                  class="admin--btn-small admin--btn-small-danger"
+                  @click="deleteServiceCenter(serviceCenter.id)"
+                >
+                  삭제
+                </button>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <!-- 페이지네이션 -->
+    <div v-if="totalPages > 1" class="admin--pagination">
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(currentPage - 1)"
+      >
+        이전
+      </button>
+      <button
+        v-for="page in visiblePages"
+        :key="page"
+        class="admin--pagination-btn"
+        :class="{ 'is-active': page === currentPage }"
+        @click="changePage(page)"
+      >
+        {{ page }}
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(currentPage + 1)"
+      >
+        다음
+      </button>
+    </div>
+
+    <!-- 알림 모달 -->
+    <AdminAlertModal
+      v-if="alertModal.show"
+      :title="alertModal.title"
+      :message="alertModal.message"
+      :type="alertModal.type"
+      @confirm="handleAlertConfirm"
+      @cancel="handleAlertCancel"
+      @close="closeAlertModal"
+    />
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import AdminAlertModal from '~/components/admin/AdminAlertModal.vue'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const router = useRouter()
+const { get, del, post } = useApi()
+
+const isLoading = ref(false)
+const serviceCenters = ref([])
+const currentPage = ref(1)
+const perPage = ref(10)
+const totalCount = ref(0)
+const totalPages = ref(0)
+
+// 알림 모달
+const alertModal = ref({
+  show: false,
+  title: '알림',
+  message: '',
+  type: 'alert',
+  onConfirm: null
+})
+
+// 알림 모달 표시
+const showAlert = (message, title = '알림') => {
+  alertModal.value = {
+    show: true,
+    title,
+    message,
+    type: 'alert',
+    onConfirm: null
+  }
+}
+
+// 확인 모달 표시
+const showConfirm = (message, onConfirm, title = '확인') => {
+  alertModal.value = {
+    show: true,
+    title,
+    message,
+    type: 'confirm',
+    onConfirm
+  }
+}
+
+// 알림 모달 닫기
+const closeAlertModal = () => {
+  alertModal.value.show = false
+}
+
+// 알림 모달 확인
+const handleAlertConfirm = () => {
+  if (alertModal.value.onConfirm) {
+    alertModal.value.onConfirm()
+  }
+  closeAlertModal()
+}
+
+// 알림 모달 취소
+const handleAlertCancel = () => {
+  closeAlertModal()
+}
+
+// 보이는 페이지 번호 계산
+const visiblePages = computed(() => {
+  const pages = []
+  const maxVisible = 5
+  let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2))
+  let end = Math.min(totalPages.value, start + maxVisible - 1)
+
+  if (end - start < maxVisible - 1) {
+    start = Math.max(1, end - maxVisible + 1)
+  }
+
+  for (let i = start; i <= end; i++) {
+    pages.push(i)
+  }
+
+  return pages
+})
+
+// 데이터 로드
+const loadServiceCenters = async () => {
+  isLoading.value = true
+
+  const params = {
+    page: currentPage.value,
+    per_page: perPage.value
+  }
+
+  const { data, error } = await get('/service-center/list', { params })
+
+  console.log('[ServiceCenterList] API 응답:', { data, error })
+
+  if (data?.success && data?.data) {
+    serviceCenters.value = data.data.items || []
+    totalCount.value = data.data.total || 0
+    totalPages.value = Math.ceil(totalCount.value / perPage.value)
+    console.log('[ServiceCenterList] 로드 성공:', serviceCenters.value.length)
+  }
+
+  isLoading.value = false
+}
+
+// 페이지 변경
+const changePage = (page) => {
+  if (page < 1 || page > totalPages.value) return
+  currentPage.value = page
+  loadServiceCenters()
+  window.scrollTo({ top: 0, behavior: 'smooth' })
+}
+
+// 서비스센터 등록 페이지로 이동
+const goToCreate = () => {
+  router.push('/site-manager/service-center/create')
+}
+
+// 서비스센터 수정 페이지로 이동
+const goToEdit = (id) => {
+  router.push(`/site-manager/service-center/edit/${id}`)
+}
+
+// 서비스센터 삭제
+const deleteServiceCenter = (id) => {
+  showConfirm(
+    '정말 삭제하시겠습니까?',
+    async () => {
+      const { data, error } = await del(`/service-center/${id}`)
+
+      if (error || !data?.success) {
+        showAlert(error?.message || data?.message || '삭제에 실패했습니다.', '오류')
+      } else {
+        showAlert(data.message || '서비스센터가 삭제되었습니다.', '성공')
+        loadServiceCenters()
+      }
+    },
+    '서비스센터 삭제'
+  )
+}
+
+// 사용/비사용 토글
+const toggleActive = (id, currentStatus) => {
+  console.log('[toggleActive] 호출:', { id, currentStatus })
+  const statusText = currentStatus == 1 ? '비사용' : '사용'
+
+  showConfirm(
+    `서비스센터를 ${statusText} 상태로 변경하시겠습니까?`,
+    async () => {
+      console.log('[toggleActive] API 호출:', `/service-center/${id}/toggle-active`)
+
+      const { data, error } = await post(`/service-center/${id}/toggle-active`)
+
+      console.log('[toggleActive] API 응답:', { data, error })
+
+      if (error || !data?.success) {
+        console.error('[toggleActive] 에러 상세:', error)
+        showAlert(error?.message || data?.message || '상태 변경에 실패했습니다.', '오류')
+      } else {
+        showAlert(data.message || '서비스센터 상태가 변경되었습니다.', '성공')
+        loadServiceCenters()
+      }
+    },
+    '상태 변경'
+  )
+}
+
+onMounted(() => {
+  loadServiceCenters()
+})
+</script>
+
+<style scoped>
+.admin--search-actions .admin--btn-primary {
+  background: var(--admin-accent-primary);
+  color: white;
+  border-color: var(--admin-accent-primary);
+  font-weight: 500;
+  padding: 8px 18px;
+  font-size: 13px;
+  border-radius: 8px;
+  transition: all 0.3s ease;
+}
+
+.admin--search-actions .admin--btn-primary:hover {
+  background: var(--admin-accent-hover);
+  border-color: var(--admin-accent-hover);
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.admin--toggle-btn {
+  padding: 6px 16px;
+  font-size: 12px;
+  border-radius: 20px;
+  border: 1px solid #ddd;
+  background: #f5f5f5;
+  color: #666;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  font-weight: 500;
+}
+
+.admin--toggle-btn:hover {
+  border-color: #bbb;
+  background: #e8e8e8;
+}
+
+.admin--toggle-btn.is-active {
+  background: var(--admin-accent-primary);
+  color: white;
+  border-color: var(--admin-accent-primary);
+}
+
+.admin--toggle-btn.is-active:hover {
+  background: var(--admin-accent-hover);
+  border-color: var(--admin-accent-hover);
+}
+</style>

+ 234 - 0
app/pages/site-manager/service/brochure.vue

@@ -0,0 +1,234 @@
+<template>
+  <div class="admin--brochure-list">
+    <!-- 검색 영역 -->
+    <div class="admin--search-box">
+      <div class="admin--search-form">
+        <input
+          v-model="searchKeyword"
+          type="text"
+          class="admin--form-input admin--search-input"
+          placeholder="신청자명으로 검색"
+          @keyup.enter="handleSearch"
+        >
+        <button class="admin--btn admin--btn-primary" @click="handleSearch">
+          검색
+        </button>
+        <button class="admin--btn admin--btn-secondary" @click="handleReset">
+          초기화
+        </button>
+      </div>
+      <div class="admin--search-actions">
+        <button class="admin--btn admin--btn-secondary" @click="handleExcelDownload">
+          엑셀 다운로드
+        </button>
+      </div>
+    </div>
+
+    <!-- 테이블 -->
+    <div class="admin--table-wrapper">
+      <table class="admin--table">
+        <thead>
+          <tr>
+            <th>NO</th>
+            <th>신청자</th>
+            <th>지점</th>
+            <th>희망차종</th>
+            <th>핸드폰</th>
+            <th>구입예상</th>
+            <th>신청일자</th>
+            <th>상태</th>
+            <th>관리</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="isLoading">
+            <td colspan="9" class="admin--table-loading">
+              데이터를 불러오는 중...
+            </td>
+          </tr>
+          <tr v-else-if="!brochures || brochures.length === 0">
+            <td colspan="9" class="admin--table-empty">
+              브로셔 요청이 없습니다.
+            </td>
+          </tr>
+          <tr v-else v-for="(brochure, index) in brochures" :key="brochure.id">
+            <td>{{ totalCount - ((currentPage - 1) * perPage + index) }}</td>
+            <td class="admin--table-title">{{ brochure.name }}</td>
+            <td>{{ brochure.branch_name }}</td>
+            <td>{{ brochure.car_model }}</td>
+            <td>{{ brochure.phone }}</td>
+            <td>{{ brochure.purchase_plan }}</td>
+            <td>{{ formatDate(brochure.created_at) }}</td>
+            <td>
+              <select
+                v-model="brochure.status"
+                class="admin--status-select"
+                @change="handleStatusChange(brochure.id, brochure.status)"
+              >
+                <option value="접수">접수</option>
+                <option value="접수완료">접수완료</option>
+                <option value="계약완료">계약완료</option>
+                <option value="출고완료">출고완료</option>
+              </select>
+            </td>
+            <td>
+              <button
+                class="admin--btn-small admin--btn-small-danger"
+                @click="handleDelete(brochure.id)"
+              >
+                삭제
+              </button>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <!-- 페이지네이션 -->
+    <div v-if="totalPages > 1" class="admin--pagination">
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(currentPage - 1)"
+      >
+        이전
+      </button>
+      <button
+        v-for="page in visiblePages"
+        :key="page"
+        class="admin--pagination-btn"
+        :class="{ 'is-active': page === currentPage }"
+        @click="changePage(page)"
+      >
+        {{ page }}
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(currentPage + 1)"
+      >
+        다음
+      </button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const { get, put, del } = useApi()
+
+const isLoading = ref(false)
+const brochures = ref([])
+const searchKeyword = ref('')
+const currentPage = ref(1)
+const perPage = ref(10)
+const totalCount = ref(0)
+const totalPages = ref(0)
+
+const visiblePages = computed(() => {
+  const pages = []
+  const maxVisible = 5
+  let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2))
+  let end = Math.min(totalPages.value, start + maxVisible - 1)
+
+  if (end - start < maxVisible - 1) {
+    start = Math.max(1, end - maxVisible + 1)
+  }
+
+  for (let i = start; i <= end; i++) {
+    pages.push(i)
+  }
+
+  return pages
+})
+
+const loadBrochures = async () => {
+  isLoading.value = true
+
+  const params = {
+    page: currentPage.value,
+    per_page: perPage.value
+  }
+
+  if (searchKeyword.value) {
+    params.search_keyword = searchKeyword.value
+  }
+
+  const { data, error } = await get('/service/brochure', { params })
+
+  console.log('[Brochure] API 응답:', { data, error })
+
+  // API 응답: { success: true, data: { items, total }, message }
+  if (data?.success && data?.data) {
+    brochures.value = data.data.items || []
+    totalCount.value = data.data.total || 0
+    totalPages.value = Math.ceil(totalCount.value / perPage.value)
+    console.log('[Brochure] 로드 성공:', brochures.value.length)
+  }
+
+  isLoading.value = false
+}
+
+const handleSearch = () => {
+  currentPage.value = 1
+  loadBrochures()
+}
+
+const handleReset = () => {
+  searchKeyword.value = ''
+  currentPage.value = 1
+  loadBrochures()
+}
+
+const changePage = (page) => {
+  if (page < 1 || page > totalPages.value) return
+  currentPage.value = page
+  loadBrochures()
+  window.scrollTo({ top: 0, behavior: 'smooth' })
+}
+
+const handleExcelDownload = () => {
+  const params = searchKeyword.value ? { search_keyword: searchKeyword.value } : {}
+  window.open(`/api/service/brochure/excel?${new URLSearchParams(params)}`, '_blank')
+}
+
+const handleStatusChange = async (id, status) => {
+  const { error } = await put(`/service/brochure/${id}/status`, { status })
+
+  if (error) {
+    alert('상태 변경에 실패했습니다.')
+    loadBrochures()
+  } else {
+    alert('상태가 변경되었습니다.')
+  }
+}
+
+const handleDelete = async (id) => {
+  if (!confirm('정말 삭제하시겠습니까?')) return
+
+  const { error } = await del(`/service/brochure/${id}`)
+
+  if (error) {
+    alert('삭제에 실패했습니다.')
+  } else {
+    alert('삭제되었습니다.')
+    loadBrochures()
+  }
+}
+
+const formatDate = (dateString) => {
+  if (!dateString) return '-'
+  const date = new Date(dateString)
+  return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`
+}
+
+onMounted(() => {
+  loadBrochures()
+})
+</script>

+ 317 - 0
app/pages/site-manager/showroom/create.vue

@@ -0,0 +1,317 @@
+<template>
+  <div class="admin--showroom-form">
+    <form @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 전시장명 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">전시장명 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="전시장명을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 소속명 선택 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">소속명 <span class="admin--required">*</span></label>
+        <select
+          v-model="formData.branch_id"
+          class="admin--form-select"
+          required
+        >
+          <option value="">소속 지점을 선택하세요</option>
+          <option v-for="branch in branches" :key="branch.id" :value="branch.id">
+            {{ branch.name }}
+          </option>
+        </select>
+      </div>
+
+      <!-- 대표번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">대표번호 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.phone"
+          type="tel"
+          class="admin--form-input"
+          placeholder="02-1234-5678"
+          required
+        >
+      </div>
+
+      <!-- 주소 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">주소 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.address"
+          type="text"
+          class="admin--form-input"
+          placeholder="주소를 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 상세주소 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">상세주소</label>
+        <input
+          v-model="formData.detail_address"
+          type="text"
+          class="admin--form-input"
+          placeholder="상세주소를 입력하세요"
+        >
+      </div>
+
+      <!-- 위도/경도 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">위치 좌표</label>
+        <div class="admin--coordinate-group">
+          <div class="admin--coordinate-item">
+            <label>위도</label>
+            <input
+              v-model.number="formData.latitude"
+              type="number"
+              step="any"
+              class="admin--form-input"
+              placeholder="37.5665"
+            >
+          </div>
+          <div class="admin--coordinate-item">
+            <label>경도</label>
+            <input
+              v-model.number="formData.longitude"
+              type="number"
+              step="any"
+              class="admin--form-input"
+              placeholder="126.9780"
+            >
+          </div>
+        </div>
+      </div>
+
+      <!-- 영업시간 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">영업시간</label>
+        <textarea
+          v-model="formData.business_hours"
+          class="admin--form-textarea"
+          rows="3"
+          placeholder="평일: 09:00 - 18:00&#10;주말: 10:00 - 17:00"
+        ></textarea>
+      </div>
+
+      <!-- 견적요청 링크 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">견적요청 링크</label>
+        <input
+          v-model="formData.quote_link"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com/quote"
+        >
+      </div>
+
+      <!-- 시승신청 링크 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">시승신청 링크</label>
+        <input
+          v-model="formData.test_drive_link"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com/test-drive"
+        >
+      </div>
+
+      <!-- 링크 관리 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">관련 링크</label>
+        <div class="admin--multi-input-wrapper">
+          <div
+            v-for="(link, index) in formData.links"
+            :key="index"
+            class="admin--multi-input-item"
+          >
+            <input
+              v-model="formData.links[index]"
+              type="url"
+              class="admin--form-input"
+              placeholder="https://example.com"
+            >
+            <button
+              type="button"
+              class="admin--btn-remove"
+              @click="removeLink(index)"
+            >
+              삭제
+            </button>
+          </div>
+          <button
+            type="button"
+            class="admin--btn-add"
+            @click="addLink"
+          >
+            + 링크 추가
+          </button>
+        </div>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button
+          type="submit"
+          class="admin--btn admin--btn-primary"
+          :disabled="isSaving"
+        >
+          {{ isSaving ? '저장 중...' : '확인' }}
+        </button>
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="goToList"
+        >
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const router = useRouter()
+const { get, post } = useApi()
+
+const isSaving = ref(false)
+const successMessage = ref('')
+const errorMessage = ref('')
+const branches = ref([])
+
+const formData = ref({
+  name: '',
+  branch_id: '',
+  phone: '',
+  address: '',
+  detail_address: '',
+  latitude: null,
+  longitude: null,
+  business_hours: '',
+  quote_link: '',
+  test_drive_link: '',
+  links: [''] // 초기 링크 하나
+})
+
+// 지점 목록 로드
+const loadBranches = async () => {
+  const { data, error } = await get('/branch/list', { params: { per_page: 1000 } })
+
+  if (data?.success && data?.data?.items) {
+    branches.value = data.data.items.filter(branch => branch.is_active == 1)
+  }
+}
+
+// 링크 추가
+const addLink = () => {
+  formData.value.links.push('')
+}
+
+// 링크 삭제
+const removeLink = (index) => {
+  formData.value.links.splice(index, 1)
+}
+
+// 폼 제출
+const handleSubmit = async () => {
+  successMessage.value = ''
+  errorMessage.value = ''
+
+  // 유효성 검사
+  if (!formData.value.name) {
+    errorMessage.value = '전시장명을 입력하세요.'
+    return
+  }
+
+  if (!formData.value.branch_id) {
+    errorMessage.value = '소속 지점을 선택하세요.'
+    return
+  }
+
+  if (!formData.value.phone) {
+    errorMessage.value = '대표번호를 입력하세요.'
+    return
+  }
+
+  if (!formData.value.address) {
+    errorMessage.value = '주소를 입력하세요.'
+    return
+  }
+
+  isSaving.value = true
+
+  try {
+    // 빈 링크 제거
+    const submitData = {
+      ...formData.value,
+      links: formData.value.links.filter(link => link.trim() !== '')
+    }
+
+    const { data, error } = await post('/showroom', submitData)
+
+    if (error || !data?.success) {
+      errorMessage.value = error?.message || data?.message || '등록에 실패했습니다.'
+    } else {
+      successMessage.value = data.message || '전시장이 등록되었습니다.'
+      setTimeout(() => {
+        router.push('/site-manager/showroom/list')
+      }, 1000)
+    }
+  } catch (error) {
+    errorMessage.value = '서버 오류가 발생했습니다.'
+    console.error('Save error:', error)
+  } finally {
+    isSaving.value = false
+  }
+}
+
+// 목록으로 이동
+const goToList = () => {
+  router.push('/site-manager/showroom/list')
+}
+
+onMounted(() => {
+  loadBranches()
+})
+</script>
+
+<style scoped>
+.admin--coordinate-group {
+  display: flex;
+  gap: 16px;
+}
+
+.admin--coordinate-item {
+  flex: 1;
+}
+
+.admin--coordinate-item label {
+  display: block;
+  margin-bottom: 8px;
+  font-size: 14px;
+  color: #666;
+}
+</style>

+ 337 - 0
app/pages/site-manager/showroom/edit/[id].vue

@@ -0,0 +1,337 @@
+<template>
+  <div class="admin--showroom-form">
+    <div v-if="isLoading" class="admin--loading">데이터를 불러오는 중...</div>
+    <form v-else @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 전시장명 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >전시장명 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="전시장명을 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 소속명 선택 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >소속명 <span class="admin--required">*</span></label
+        >
+        <select v-model="formData.branch_id" class="admin--form-select" required>
+          <option value="">소속 지점을 선택하세요</option>
+          <option v-for="branch in branches" :key="branch.id" :value="branch.id">
+            {{ branch.name }}
+          </option>
+        </select>
+      </div>
+
+      <!-- 대표번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >대표번호 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.phone"
+          type="tel"
+          class="admin--form-input"
+          placeholder="02-1234-5678"
+          required
+        />
+      </div>
+
+      <!-- 주소 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label"
+          >주소 <span class="admin--required">*</span></label
+        >
+        <input
+          v-model="formData.address"
+          type="text"
+          class="admin--form-input"
+          placeholder="주소를 입력하세요"
+          required
+        />
+      </div>
+
+      <!-- 상세주소 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">상세주소</label>
+        <input
+          v-model="formData.detail_address"
+          type="text"
+          class="admin--form-input"
+          placeholder="상세주소를 입력하세요"
+        />
+      </div>
+
+      <!-- 위도/경도 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">위치 좌표</label>
+        <div class="admin--coordinate-group">
+          <div class="admin--coordinate-item">
+            <label>위도</label>
+            <input
+              v-model.number="formData.latitude"
+              type="text"
+              step="any"
+              class="admin--form-input"
+              placeholder="37.5665"
+            />
+          </div>
+          <div class="admin--coordinate-item">
+            <label>경도</label>
+            <input
+              v-model.number="formData.longitude"
+              type="text"
+              step="any"
+              class="admin--form-input"
+              placeholder="126.9780"
+            />
+          </div>
+        </div>
+      </div>
+
+      <!-- 영업시간 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">영업시간</label>
+        <textarea
+          v-model="formData.business_hours"
+          class="admin--form-textarea"
+          rows="3"
+          placeholder="평일: 09:00 - 18:00&#10;주말: 10:00 - 17:00"
+        ></textarea>
+      </div>
+
+      <!-- 견적요청 링크 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">견적요청 링크</label>
+        <input
+          v-model="formData.quote_link"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com/quote"
+        />
+      </div>
+
+      <!-- 시승신청 링크 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">시승신청 링크</label>
+        <input
+          v-model="formData.test_drive_link"
+          type="url"
+          class="admin--form-input"
+          placeholder="https://example.com/test-drive"
+        />
+      </div>
+
+      <!-- 링크 관리 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">관련 링크</label>
+        <div class="admin--multi-input-wrapper">
+          <div
+            v-for="(link, index) in formData.links"
+            :key="index"
+            class="admin--multi-input-item"
+          >
+            <input
+              v-model="formData.links[index]"
+              type="url"
+              class="admin--form-input"
+              placeholder="https://example.com"
+            />
+            <button type="button" class="admin--btn-remove" @click="removeLink(index)">
+              삭제
+            </button>
+          </div>
+          <button type="button" class="admin--btn-add" @click="addLink">
+            + 링크 추가
+          </button>
+        </div>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
+          {{ isSaving ? "저장 중..." : "확인" }}
+        </button>
+        <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+  import { ref, onMounted } from "vue";
+  import { useRouter, useRoute } from "vue-router";
+
+  definePageMeta({
+    layout: "admin",
+    middleware: ["auth"],
+  });
+
+  const router = useRouter();
+  const route = useRoute();
+  const { get, put } = useApi();
+
+  const isLoading = ref(true);
+  const isSaving = ref(false);
+  const successMessage = ref("");
+  const errorMessage = ref("");
+  const branches = ref([]);
+
+  const formData = ref({
+    name: "",
+    branch_id: "",
+    phone: "",
+    address: "",
+    detail_address: "",
+    latitude: null,
+    longitude: null,
+    business_hours: "",
+    quote_link: "",
+    test_drive_link: "",
+    links: [],
+  });
+
+  // 지점 목록 로드
+  const loadBranches = async () => {
+    const { data, error } = await get("/branch/list", { params: { per_page: 1000 } });
+
+    if (data?.success && data?.data?.items) {
+      branches.value = data.data.items.filter((branch) => branch.is_active == 1);
+    }
+  };
+
+  // 전시장 데이터 로드
+  const loadShowroom = async () => {
+    const id = route.params.id;
+
+    const { data, error } = await get(`/showroom/${id}`);
+
+    if (data?.success && data?.data) {
+      const showroom = data.data;
+      formData.value = {
+        name: showroom.name || "",
+        branch_id: showroom.branch_id || "",
+        phone: showroom.main_phone || "",
+        address: showroom.address || "",
+        detail_address: showroom.detail_address || "",
+        latitude: showroom.latitude || null,
+        longitude: showroom.longitude || null,
+        business_hours: showroom.business_hours || "",
+        quote_link: showroom.quote_link || "",
+        test_drive_link: showroom.test_drive_link || "",
+        links: showroom.links && showroom.links.length > 0 ? showroom.links : [""],
+      };
+    }
+
+    isLoading.value = false;
+  };
+
+  // 링크 추가
+  const addLink = () => {
+    formData.value.links.push("");
+  };
+
+  // 링크 삭제
+  const removeLink = (index) => {
+    formData.value.links.splice(index, 1);
+  };
+
+  // 폼 제출
+  const handleSubmit = async () => {
+    successMessage.value = "";
+    errorMessage.value = "";
+
+    // 유효성 검사
+    if (!formData.value.name) {
+      errorMessage.value = "전시장명을 입력하세요.";
+      return;
+    }
+
+    if (!formData.value.branch_id) {
+      errorMessage.value = "소속 지점을 선택하세요.";
+      return;
+    }
+
+    if (!formData.value.phone) {
+      errorMessage.value = "대표번호를 입력하세요.";
+      return;
+    }
+
+    if (!formData.value.address) {
+      errorMessage.value = "주소를 입력하세요.";
+      return;
+    }
+
+    isSaving.value = true;
+
+    try {
+      const id = route.params.id;
+
+      // 빈 링크 제거
+      const submitData = {
+        ...formData.value,
+        links: formData.value.links.filter((link) => link.trim() !== ""),
+      };
+
+      const { data, error } = await put(`/showroom/${id}`, submitData);
+
+      if (error || !data?.success) {
+        errorMessage.value = error?.message || data?.message || "수정에 실패했습니다.";
+      } else {
+        successMessage.value = data.message || "전시장이 수정되었습니다.";
+        setTimeout(() => {
+          router.push("/site-manager/showroom/list");
+        }, 1000);
+      }
+    } catch (error) {
+      errorMessage.value = "서버 오류가 발생했습니다.";
+      console.error("Save error:", error);
+    } finally {
+      isSaving.value = false;
+    }
+  };
+
+  // 목록으로 이동
+  const goToList = () => {
+    router.push("/site-manager/showroom/list");
+  };
+
+  onMounted(async () => {
+    await loadBranches();
+    await loadShowroom();
+  });
+</script>
+
+<style scoped>
+  .admin--coordinate-group {
+    display: flex;
+    gap: 16px;
+  }
+
+  .admin--coordinate-item {
+    flex: 1;
+  }
+
+  .admin--coordinate-item label {
+    display: block;
+    margin-bottom: 8px;
+    font-size: 14px;
+    color: #666;
+  }
+</style>

+ 337 - 0
app/pages/site-manager/showroom/list.vue

@@ -0,0 +1,337 @@
+<template>
+  <div class="admin--showroom-list">
+    <!-- 상단 버튼 -->
+    <div class="admin--search-box">
+      <div class="admin--search-form"></div>
+      <div class="admin--search-actions">
+        <button class="admin--btn admin--btn-primary" @click="goToCreate">
+          + 전시장 등록
+        </button>
+      </div>
+    </div>
+
+    <!-- 테이블 -->
+    <div class="admin--table-wrapper">
+      <table class="admin--table">
+        <thead>
+          <tr>
+            <th>NO</th>
+            <th>전시장명</th>
+            <th>소속명</th>
+            <th>대표번호</th>
+            <th>주소</th>
+            <th>상태</th>
+            <th>관리</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="isLoading">
+            <td colspan="7" class="admin--table-loading">
+              데이터를 불러오는 중...
+            </td>
+          </tr>
+          <tr v-else-if="!showrooms || showrooms.length === 0">
+            <td colspan="7" class="admin--table-empty">
+              등록된 전시장이 없습니다.
+            </td>
+          </tr>
+          <tr v-else v-for="(showroom, index) in showrooms" :key="showroom.id">
+            <td>{{ totalCount - ((currentPage - 1) * perPage + index) }}</td>
+            <td class="admin--table-title">{{ showroom.name }}</td>
+            <td>{{ showroom.branch_name || '-' }}</td>
+            <td>{{ showroom.main_phone }}</td>
+            <td>{{ showroom.address }}</td>
+            <td>
+              <button
+                class="admin--toggle-btn"
+                :class="{ 'is-active': showroom.is_active == 1 }"
+                @click="toggleActive(showroom.id, showroom.is_active)"
+              >
+                {{ showroom.is_active == 1 ? '사용' : '비사용' }}
+              </button>
+            </td>
+            <td>
+              <div class="admin--table-actions">
+                <button
+                  class="admin--btn-small admin--btn-small-primary"
+                  @click="goToEdit(showroom.id)"
+                >
+                  수정
+                </button>
+                <button
+                  class="admin--btn-small admin--btn-small-danger"
+                  @click="deleteShowroom(showroom.id)"
+                >
+                  삭제
+                </button>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <!-- 페이지네이션 -->
+    <div v-if="totalPages > 1" class="admin--pagination">
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === 1"
+        @click="changePage(currentPage - 1)"
+      >
+        이전
+      </button>
+      <button
+        v-for="page in visiblePages"
+        :key="page"
+        class="admin--pagination-btn"
+        :class="{ 'is-active': page === currentPage }"
+        @click="changePage(page)"
+      >
+        {{ page }}
+      </button>
+      <button
+        class="admin--pagination-btn"
+        :disabled="currentPage === totalPages"
+        @click="changePage(currentPage + 1)"
+      >
+        다음
+      </button>
+    </div>
+
+    <!-- 알림 모달 -->
+    <AdminAlertModal
+      v-if="alertModal.show"
+      :title="alertModal.title"
+      :message="alertModal.message"
+      :type="alertModal.type"
+      @confirm="handleAlertConfirm"
+      @cancel="handleAlertCancel"
+      @close="closeAlertModal"
+    />
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import AdminAlertModal from '~/components/admin/AdminAlertModal.vue'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const router = useRouter()
+const { get, del, post } = useApi()
+
+const isLoading = ref(false)
+const showrooms = ref([])
+const currentPage = ref(1)
+const perPage = ref(10)
+const totalCount = ref(0)
+const totalPages = ref(0)
+
+// 알림 모달
+const alertModal = ref({
+  show: false,
+  title: '알림',
+  message: '',
+  type: 'alert',
+  onConfirm: null
+})
+
+// 알림 모달 표시
+const showAlert = (message, title = '알림') => {
+  alertModal.value = {
+    show: true,
+    title,
+    message,
+    type: 'alert',
+    onConfirm: null
+  }
+}
+
+// 확인 모달 표시
+const showConfirm = (message, onConfirm, title = '확인') => {
+  alertModal.value = {
+    show: true,
+    title,
+    message,
+    type: 'confirm',
+    onConfirm
+  }
+}
+
+// 알림 모달 닫기
+const closeAlertModal = () => {
+  alertModal.value.show = false
+}
+
+// 알림 모달 확인
+const handleAlertConfirm = () => {
+  if (alertModal.value.onConfirm) {
+    alertModal.value.onConfirm()
+  }
+  closeAlertModal()
+}
+
+// 알림 모달 취소
+const handleAlertCancel = () => {
+  closeAlertModal()
+}
+
+// 보이는 페이지 번호 계산
+const visiblePages = computed(() => {
+  const pages = []
+  const maxVisible = 5
+  let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2))
+  let end = Math.min(totalPages.value, start + maxVisible - 1)
+
+  if (end - start < maxVisible - 1) {
+    start = Math.max(1, end - maxVisible + 1)
+  }
+
+  for (let i = start; i <= end; i++) {
+    pages.push(i)
+  }
+
+  return pages
+})
+
+// 데이터 로드
+const loadShowrooms = async () => {
+  isLoading.value = true
+
+  const params = {
+    page: currentPage.value,
+    per_page: perPage.value
+  }
+
+  const { data, error } = await get('/showroom/list', { params })
+
+  console.log('[ShowroomList] API 응답:', { data, error })
+
+  if (data?.success && data?.data) {
+    showrooms.value = data.data.items || []
+    totalCount.value = data.data.total || 0
+    totalPages.value = Math.ceil(totalCount.value / perPage.value)
+    console.log('[ShowroomList] 로드 성공:', showrooms.value.length)
+  }
+
+  isLoading.value = false
+}
+
+// 페이지 변경
+const changePage = (page) => {
+  if (page < 1 || page > totalPages.value) return
+  currentPage.value = page
+  loadShowrooms()
+  window.scrollTo({ top: 0, behavior: 'smooth' })
+}
+
+// 전시장 등록 페이지로 이동
+const goToCreate = () => {
+  router.push('/site-manager/showroom/create')
+}
+
+// 전시장 수정 페이지로 이동
+const goToEdit = (id) => {
+  router.push(`/site-manager/showroom/edit/${id}`)
+}
+
+// 전시장 삭제
+const deleteShowroom = (id) => {
+  showConfirm(
+    '정말 삭제하시겠습니까?',
+    async () => {
+      const { data, error } = await del(`/showroom/${id}`)
+
+      if (error || !data?.success) {
+        showAlert(error?.message || data?.message || '삭제에 실패했습니다.', '오류')
+      } else {
+        showAlert(data.message || '전시장이 삭제되었습니다.', '성공')
+        loadShowrooms()
+      }
+    },
+    '전시장 삭제'
+  )
+}
+
+// 사용/비사용 토글
+const toggleActive = (id, currentStatus) => {
+  console.log('[toggleActive] 호출:', { id, currentStatus })
+  const statusText = currentStatus == 1 ? '비사용' : '사용'
+
+  showConfirm(
+    `전시장을 ${statusText} 상태로 변경하시겠습니까?`,
+    async () => {
+      console.log('[toggleActive] API 호출:', `/showroom/${id}/toggle-active`)
+
+      const { data, error } = await post(`/showroom/${id}/toggle-active`)
+
+      console.log('[toggleActive] API 응답:', { data, error })
+
+      if (error || !data?.success) {
+        console.error('[toggleActive] 에러 상세:', error)
+        showAlert(error?.message || data?.message || '상태 변경에 실패했습니다.', '오류')
+      } else {
+        showAlert(data.message || '전시장 상태가 변경되었습니다.', '성공')
+        loadShowrooms()
+      }
+    },
+    '상태 변경'
+  )
+}
+
+onMounted(() => {
+  loadShowrooms()
+})
+</script>
+
+<style scoped>
+.admin--search-actions .admin--btn-primary {
+  background: var(--admin-accent-primary);
+  color: white;
+  border-color: var(--admin-accent-primary);
+  font-weight: 500;
+  padding: 8px 18px;
+  font-size: 13px;
+  border-radius: 8px;
+  transition: all 0.3s ease;
+}
+
+.admin--search-actions .admin--btn-primary:hover {
+  background: var(--admin-accent-hover);
+  border-color: var(--admin-accent-hover);
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.admin--toggle-btn {
+  padding: 6px 16px;
+  font-size: 12px;
+  border-radius: 20px;
+  border: 1px solid #ddd;
+  background: #f5f5f5;
+  color: #666;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  font-weight: 500;
+}
+
+.admin--toggle-btn:hover {
+  border-color: #bbb;
+  background: #e8e8e8;
+}
+
+.admin--toggle-btn.is-active {
+  background: var(--admin-accent-primary);
+  color: white;
+  border-color: var(--admin-accent-primary);
+}
+
+.admin--toggle-btn.is-active:hover {
+  background: var(--admin-accent-hover);
+  border-color: var(--admin-accent-hover);
+}
+</style>

+ 219 - 0
app/pages/site-manager/staff/advisor/create.vue

@@ -0,0 +1,219 @@
+<template>
+  <div class="admin--advisor-form">
+    <form @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 서비스센터 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">서비스센터 <span class="admin--required">*</span></label>
+        <select v-model="formData.service_center" class="admin--form-select" required>
+          <option value="">서비스센터를 선택하세요</option>
+          <option v-for="center in serviceCenters" :key="center.code" :value="center.code">
+            {{ center.code }} : {{ center.name }}
+          </option>
+        </select>
+      </div>
+
+      <!-- 이름 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이름 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="이름을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 대표번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">대표번호 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.main_phone"
+          type="tel"
+          class="admin--form-input"
+          placeholder="02-1234-5678"
+          required
+        >
+      </div>
+
+      <!-- 직통번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">직통번호</label>
+        <input
+          v-model="formData.direct_phone"
+          type="tel"
+          class="admin--form-input"
+          placeholder="02-1234-5679"
+        >
+      </div>
+
+      <!-- 사진 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">사진</label>
+        <input
+          type="file"
+          accept="image/*"
+          class="admin--form-file"
+          @change="handlePhotoUpload"
+        >
+        <div v-if="photoPreview" class="admin--image-preview">
+          <img :src="photoPreview" alt="미리보기">
+          <button type="button" class="admin--btn-remove-image" @click="removePhoto">
+            삭제
+          </button>
+        </div>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button
+          type="submit"
+          class="admin--btn admin--btn-primary"
+          :disabled="isSaving"
+        >
+          {{ isSaving ? '저장 중...' : '확인' }}
+        </button>
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="goToList"
+        >
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const router = useRouter()
+const { get, post, upload } = useApi()
+
+const isSaving = ref(false)
+const successMessage = ref('')
+const errorMessage = ref('')
+const photoPreview = ref(null)
+const photoFile = ref(null)
+
+const formData = ref({
+  service_center: '',
+  name: '',
+  main_phone: '',
+  direct_phone: '',
+  photo_url: ''
+})
+
+const serviceCenters = ref([
+  { code: 'CA', name: '성수서비스센터' },
+  { code: 'CB', name: '수원서비스센터' },
+  { code: 'CC', name: '대전서비스센터' },
+  { code: 'CD', name: '광주서비스센터' }
+])
+
+const handlePhotoUpload = (event) => {
+  const file = event.target.files[0]
+  if (!file) return
+
+  if (!file.type.startsWith('image/')) {
+    alert('이미지 파일만 업로드 가능합니다.')
+    return
+  }
+
+  photoFile.value = file
+
+  const reader = new FileReader()
+  reader.onload = (e) => {
+    photoPreview.value = e.target.result
+  }
+  reader.readAsDataURL(file)
+}
+
+const removePhoto = () => {
+  photoPreview.value = null
+  photoFile.value = null
+  formData.value.photo_url = ''
+}
+
+const handleSubmit = async () => {
+  successMessage.value = ''
+  errorMessage.value = ''
+
+  if (!formData.value.service_center) {
+    errorMessage.value = '서비스센터를 선택하세요.'
+    return
+  }
+
+  if (!formData.value.name) {
+    errorMessage.value = '이름을 입력하세요.'
+    return
+  }
+
+  isSaving.value = true
+
+  try {
+    let photoUrl = formData.value.photo_url
+
+    if (photoFile.value) {
+      const formDataImage = new FormData()
+      formDataImage.append('file', photoFile.value)
+
+      const { data: uploadData, error: uploadError } = await upload('/upload/advisor-image', formDataImage)
+
+      if (uploadError) {
+        errorMessage.value = '사진 업로드에 실패했습니다.'
+        isSaving.value = false
+        return
+      }
+
+      if (!uploadData?.success || !uploadData?.data?.url) {
+        errorMessage.value = '사진 업로드 응답이 올바르지 않습니다.'
+        isSaving.value = false
+        return
+      }
+
+      photoUrl = uploadData.data.url
+    }
+
+    const submitData = {
+      ...formData.value,
+      photo_url: photoUrl
+    }
+
+    const { data, error } = await post('/staff/advisor', submitData)
+
+    if (error) {
+      errorMessage.value = error.message || '등록에 실패했습니다.'
+    } else {
+      successMessage.value = '어드바이저가 등록되었습니다.'
+      setTimeout(() => {
+        router.push('/site-manager/staff/advisor')
+      }, 1000)
+    }
+  } catch (error) {
+    errorMessage.value = '서버 오류가 발생했습니다.'
+    console.error('Save error:', error)
+  } finally {
+    isSaving.value = false
+  }
+}
+
+const goToList = () => {
+  router.push('/site-manager/staff/advisor')
+}
+</script>

+ 254 - 0
app/pages/site-manager/staff/advisor/edit/[id].vue

@@ -0,0 +1,254 @@
+<template>
+  <div class="admin--advisor-form">
+    <form @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 서비스센터 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">서비스센터 <span class="admin--required">*</span></label>
+        <select v-model="formData.service_center" class="admin--form-select" required>
+          <option value="">서비스센터를 선택하세요</option>
+          <option v-for="center in serviceCenters" :key="center.code" :value="center.code">
+            {{ center.code }} : {{ center.name }}
+          </option>
+        </select>
+      </div>
+
+      <!-- 이름 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">이름 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.name"
+          type="text"
+          class="admin--form-input"
+          placeholder="이름을 입력하세요"
+          required
+        >
+      </div>
+
+      <!-- 대표번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">대표번호 <span class="admin--required">*</span></label>
+        <input
+          v-model="formData.main_phone"
+          type="tel"
+          class="admin--form-input"
+          placeholder="02-1234-5678"
+          required
+        >
+      </div>
+
+      <!-- 직통번호 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">직통번호</label>
+        <input
+          v-model="formData.direct_phone"
+          type="tel"
+          class="admin--form-input"
+          placeholder="02-1234-5679"
+        >
+      </div>
+
+      <!-- 사진 -->
+      <div class="admin--form-group">
+        <label class="admin--form-label">사진</label>
+        <input
+          type="file"
+          accept="image/*"
+          class="admin--form-file"
+          @change="handlePhotoUpload"
+        >
+        <div v-if="photoPreview" class="admin--image-preview">
+          <img :src="photoPreview" alt="미리보기">
+          <button type="button" class="admin--btn-remove-image" @click="removePhoto">
+            삭제
+          </button>
+        </div>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button
+          type="submit"
+          class="admin--btn admin--btn-primary"
+          :disabled="isSaving"
+        >
+          {{ isSaving ? '저장 중...' : '확인' }}
+        </button>
+        <button
+          type="button"
+          class="admin--btn admin--btn-secondary"
+          @click="goToList"
+        >
+          목록
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+
+definePageMeta({
+  layout: 'admin',
+  middleware: ['auth']
+})
+
+const router = useRouter()
+const route = useRoute()
+const { get, put, upload } = useApi()
+const { getImageUrl } = useImage()
+
+const isSaving = ref(false)
+const isLoading = ref(false)
+const successMessage = ref('')
+const errorMessage = ref('')
+const photoPreview = ref(null)
+const photoFile = ref(null)
+
+const formData = ref({
+  service_center: '',
+  name: '',
+  main_phone: '',
+  direct_phone: '',
+  photo_url: ''
+})
+
+const serviceCenters = ref([
+  { code: 'CA', name: '성수서비스센터' },
+  { code: 'CB', name: '수원서비스센터' },
+  { code: 'CC', name: '대전서비스센터' },
+  { code: 'CD', name: '광주서비스센터' }
+])
+
+const loadAdvisor = async () => {
+  isLoading.value = true
+
+  const id = route.params.id
+  const { data, error } = await get(`/staff/advisor/${id}`)
+  console.log('[AdvisorEdit] 데이터 로드:', { data, error })
+
+  if (data?.success && data?.data) {
+    const advisor = data.data
+    formData.value = {
+      service_center: advisor.service_center || '',
+      name: advisor.name || '',
+      main_phone: advisor.main_phone || '',
+      direct_phone: advisor.direct_phone || '',
+      photo_url: advisor.photo_url || ''
+    }
+
+    if (advisor.photo_url) {
+      photoPreview.value = getImageUrl(advisor.photo_url)
+    }
+
+    console.log('[AdvisorEdit] 로드 성공')
+  }
+
+  isLoading.value = false
+}
+
+const handlePhotoUpload = (event) => {
+  const file = event.target.files[0]
+  if (!file) return
+
+  if (!file.type.startsWith('image/')) {
+    alert('이미지 파일만 업로드 가능합니다.')
+    return
+  }
+
+  photoFile.value = file
+
+  const reader = new FileReader()
+  reader.onload = (e) => {
+    photoPreview.value = e.target.result
+  }
+  reader.readAsDataURL(file)
+}
+
+const removePhoto = () => {
+  photoPreview.value = null
+  photoFile.value = null
+  formData.value.photo_url = ''
+}
+
+const handleSubmit = async () => {
+  successMessage.value = ''
+  errorMessage.value = ''
+
+  if (!formData.value.service_center) {
+    errorMessage.value = '서비스센터를 선택하세요.'
+    return
+  }
+
+  if (!formData.value.name) {
+    errorMessage.value = '이름을 입력하세요.'
+    return
+  }
+
+  isSaving.value = true
+
+  try {
+    let photoUrl = formData.value.photo_url
+
+    if (photoFile.value) {
+      const formDataImage = new FormData()
+      formDataImage.append('file', photoFile.value)
+
+      const { data: uploadData, error: uploadError } = await upload('/upload/advisor-image', formDataImage)
+
+      if (uploadError) {
+        errorMessage.value = '사진 업로드에 실패했습니다.'
+        isSaving.value = false
+        return
+      }
+
+      if (!uploadData?.success || !uploadData?.data?.url) {
+        errorMessage.value = '사진 업로드 응답이 올바르지 않습니다.'
+        isSaving.value = false
+        return
+      }
+
+      photoUrl = uploadData.data.url
+    }
+
+    const submitData = {
+      ...formData.value,
+      photo_url: photoUrl
+    }
+
+    const id = route.params.id
+    const { data, error } = await put(`/staff/advisor/${id}`, submitData)
+
+    if (error) {
+      errorMessage.value = error.message || '수정에 실패했습니다.'
+    } else {
+      successMessage.value = '어드바이저가 수정되었습니다.'
+      setTimeout(() => {
+        router.push('/site-manager/staff/advisor')
+      }, 1000)
+    }
+  } catch (error) {
+    errorMessage.value = '서버 오류가 발생했습니다.'
+    console.error('Save error:', error)
+  } finally {
+    isSaving.value = false
+  }
+}
+
+const goToList = () => {
+  router.push('/site-manager/staff/advisor')
+}
+
+onMounted(() => {
+  loadAdvisor()
+})
+</script>

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff