dm: 사례 콘텐츠

This commit is contained in:
2026-05-20 17:16:56 +09:00
parent d8d5755849
commit 48172aa3f0
12 changed files with 2525 additions and 1506 deletions
+1
View File
@@ -20,6 +20,7 @@ NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&ver
NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API
# NEXT_PUBLIC_GATEWAY_URL=http://localhost:8883 # 개발 서버 API
CONTENTS_INTRO = 31
CONTENTS_EXAMPLE = 32
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
NEXT_PRIVATE_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM=
+1
View File
@@ -21,6 +21,7 @@ NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API
# NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정
CONTENTS_INTRO = 31
CONTENTS_EXAMPLE = 32
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
NEXT_PRIVATE_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM=
+1
View File
@@ -19,6 +19,7 @@ NEXT_PUBLIC_NGII_URL = http://172.29.143.87/poiact
NEXT_PUBLIC_NGII_PARAM = BDE2BEF39DAFEC293AB6730150F222AC&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={y}&tilerow={x}
NEXT_PUBLIC_GATEWAY_URL = http://apigateway.iipbs-was.svc.cluster.local:8000
CONTENTS_INTRO = 31
CONTENTS_EXAMPLE = 32
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
NEXT_PRIVATE_WR_ADDRESS_KEY = U01TXOFVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA=
File diff suppressed because it is too large Load Diff
@@ -1,191 +1,415 @@
"use client";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
function BlockTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-2">
<span className="relative inline-block h-[16px] w-[27px] align-middle"> <span className="absolute left-0 top-0 h-[16px] w-[16px] rounded-full bg-[#01ACE5]/40" /> <span className="absolute left-[7px] top-0 h-[16px] w-[16px] rounded-full bg-[#6DBB4F]/40" /></span>
<h4 className="text-[20px] font-semibold text-[#1A1A1A]">{children}</h4>
</div>
);
}
// 구분선
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
type SupplyRow = {
facility: string;
usage: string;
amount: string;
};
function SupplyTable({ rows }: { rows: SupplyRow[] }) {
return (
<div className="mt-8 px-10">
<table className="w-full min-w-[860px] border border-[#D8DDE3] text-center">
<thead>
<tr>
<th className="border border-[#D8DDE3] bg-[#14A9D9] px-4 py-2 text-[18px] font-medium text-white">
시설명
</th>
<th className="border border-[#D8DDE3] bg-[#40B5A5] px-4 py-2 text-[18px] font-medium text-white">
이용용도
</th>
<th className="border border-[#D8DDE3] bg-[#73BF4B] px-4 py-2 text-[18px] font-medium text-white">
공급량(㎥)
</th>
</tr>
</thead>
<tbody>
{rows.map((row, index) => (
<tr key={`${row.facility}-${index}`}>
<td className="border border-[#D8DDE3] bg-white px-4 py-2 text-[16px] text-[#4F4F4F]">
{row.facility}
</td>
<td className="border border-[#D8DDE3] bg-white px-4 py-2 text-[16px] text-[#4F4F4F]">
{row.usage}
</td>
<td className="border border-[#D8DDE3] bg-white px-4 py-2 text-[16px] text-[#4F4F4F]">
{row.amount}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
function InfoList({
projectName,
purpose,
totalCost,
treatment,
}: {
projectName: string;
purpose: string;
totalCost: string;
treatment: string;
}) {
return (
<div className="mt-10 space-y-10 px-10">
<div>
<BlockTitle>사업명</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-16px] leading-[1.8] text-[#4F4F4F]">
<li>{projectName}</li>
</ul>
</div>
<div>
<BlockTitle>사업목적</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-16px] leading-[1.8] text-[#4F4F4F]">
<li>{purpose}</li>
</ul>
</div>
<div>
<BlockTitle>사업 주요 내용</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-16px] leading-[1.8] text-[#4F4F4F]">
<li>총사업비 : {totalCost}</li>
<li>처리공법 : {treatment}</li>
</ul>
</div>
<div>
<BlockTitle>사업량</BlockTitle>
</div>
</div>
);
}
import { useEffect, useRef } from "react";
export default function GreywaterExampleSection() {
const title = "중수도 사례";
return (
<div className="bg-white">
{/* 상단 타이틀 */}
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold tracking-tight leading-none text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute left-0 bottom-[-4px] z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
const rootRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const root = rootRef.current;
if (!root) return;
const cleanups: Array<() => void> = [];
const sliders = Array.from(root.querySelectorAll(".image-slider"));
sliders.forEach((sliderElement) => {
const slider = sliderElement as HTMLElement;
const viewport = slider.querySelector(
".image-slider-viewport",
) as HTMLElement | null;
const track = slider.querySelector(".image-slider-track") as HTMLElement | null;
const dotsWrap = slider.querySelector(
".image-slider-dots",
) as HTMLElement | null;
const pages = Array.from(
slider.querySelectorAll(".image-slider-page"),
) as HTMLElement[];
if (!viewport || !track || !dotsWrap || pages.length === 0) return;
let page = 0;
let dragging = false;
let isHover = false;
let startX = 0;
let deltaX = 0;
let timer: number | null = null;
const autoPlayMs = Number(slider.dataset.autoplay ?? "4500");
const canSlide = pages.length > 1;
if (!canSlide) {
dotsWrap.style.display = "none";
} else {
dotsWrap.style.display = "";
}
const render = () => {
track.style.transform = `translateX(-${page * 100}%)`;
Array.from(dotsWrap.querySelectorAll("button")).forEach((dot, index) => {
dot.classList.toggle("is-active", index === page);
});
};
const goTo = (nextPage: number) => {
if (!canSlide) return;
if (nextPage < 0) page = pages.length - 1;
else if (nextPage >= pages.length) page = 0;
else page = nextPage;
render();
};
const stopTimer = () => {
if (timer !== null) {
window.clearInterval(timer);
timer = null;
}
};
const startTimer = () => {
stopTimer();
if (!canSlide || dragging || isHover) return;
timer = window.setInterval(() => {
page = page + 1 >= pages.length ? 0 : page + 1;
render();
}, autoPlayMs);
};
const dotButtons = pages.map((_, index) => {
const button = document.createElement("button");
button.type = "button";
button.className = "image-slider-dot";
const onClick = () => {
goTo(index);
startTimer();
};
button.addEventListener("click", onClick);
dotsWrap.appendChild(button);
cleanups.push(() => {
button.removeEventListener("click", onClick);
});
return button;
});
const onMouseEnter = () => {
isHover = true;
stopTimer();
};
const onMouseLeave = () => {
isHover = false;
startTimer();
};
const onPointerDown = (event: PointerEvent) => {
if (!canSlide) return;
dragging = true;
startX = event.clientX;
deltaX = 0;
viewport.setPointerCapture(event.pointerId);
};
const onPointerMove = (event: PointerEvent) => {
if (!dragging) return;
deltaX = event.clientX - startX;
};
const onPointerUp = () => {
if (!dragging) return;
dragging = false;
if (deltaX > 50) goTo(page - 1);
if (deltaX < -50) goTo(page + 1);
deltaX = 0;
startTimer();
};
viewport.addEventListener("mouseenter", onMouseEnter);
viewport.addEventListener("mouseleave", onMouseLeave);
viewport.addEventListener("pointerdown", onPointerDown);
viewport.addEventListener("pointermove", onPointerMove);
viewport.addEventListener("pointerup", onPointerUp);
viewport.addEventListener("pointercancel", onPointerUp);
cleanups.push(() => {
stopTimer();
viewport.removeEventListener("mouseenter", onMouseEnter);
viewport.removeEventListener("mouseleave", onMouseLeave);
viewport.removeEventListener("pointerdown", onPointerDown);
viewport.removeEventListener("pointermove", onPointerMove);
viewport.removeEventListener("pointerup", onPointerUp);
viewport.removeEventListener("pointercancel", onPointerUp);
dotButtons.forEach((button) => button.remove());
});
render();
startTimer();
});
return () => {
cleanups.forEach((cleanup) => cleanup());
};
}, []);
const contentsHtml = `
<div class="contents-page">
<div class="page-title">
<h2>
<span class="title-text">중수도 사례</span>
<span class="title-bg">&nbsp;</span>
</h2>
</div>
<div className="mt-10 pb-[120px]">
<section id="example-greywater-ansan" className="scroll-mt-[120px]">
<SectionTitle>경기도 안산시</SectionTitle>
<InfoList
projectName="안산시 중수도 설치사업"
purpose="수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현"
totalCost="81억원"
treatment="MBLM공법"
/>
<div class="sections greywater-example-sections">
<section id="example-greywater-ansan" class="section first">
<div class="section-title">
<span class="section-bar">&nbsp;</span>
<h3>경기도 안산시</h3>
</div>
<SupplyTable
rows={[
{ facility: "경기테크노파크", usage: "청소·화장실용수", amount: "40㎥/일" },
{ facility: "올림픽수영장", usage: "청소·화장실/세척·살수용수", amount: "35㎥/일" },
{ facility: "와스타디움", usage: "청소·화장실/조경용수", amount: "90㎥/일" },
{ facility: "호수실내수영장", usage: "청소·화장실/조경/세척·살수용수", amount: "50㎥/일" },
{ facility: "한양대학교 에리카캠퍼스", usage: "청소·화장실용수", amount: "370㎥/일" },
]}
/>
<div class="content-group">
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업명</h4>
</div>
<ul class="bullet-list">
<li>안산시 중수도 설치사업</li>
</ul>
</div>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업목적</h4>
</div>
<ul class="bullet-list">
<li>수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현</li>
</ul>
</div>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업 주요 내용</h4>
</div>
<ul class="bullet-list">
<li>총사업비 : 81억원</li>
<li>처리공법 : MBLM공법</li>
</ul>
</div>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업량</h4>
</div>
<div class="table-wrap greywater-table-wrap">
<table class="contents-table greywater-table">
<thead>
<tr>
<th class="th-blue">시설명</th>
<th class="th-teal-greywater">이용용도</th>
<th class="th-green">공급량(㎥)</th>
</tr>
</thead>
<tbody>
<tr><td>경기테크노파크</td><td>청소·화장실용수</td><td>40㎥/일</td></tr>
<tr><td>올림픽수영장</td><td>청소·화장실/세척·살수용수</td><td>35㎥/일</td></tr>
<tr><td>와스타디움</td><td>청소·화장실/조경용수</td><td>90㎥/일</td></tr>
<tr><td>호수실내수영장</td><td>청소·화장실/조경/세척·살수용수</td><td>50㎥/일</td></tr>
<tr><td>한양대학교 에리카캠퍼스</td><td>청소·화장실용수</td><td>370㎥/일</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<Hr className="my-20" />
<div class="divider"></div>
<section id="example-greywater-yongin" className="scroll-mt-[120px]">
<SectionTitle>경기도 용인시</SectionTitle>
<section id="example-greywater-yongin" class="section">
<div class="section-title">
<span class="section-bar">&nbsp;</span>
<h3>경기도 용인시</h3>
</div>
<InfoList
projectName="용인시 중수도 설치사업"
purpose="물 부족 등의 비상상황에 대한 대비, 하수라도 사용할 집결 등 수자원의 효율적 활용"
totalCost="40억원"
treatment="MBLM공법"
/>
<div class="content-group">
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업명</h4>
</div>
<ul class="bullet-list">
<li>용인시 중수도 설치사업</li>
</ul>
</div>
<SupplyTable
rows={[
{ facility: "여성회관", usage: "청소·화장실용수", amount: "35㎥/일" },
{ facility: "수지아르피아", usage: "청소·화장실용수", amount: "35㎥/일" },
{ facility: "지상작전사령부", usage: "청소·화장실/조경/세척·살수용수", amount: "53㎥/일" },
]}
/>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업목적</h4>
</div>
<ul class="bullet-list">
<li>물 부족 등의 비상상황에 대한 대비, 하수라도 사용할 집결 등 수자원의 효율적 활용</li>
</ul>
</div>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업 주요 내용</h4>
</div>
<ul class="bullet-list">
<li>총사업비 : 40억원</li>
<li>처리공법 : MBLM공법</li>
</ul>
</div>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업량</h4>
</div>
<div class="table-wrap greywater-table-wrap">
<table class="contents-table greywater-table">
<thead>
<tr>
<th class="th-blue">시설명</th>
<th class="th-teal-greywater">이용용도</th>
<th class="th-green">공급량(㎥)</th>
</tr>
</thead>
<tbody>
<tr><td>여성회관</td><td>청소·화장실용수</td><td>35㎥/일</td></tr>
<tr><td>수지아르피아</td><td>청소·화장실용수</td><td>35㎥/일</td></tr>
<tr><td>지상작전사령부</td><td>청소·화장실/조경/세척·살수용수</td><td>53㎥/일</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<Hr className="my-20" />
<div class="divider"></div>
<section id="example-greywater-pyeongtaek" className="scroll-mt-[120px]">
<SectionTitle>경기도 평택시</SectionTitle>
<section id="example-greywater-pyeongtaek" class="section">
<div class="section-title">
<span class="section-bar">&nbsp;</span>
<h3>경기도 평택시</h3>
</div>
<InfoList
projectName="평택시 중수도 설치사업"
purpose="수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현"
totalCost="21억원"
treatment="MBLM공법"
/>
<div class="content-group">
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업명</h4>
</div>
<ul class="bullet-list">
<li>평택시 중수도 설치사업</li>
</ul>
</div>
<SupplyTable
rows={[
{
facility: "평택대학교",
usage: "청소·화장실용수",
amount: "50㎥/일",
},
]}
/>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업목적</h4>
</div>
<ul class="bullet-list">
<li>수자원을 효율적으로 활용하여 지속가능성 도모 및 물 절약 실현</li>
</ul>
</div>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업 주요 내용</h4>
</div>
<ul class="bullet-list">
<li>총사업비 : 21억원</li>
<li>처리공법 : MBLM공법</li>
</ul>
</div>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업량</h4>
</div>
<div class="table-wrap greywater-table-wrap">
<table class="contents-table greywater-table">
<thead>
<tr>
<th class="th-blue">시설명</th>
<th class="th-teal-greywater">이용용도</th>
<th class="th-green">공급량(㎥)</th>
</tr>
</thead>
<tbody>
<tr><td>평택대학교</td><td>청소·화장실용수</td><td>50㎥/일</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
</div>
</div>
`;
return (
<div
ref={rootRef}
className="bg-white"
dangerouslySetInnerHTML={{ __html: contentsHtml }}
/>
);
}
}
@@ -1,225 +1,382 @@
"use client";
import CaseImageSlider from "@/components/CaseImageSlider";
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
function BlockTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-2">
<span className="relative inline-block h-[16px] w-[27px] align-middle">
<span className="absolute left-0 top-0 h-[16px] w-[16px] rounded-full bg-[#01ACE5]/40" />
<span className="absolute left-[7px] top-0 h-[16px] w-[16px] rounded-full bg-[#6DBB4F]/40" />
</span>
<h4 className="text-[20px] font-semibold text-[#1A1A1A]">{children}</h4>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function InfoList({
projectName,
purpose,
details,
}: {
projectName: string;
purpose: string;
details: string[];
}) {
return (
<div className="mt-10 space-y-9 px-10">
<div>
<BlockTitle>사업명</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-[16px] leading-[1.8] text-[#4F4F4F]">
<li>{projectName}</li>
</ul>
</div>
<div>
<BlockTitle>사업목적</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-[16px] leading-[1.8] text-[#4F4F4F]">
<li>{purpose}</li>
</ul>
</div>
<div>
<BlockTitle>사업 주요 내용</BlockTitle>
<ul className="mt-4 list-disc pl-8 text-[16px] leading-[1.8] text-[#4F4F4F]">
{details.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
</div>
);
}
type RainTableRow = {
facility: string;
storage: string;
supply: string;
pipe: string;
};
function RainTable({ rows }: { rows: RainTableRow[] }) {
return (
<div className="mt-8 overflow-x-auto">
<table className="w-full min-w-[860px] border border-[#D8DDE3] text-center">
<thead>
<tr>
<th className="border border-[#D8DDE3] bg-[#14A9D9] px-4 py-3 text-[18px] font-medium text-white">
시설명
</th>
<th className="border border-[#D8DDE3] bg-[#39B2BD] px-4 py-3 text-[18px] font-medium text-white">
저류조용량(㎥)
</th>
<th className="border border-[#D8DDE3] bg-[#58B99A] px-4 py-3 text-[18px] font-medium text-white">
빗물공급기
</th>
<th className="border border-[#D8DDE3] bg-[#70BF4B] px-4 py-3 text-[18px] font-medium text-white">
빗물분사장치(m)
</th>
</tr>
</thead>
<tbody>
{rows.map((row, index) => (
<tr key={`${row.facility}-${index}`}>
<td className="border border-[#D8DDE3] bg-white px-4 py-3 text-[16px] text-[#4F4F4F]">
{row.facility}
</td>
<td className="border border-[#D8DDE3] bg-white px-4 py-3 text-[16px] text-[#4F4F4F]">
{row.storage}
</td>
{index === 0 && (
<td
rowSpan={rows.length}
className="border border-[#D8DDE3] bg-white px-4 py-3 text-[16px] text-[#4F4F4F]"
>
{row.supply}
</td>
)}
<td className="border border-[#D8DDE3] bg-white px-4 py-3 text-[16px] text-[#4F4F4F]">
{row.pipe}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
import { useEffect, useRef } from "react";
export default function RainExampleSection() {
const title = "빗물이용시설 사례";
const rootRef = useRef<HTMLDivElement>(null);
return (
<div className="bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
useEffect(() => {
const root = rootRef.current;
if (!root) return;
const cleanups: Array<() => void> = [];
const sliders = Array.from(root.querySelectorAll(".image-slider"));
sliders.forEach((sliderElement) => {
const slider = sliderElement as HTMLElement;
const viewport = slider.querySelector(".image-slider-viewport") as HTMLElement | null;
const track = slider.querySelector(".image-slider-track") as HTMLElement | null;
const dotsWrap = slider.querySelector(".image-slider-dots") as HTMLElement | null;
const pages = Array.from(
slider.querySelectorAll(".image-slider-page"),
) as HTMLElement[];
if (!viewport || !track || !dotsWrap || pages.length === 0) return;
let page = 0;
let dragging = false;
let isHover = false;
let startX = 0;
let deltaX = 0;
let timer: number | null = null;
const autoPlayMs = Number(slider.dataset.autoplay ?? "4500");
const canSlide = pages.length > 1;
if (!canSlide) {
dotsWrap.style.display = "none";
} else {
dotsWrap.style.display = "";
}
const render = () => {
track.style.transform = `translateX(-${page * 100}%)`;
Array.from(dotsWrap.querySelectorAll("button")).forEach((dot, index) => {
dot.classList.toggle("is-active", index === page);
});
};
const goTo = (nextPage: number) => {
if (!canSlide) return;
if (nextPage < 0) page = pages.length - 1;
else if (nextPage >= pages.length) page = 0;
else page = nextPage;
render();
};
const stopTimer = () => {
if (timer !== null) {
window.clearInterval(timer);
timer = null;
}
};
const startTimer = () => {
stopTimer();
if (!canSlide || dragging || isHover) return;
timer = window.setInterval(() => {
page = page + 1 >= pages.length ? 0 : page + 1;
render();
}, autoPlayMs);
};
const dotButtons = pages.map((_, index) => {
const button = document.createElement("button");
button.type = "button";
button.className = "image-slider-dot";
const onClick = () => {
goTo(index);
startTimer();
};
button.addEventListener("click", onClick);
dotsWrap.appendChild(button);
cleanups.push(() => {
button.removeEventListener("click", onClick);
});
return button;
});
const onMouseEnter = () => {
isHover = true;
stopTimer();
};
const onMouseLeave = () => {
isHover = false;
startTimer();
};
const onPointerDown = (event: PointerEvent) => {
if (!canSlide) return;
dragging = true;
startX = event.clientX;
deltaX = 0;
viewport.setPointerCapture(event.pointerId);
};
const onPointerMove = (event: PointerEvent) => {
if (!dragging) return;
deltaX = event.clientX - startX;
};
const onPointerUp = () => {
if (!dragging) return;
dragging = false;
if (deltaX > 50) goTo(page - 1);
if (deltaX < -50) goTo(page + 1);
deltaX = 0;
startTimer();
};
viewport.addEventListener("mouseenter", onMouseEnter);
viewport.addEventListener("mouseleave", onMouseLeave);
viewport.addEventListener("pointerdown", onPointerDown);
viewport.addEventListener("pointermove", onPointerMove);
viewport.addEventListener("pointerup", onPointerUp);
viewport.addEventListener("pointercancel", onPointerUp);
cleanups.push(() => {
stopTimer();
viewport.removeEventListener("mouseenter", onMouseEnter);
viewport.removeEventListener("mouseleave", onMouseLeave);
viewport.removeEventListener("pointerdown", onPointerDown);
viewport.removeEventListener("pointermove", onPointerMove);
viewport.removeEventListener("pointerup", onPointerUp);
viewport.removeEventListener("pointercancel", onPointerUp);
dotButtons.forEach((button) => button.remove());
});
render();
startTimer();
});
return () => {
cleanups.forEach((cleanup) => cleanup());
};
}, []);
const contentsHtml = `
<div class="contents-page">
<div class="page-title">
<h2>
<span class="title-text">빗물이용시설 사례</span>
<span class="title-bg">&nbsp;</span>
</h2>
</div>
<div className="mt-10 pb-[120px]">
<section id="example-rain-suwon" className="scroll-mt-[120px]">
<SectionTitle>경기도 수원시</SectionTitle>
<div class="sections rain-example-sections">
<section id="example-rain-suwon" class="section first">
<div class="section-title">
<span class="section-bar">&nbsp;</span>
<h3>경기도 수원시</h3>
</div>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[1240px]"
slides={[
{ src: "/images/landing/business/rain/suwon_01.png", alt: "수원시 빗물이용시설 사례 1" },
{ src: "/images/landing/business/rain/suwon_02.png", alt: "수원시 빗물이용시설 사례 2" },
{ src: "/images/landing/business/rain/suwon_03.png", alt: "수원시 빗물이용시설 사례 3" },
{ src: "/images/landing/business/rain/suwon_04.png", alt: "수원시 빗물이용시설 사례 4" },
{ src: "/images/landing/business/rain/suwon_05.png", alt: "수원시 빗물이용시설 사례 5" },
]}
/>
<div class="image-slider" data-autoplay="4500">
<div class="image-slider-inner">
<div class="image-slider-viewport">
<div class="image-slider-track">
<div class="image-slider-page">
<div class="image-slide">
<img src="/images/landing/business/rain/suwon_01.png" alt="수원시 빗물이용시설 사례 1" />
</div>
<div class="image-slide">
<img src="/images/landing/business/rain/suwon_02.png" alt="수원시 빗물이용시설 사례 2" />
</div>
</div>
<div class="image-slider-page">
<div class="image-slide">
<img src="/images/landing/business/rain/suwon_03.png" alt="수원시 빗물이용시설 사례 3" />
</div>
<div class="image-slide">
<img src="/images/landing/business/rain/suwon_04.png" alt="수원시 빗물이용시설 사례 4" />
</div>
</div>
<div class="image-slider-page">
<div class="image-slide">
<img src="/images/landing/business/rain/suwon_05.png" alt="수원시 빗물이용시설 사례 5" />
</div>
</div>
</div>
</div>
<div class="image-slider-dots"></div>
</div>
</div>
<InfoList
projectName="수원시 빗물이용시설 설치사업"
purpose="월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물공급기를 설치하여 축서기 도시 열섬효과 및 도로 비산먼지 저감"
details={[
"총사업비 : 20억원",
"위치 : 수원시 영통구, 팔달구 및 장안구 일원",
]}
/>
<div class="content-group">
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업명</h4>
</div>
<ul class="bullet-list">
<li>수원시 빗물이용시설 설치사업</li>
</ul>
</div>
<div className="mt-9 px-10">
<BlockTitle>사업량</BlockTitle>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업목적</h4>
</div>
<ul class="bullet-list">
<li>월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물공급기를 설치하여 축서기 도시 열섬효과 및 도로 비산먼지 저감</li>
</ul>
</div>
<RainTable
rows={[
{
facility: "광교중학교",
storage: "4,500",
supply: "1대",
pipe: "160",
},
{
facility: "수원월드컵경기장",
storage: "12,000",
supply: "1대",
pipe: "451",
},
{
facility: "수원종합운동장",
storage: "10,000",
supply: "1대",
pipe: "231",
},
{
facility: "수원컨벤션센터",
storage: "3,600",
supply: "1대",
pipe: "460",
},
]}
/>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업 주요 내용</h4>
</div>
<ul class="bullet-list">
<li>총사업비 : 20억원</li>
<li>위치 : 수원시 영통구, 팔달구 및 장안구 일원</li>
</ul>
</div>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업량</h4>
</div>
<div class="table-wrap">
<table class="contents-table rain-table">
<thead>
<tr>
<th class="th-blue">시설명</th>
<th class="th-teal">저류조용량(㎥)</th>
<th class="th-mint">빗물공급기</th>
<th class="th-green">빗물분사장치(m)</th>
</tr>
</thead>
<tbody>
<tr>
<td>광교중학교</td>
<td>4,500</td>
<td rowspan="4">1대</td>
<td>160</td>
</tr>
<tr>
<td>수원월드컵경기장</td>
<td>12,000</td>
<td>451</td>
</tr>
<tr>
<td>수원종합운동장</td>
<td>10,000</td>
<td>231</td>
</tr>
<tr>
<td>수원컨벤션센터</td>
<td>3,600</td>
<td>460</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<Hr className="my-20" />
<div class="divider"></div>
<section id="example-rain-jeju" className="scroll-mt-[120px]">
<SectionTitle>제주특별자치도</SectionTitle>
<section id="example-rain-jeju" class="section">
<div class="section-title">
<span class="section-bar">&nbsp;</span>
<h3>제주특별자치도</h3>
</div>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[1240px]"
slides={[
{ src: "/images/landing/business/rain/jeju_01.png", alt: "제주특별자치도 빗물이용시설 사례 1" },
{ src: "/images/landing/business/rain/jeju_02.png", alt: "제주특별자치도 빗물이용시설 사례 2" },
{ src: "/images/landing/business/rain/jeju_03.png", alt: "제주특별자치도 빗물이용시설 사례 3" },
]}
/>
<div class="image-slider" data-autoplay="4500">
<div class="image-slider-inner">
<div class="image-slider-viewport">
<div class="image-slider-track">
<div class="image-slider-page">
<div class="image-slide">
<img src="/images/landing/business/rain/jeju_01.png" alt="제주특별자치도 빗물이용시설 사례 1" />
</div>
<div class="image-slide">
<img src="/images/landing/business/rain/jeju_02.png" alt="제주특별자치도 빗물이용시설 사례 2" />
</div>
</div>
<div class="image-slider-page">
<div class="image-slide">
<img src="/images/landing/business/rain/jeju_03.png" alt="제주특별자치도 빗물이용시설 사례 3" />
</div>
</div>
</div>
</div>
<div class="image-slider-dots"></div>
</div>
</div>
<InfoList
projectName="제주특별자치도 중규모 빗물이용시설 설치사업"
purpose="강우시 비닐하우스에서 발생하는 대체수자원인 빗물을 순환 활용하고자 하는 사업으로, 지하수 사용 저감과 집중호우시 도로와 농경지 침수피해 예방"
details={[
"총사업비 : 278억원",
"위치 : 제주특별자치도 서귀포시 남원읍 위미리 일원",
"사업량 : 저류조 7,000㎥, 배수갑 500㎡, 관로 21.7km",
"사업기간 : 2024. 5. ~ 2028. 12.",
]}
/>
<div class="content-group">
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업명</h4>
</div>
<ul class="bullet-list">
<li>제주특별자치도 중규모 빗물이용시설 설치사업</li>
</ul>
</div>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업목적</h4>
</div>
<ul class="bullet-list">
<li>강우시 비닐하우스에서 발생하는 대체수자원인 빗물을 순환 활용하고자 하는 사업으로, 지하수 사용 저감과 집중호우시 도로와 농경지 침수피해 예방</li>
</ul>
</div>
<div class="content-block">
<div class="block-title">
<span class="block-title-icon">
<span class="bubble bubble-sky">&nbsp;</span>
<span class="bubble bubble-green">&nbsp;</span>
</span>
<h4>사업 주요 내용</h4>
</div>
<ul class="bullet-list">
<li>총사업비 : 278억원</li>
<li>위치 : 제주특별자치도 서귀포시 남원읍 위미리 일원</li>
<li>사업량 : 저류조 7,000㎥, 배수갑 500㎡, 관로 21.7km</li>
<li>사업기간 : 2024. 5. ~ 2028. 12.</li>
</ul>
</div>
</div>
</section>
</div>
</div>
`;
return (
<div
ref={rootRef}
className="bg-white"
dangerouslySetInnerHTML={{ __html: contentsHtml }}
/>
);
}
}
@@ -1,367 +1,582 @@
"use client";
import CaseImageSlider from "@/components/CaseImageSlider";
type SewageCase = {
id: string;
title: string;
slides: {
src: string;
alt: string;
}[];
projectName: string;
purpose: string[];
method?: string[];
details: string[];
note?: string[];
};
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="h-5 w-[3px] bg-sky-500" />
<h3 className="text-2xl font-bold text-neutral-900">{children}</h3>
</div>
);
}
function BlockTitle({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-2">
<span className="relative inline-block h-[16px] w-[27px] align-middle">
<span className="absolute left-0 top-0 h-[16px] w-[16px] rounded-full bg-[#01ACE5]/40" />
<span className="absolute left-[7px] top-0 h-[16px] w-[16px] rounded-full bg-[#6DBB4F]/40" />
</span>
<h4 className="text-[20px] font-semibold text-[#1A1A1A]">{children}</h4>
</div>
);
}
function Hr({ className = "" }: { className?: string }) {
return <div className={`h-[2px] w-full bg-black/10 ${className}`} />;
}
function InfoBlock({
title,
items,
}: {
title: string;
items?: string[];
}) {
if (!items || items.length === 0) return null;
return (
<div>
<BlockTitle>{title}</BlockTitle>
<ul className="mt-4 list-disc space-y-2 pl-8 text-[16px] leading-[1.8] text-[#4F4F4F]">
{items.map((item, index) => (
<li key={`${title}-${index}`}>{item}</li>
))}
</ul>
</div>
);
}
function InfoList({ item }: { item: SewageCase }) {
return (
<div className="mt-10 space-y-9 px-10">
<InfoBlock title="사업명" items={[item.projectName]} />
<InfoBlock title="사업목적" items={item.purpose} />
<InfoBlock title="사업방식" items={item.method} />
<InfoBlock title="사업 주요 내용" items={item.details} />
<InfoBlock title="특이사항" items={item.note} />
</div>
);
}
const sewageCases: SewageCase[] = [
{
id: "example-sewage-guri",
title: "경기도 구리시",
slides: [
{
src: "/images/landing/business/sewage/guri_01.png",
alt: "구리시 도로자동청소시스템",
},
{
src: "/images/landing/business/sewage/guri_02.png",
alt: "구리시 도로자동청소시스템 위치도",
},
{
src: "/images/landing/business/sewage/guri_03.png",
alt: "구리시 도로자동청소시스템 위치도2",
},
{
src: "/images/landing/business/sewage/guri_04.png",
alt: "구리시 도로자동청소시스템 원리",
},
],
projectName: "구리시 도로자동청소시스템 설치사업",
purpose: [
"고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용하여 미세먼지 저감 및 폭염 취약성 완화",
],
details: [
"총사업비 : 61억원",
"위치 : 구리시 경춘로 및 갈매중앙로 일대 등",
"관로 : 5.8km",
"공급량 : 178㎥/일",
],
},
{
id: "example-sewage-bucheon",
title: "경기도 부천시",
slides: [
{
src: "/images/landing/business/sewage/bucheon_01.png",
alt: "부천시 하수 재이용수 생산시설",
},
{
src: "/images/landing/business/sewage/bucheon_02.png",
alt: "부천시 하수처리수 재이용 사례",
},
],
projectName: "부천시 하수 재이용수 생산시설 설치 운영",
purpose: [
"굴포하수처리시설 하수처리수 재이용수 생산시설 설치 운영을 통해 여월천 등 부천 관내 하천의 건천화 방지, 하천 생태계 보존 및 주민들에게 친수 공간 제공",
],
details: [
"총사업비 : 212억원",
"위치 : 부천시 여월천, 삼정천, 심곡천 및 시민의 강",
"관로 : 23.6km",
"공급량 : 50,000㎥/일",
],
},
{
id: "example-sewage-suwon",
title: "경기도 수원시",
slides: [
{
src: "/images/landing/business/sewage/suwon_01.png",
alt: "수원시 황구지천1",
},
{
src: "/images/landing/business/sewage/suwon_02.png",
alt: "수원시 황구지천2",
},
{
src: "/images/landing/business/sewage/suwon_03.png",
alt: "수원시 황구지천 지도",
},
],
projectName: "수원시 황구지천 하수처리수 재이용사업",
purpose: [
"황구지천 공공하수처리시설 하수처리수 재이용사업으로 황구 호매실천 및 금곡천 건천화 방지로 주민들에게 친수 공간 제공 및 하천 생태계 보전",
],
details: [
"총사업비 : 110억원",
"위치 : 수원시 권선구 호매실천, 금곡천",
"관로 : 3.9km",
"공급량 : 25,000㎥/일(하수처리시설 용량 : 45,000㎥/일)",
],
},
{
id: "example-sewage-paju",
title: "경기도 파주시",
slides: [
{
src: "/images/landing/business/sewage/paju_01.png",
alt: "파주시 하수처리수 시설 전경1",
},
{
src: "/images/landing/business/sewage/paju_02.png",
alt: "파주시 하수처리수 시설 전경2",
},
],
projectName: "파주시 하수처리수 재이용사업",
purpose: [
"운정 및 금촌하수처리시설의 하수처리수를 막공법으로 재처리하여 파주LCD산업단지 내 3개 업체에 공업용수를 공급(LG디스플레이, 희성전자, 엔비텍)",
"하수처리수 재이용시설 자체 농축폐수 처리공정 개발 및 적용 성공사례",
],
method: ["민간투자사업(시행사 : 파주그린허브(주), 운영사 : 테크로스)"],
details: [
"총사업비 : 651억원",
"위치 : 파주시 금촌동 714번지 일원",
"관로 : 13.6km",
"공급량 : 40,000㎥/일(시설용량 : 41,200㎥/일)",
],
},
{
id: "example-sewage-pohang",
title: "경상북도 포항시",
slides: [
{
src: "/images/landing/business/sewage/pohang_01.png",
alt: "포항시 하수처리수 전경1",
},
{
src: "/images/landing/business/sewage/pohang_02.png",
alt: "포항시 하수처리수 전경2",
},
{
src: "/images/landing/business/sewage/pohang_03.png",
alt: "포항시 하수처리수 재이용 공급계통",
},
],
projectName: "포항시 하수처리수 재이용사업",
purpose: [
"포항시 내 공단 개발 확대로 지속적이고 안정적인 공업용수 공급 추진(포스코, 동국산업 등)",
],
method: ["민간투자사업(운영사 : ㈜포웰, 한국수자원공사)"],
details: [
"총사업비 : 1,400억원",
"위치 : 포항시 남구 상도동 125번지 일원",
"관로 : 11.7km",
"공급량 : 100,000㎥/일(시설용량 : 100,000㎥/일)",
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O)",
],
},
{
id: "example-sewage-ulsan",
title: "울산광역시",
slides: [
{
src: "/images/landing/business/sewage/ulsan_01.png",
alt: "울산광역시 폐수처리수 전경1",
},
{
src: "/images/landing/business/sewage/ulsan_02.png",
alt: "울산광역시 폐수처리수 전경2",
},
{
src: "/images/landing/business/sewage/ulsan_03.png",
alt: "울산광역시 폐수처리수 시설1",
},
{
src: "/images/landing/business/sewage/ulsan_04.png",
alt: "울산광역시 폐수처리수 시설2",
},
],
projectName: "울산광역시 폐수처리수 재이용사업",
purpose: [
"’18년 갈수기시 낙동강 수질 악화로 울산석유화학단지 용수 공급 부족을 해소하기 위해 미포산단 내 한화케미칼 등 8개사 공업용수 공급",
],
method: ["민간투자사업(시행사 :울산화이텍워터(주))"],
details: [
"총사업비 : 1,729억원",
"위치 : 울산광역시 울주군 청량읍 상개로 14 일원",
"관로 : 11.7km",
"공급량 : 17,000㎥/일(시설용량 : 86,000㎥/일)",
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O, EDI)",
],
},
{
id: "example-sewage-yeosu",
title: "전라남도 여수시",
slides: [
{
src: "/images/landing/business/sewage/yeosu_01.png",
alt: "여수시 하수처리수 재이용시설1",
},
{
src: "/images/landing/business/sewage/yeosu_02.png",
alt: "여수시 하수처리수 재이용시설2",
},
],
projectName: "여수시 하수처리수 재이용사업",
purpose: [
"수요자가 원하는 양질의 공업용수를 저렴하게 공급하여 한정된 수자원의 효율적 이용 및 지역적 물 부족 해소, 하수재이용을 통한 오염총량 저감 및 방류수역 수질개선을 위한 여수국가산단 내 공업용수 공급(GS칼텍스, YNCC 등)",
],
method: ["민간투자사업(시행사 : 여수그린허브(주), 운영사 : 테크로스)"],
details: [
"총사업비 : 750억원",
"위치 : 여수시 신월로 284 일원",
"관로 : 18.6km",
"공급량 : 50,000㎥/일(시설용량 : 50,000㎥/일)",
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O)",
],
},
{
id: "example-sewage-boryeong",
title: "충청남도 보령시",
slides: [
{
src: "/images/landing/business/sewage/boryeong_01.png",
alt: "보령시 하수처리수 재이용시설1",
},
{
src: "/images/landing/business/sewage/boryeong_02.png",
alt: "보령시 하수처리수 재이용시설2",
},
],
projectName: "보령시 하수처리수 재이용사업",
purpose: [
"한국중부발전(주)의 보령 및 신보령화력발전소에 10,000㎥/일의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업 추진",
"국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU(기후에너지환경부, 충청남도, 보령시, 한국중부발전(주))를 바탕으로 추진되어 경제성보다 공익성을 바탕으로 국가전력 기반시설의 안정적 운영 도모",
],
method: ["재정사업(2017년 한국환경공단과 사업대행 위·수탁협약 체결)"],
details: [
"총사업비 : 315억원",
"위치 : 보령시 대천방조제로 322 일원",
"관로 : 8.7km",
"공급량 : 10,000㎥/일(시설용량 : 10,000㎥/일)",
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O)",
],
note: [
"국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU(환경부, 충청남도, 보령시, 한국중부발전㈜)를 바탕으로 추진 되어 경제성보다 공익성을 바탕으로 국가전력 기반시설의 안정적 운영 도모",
],
},
{
id: "example-sewage-asan",
title: "충청남도 아산시",
slides: [
{
src: "/images/landing/business/sewage/asan_01.png",
alt: "아산시 하수처리수 재이용시설",
},
{
src: "/images/landing/business/sewage/asan_02.png",
alt: "아산시 하수처리수 재이용 공정도",
},
],
projectName: "아산시 하수처리수 재이용사업",
purpose: [
"신도시 개발에 따른 인구 급증으로 발생하는 하수를 재처리하여 탕정산업단지(삼성디스플레이)에 공업용수를 안정적으로 공급함으로써 지역기반 및 활성화를 도모",
],
method: ["민간투자사업(시행사 : 아산스마트원터, 운영사 : 한국수자원공사)"],
details: [
"총사업비 : 297억원",
"위치 : 아산시 탕정면 갈산리 807 일원",
"관로 : 4.2km",
"공급량 : 27,000㎥/일(시설용량 : 45,000㎥/일)",
"처리공법 : 전처리시설(U/F) + 주처리시설(R/O)",
],
},
];
import { useEffect, useRef } from "react";
export default function SewageExampleSection() {
const title = "하수처리수 재이용 사례";
const rootRef = useRef<HTMLDivElement>(null);
return (
<div className="bg-white">
<div className="inline-block">
<h2 className="relative inline-block px-2 text-[28px] font-extrabold leading-none tracking-tight text-slate-900">
<span className="relative z-10">{title}</span>
<span className="absolute bottom-[-4px] left-0 z-0 h-3 w-full bg-gradient-to-r from-sky-300 to-green-300" />
useEffect(() => {
const root = rootRef.current;
if (!root) return;
const cleanups: Array<() => void> = [];
const sliders = Array.from(root.querySelectorAll(".image-slider"));
sliders.forEach((sliderElement) => {
const slider = sliderElement as HTMLElement;
const viewport = slider.querySelector(
".image-slider-viewport",
) as HTMLElement | null;
const track = slider.querySelector(".image-slider-track") as HTMLElement | null;
const dotsWrap = slider.querySelector(
".image-slider-dots",
) as HTMLElement | null;
const pages = Array.from(
slider.querySelectorAll(".image-slider-page"),
) as HTMLElement[];
if (!viewport || !track || !dotsWrap || pages.length === 0) return;
let page = 0;
let dragging = false;
let isHover = false;
let startX = 0;
let deltaX = 0;
let timer: number | null = null;
const autoPlayMs = Number(slider.dataset.autoplay ?? "4500");
const canSlide = pages.length > 1;
if (!canSlide) {
dotsWrap.style.display = "none";
} else {
dotsWrap.style.display = "";
}
const render = () => {
track.style.transform = `translateX(-${page * 100}%)`;
Array.from(dotsWrap.querySelectorAll("button")).forEach((dot, index) => {
dot.classList.toggle("is-active", index === page);
});
};
const goTo = (nextPage: number) => {
if (!canSlide) return;
if (nextPage < 0) page = pages.length - 1;
else if (nextPage >= pages.length) page = 0;
else page = nextPage;
render();
};
const stopTimer = () => {
if (timer !== null) {
window.clearInterval(timer);
timer = null;
}
};
const startTimer = () => {
stopTimer();
if (!canSlide || dragging || isHover) return;
timer = window.setInterval(() => {
page = page + 1 >= pages.length ? 0 : page + 1;
render();
}, autoPlayMs);
};
const dotButtons = pages.map((_, index) => {
const button = document.createElement("button");
button.type = "button";
button.className = "image-slider-dot";
const onClick = () => {
goTo(index);
startTimer();
};
button.addEventListener("click", onClick);
dotsWrap.appendChild(button);
cleanups.push(() => {
button.removeEventListener("click", onClick);
});
return button;
});
const onMouseEnter = () => {
isHover = true;
stopTimer();
};
const onMouseLeave = () => {
isHover = false;
startTimer();
};
const onPointerDown = (event: PointerEvent) => {
if (!canSlide) return;
dragging = true;
startX = event.clientX;
deltaX = 0;
viewport.setPointerCapture(event.pointerId);
};
const onPointerMove = (event: PointerEvent) => {
if (!dragging) return;
deltaX = event.clientX - startX;
};
const onPointerUp = () => {
if (!dragging) return;
dragging = false;
if (deltaX > 50) goTo(page - 1);
if (deltaX < -50) goTo(page + 1);
deltaX = 0;
startTimer();
};
viewport.addEventListener("mouseenter", onMouseEnter);
viewport.addEventListener("mouseleave", onMouseLeave);
viewport.addEventListener("pointerdown", onPointerDown);
viewport.addEventListener("pointermove", onPointerMove);
viewport.addEventListener("pointerup", onPointerUp);
viewport.addEventListener("pointercancel", onPointerUp);
cleanups.push(() => {
stopTimer();
viewport.removeEventListener("mouseenter", onMouseEnter);
viewport.removeEventListener("mouseleave", onMouseLeave);
viewport.removeEventListener("pointerdown", onPointerDown);
viewport.removeEventListener("pointermove", onPointerMove);
viewport.removeEventListener("pointerup", onPointerUp);
viewport.removeEventListener("pointercancel", onPointerUp);
dotButtons.forEach((button) => button.remove());
});
render();
startTimer();
});
return () => {
cleanups.forEach((cleanup) => cleanup());
};
}, []);
const contentsHtml = `
<div class="contents-page">
<div class="page-title">
<h2>
<span class="title-text">하수처리수 재이용 사례</span>
<span class="title-bg">&nbsp;</span>
</h2>
</div>
<div className="mt-10 pb-[120px]">
{sewageCases.map((item, index) => (
<section key={item.id} id={item.id} className="scroll-mt-[120px]">
<SectionTitle>{item.title}</SectionTitle>
<div class="sections sewage-example-sections">
<section id="example-sewage-guri" class="section first">
<div class="section-title">
<span class="section-bar">&nbsp;</span>
<h3>경기도 구리시</h3>
</div>
<CaseImageSlider
className="mt-8"
maxWidthClassName="max-w-[1240px]"
slides={item.slides}
/>
<div class="image-slider" data-autoplay="4500">
<div class="image-slider-inner">
<div class="image-slider-viewport">
<div class="image-slider-track">
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/guri_01.png" alt="구리시 도로자동청소시스템" /></div>
<div class="image-slide"><img src="/images/landing/business/sewage/guri_02.png" alt="구리시 도로자동청소시스템 위치도" /></div>
</div>
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/guri_03.png" alt="구리시 도로자동청소시스템 위치도2" /></div>
<div class="image-slide"><img src="/images/landing/business/sewage/guri_04.png" alt="구리시 도로자동청소시스템 원리" /></div>
</div>
</div>
</div>
<div class="image-slider-dots"></div>
</div>
</div>
<InfoList item={item} />
<div class="content-group">
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업명</h4></div>
<ul class="bullet-list"><li>구리시 도로자동청소시스템 설치사업</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업목적</h4></div>
<ul class="bullet-list"><li>고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용하여 미세먼지 저감 및 폭염 취약성 완화</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업 주요 내용</h4></div>
<ul class="bullet-list">
<li>총사업비 : 61억원</li>
<li>위치 : 구리시 경춘로 및 갈매중앙로 일대 등</li>
<li>관로 : 5.8km</li>
<li>공급량 : 178㎥/일</li>
</ul>
</div>
</div>
</section>
{index !== sewageCases.length - 1 && <Hr className="my-20" />}
</section>
))}
<div class="divider"></div>
<section id="example-sewage-bucheon" class="section">
<div class="section-title"><span class="section-bar">&nbsp;</span><h3>경기도 부천시</h3></div>
<div class="image-slider" data-autoplay="4500">
<div class="image-slider-inner">
<div class="image-slider-viewport">
<div class="image-slider-track">
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/bucheon_01.png" alt="부천시 하수 재이용수 생산시설" /></div>
<div class="image-slide"><img src="/images/landing/business/sewage/bucheon_02.png" alt="부천시 하수처리수 재이용 사례" /></div>
</div>
</div>
</div>
<div class="image-slider-dots"></div>
</div>
</div>
<div class="content-group">
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업명</h4></div>
<ul class="bullet-list"><li>부천시 하수 재이용수 생산시설 설치 운영</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업목적</h4></div>
<ul class="bullet-list"><li>굴포하수처리시설 하수처리수 재이용수 생산시설 설치 운영을 통해 여월천 등 부천 관내 하천의 건천화 방지, 하천 생태계 보존 및 주민들에게 친수 공간 제공</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업 주요 내용</h4></div>
<ul class="bullet-list">
<li>총사업비 : 212억원</li>
<li>위치 : 부천시 여월천, 삼정천, 심곡천 및 시민의 강</li>
<li>관로 : 23.6km</li>
<li>공급량 : 50,000㎥/일</li>
</ul>
</div>
</div>
</section>
<div class="divider"></div>
<section id="example-sewage-suwon" class="section">
<div class="section-title"><span class="section-bar">&nbsp;</span><h3>경기도 수원시</h3></div>
<div class="image-slider" data-autoplay="4500">
<div class="image-slider-inner">
<div class="image-slider-viewport">
<div class="image-slider-track">
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/suwon_01.png" alt="수원시 황구지천1" /></div>
<div class="image-slide"><img src="/images/landing/business/sewage/suwon_02.png" alt="수원시 황구지천2" /></div>
</div>
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/suwon_03.png" alt="수원시 황구지천 지도" /></div>
</div>
</div>
</div>
<div class="image-slider-dots"></div>
</div>
</div>
<div class="content-group">
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업명</h4></div>
<ul class="bullet-list"><li>수원시 황구지천 하수처리수 재이용사업</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업목적</h4></div>
<ul class="bullet-list"><li>황구지천 공공하수처리시설 하수처리수 재이용사업으로 황구 호매실천 및 금곡천 건천화 방지로 주민들에게 친수 공간 제공 및 하천 생태계 보전</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업 주요 내용</h4></div>
<ul class="bullet-list">
<li>총사업비 : 110억원</li>
<li>위치 : 수원시 권선구 호매실천, 금곡천</li>
<li>관로 : 3.9km</li>
<li>공급량 : 25,000㎥/일(하수처리시설 용량 : 45,000㎥/일)</li>
</ul>
</div>
</div>
</section>
<div class="divider"></div>
<section id="example-sewage-paju" class="section">
<div class="section-title"><span class="section-bar">&nbsp;</span><h3>경기도 파주시</h3></div>
<div class="image-slider" data-autoplay="4500">
<div class="image-slider-inner">
<div class="image-slider-viewport">
<div class="image-slider-track">
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/paju_01.png" alt="파주시 하수처리수 시설 전경1" /></div>
<div class="image-slide"><img src="/images/landing/business/sewage/paju_02.png" alt="파주시 하수처리수 시설 전경2" /></div>
</div>
</div>
</div>
<div class="image-slider-dots"></div>
</div>
</div>
<div class="content-group">
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업명</h4></div>
<ul class="bullet-list"><li>파주시 하수처리수 재이용사업</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업목적</h4></div>
<ul class="bullet-list">
<li>운정 및 금촌하수처리시설의 하수처리수를 막공법으로 재처리하여 파주LCD산업단지 내 3개 업체에 공업용수를 공급(LG디스플레이, 희성전자, 엔비텍)</li>
<li>하수처리수 재이용시설 자체 농축폐수 처리공정 개발 및 적용 성공사례</li>
</ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업방식</h4></div>
<ul class="bullet-list"><li>민간투자사업(시행사 : 파주그린허브(주), 운영사 : 테크로스)</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업 주요 내용</h4></div>
<ul class="bullet-list">
<li>총사업비 : 651억원</li>
<li>위치 : 파주시 금촌동 714번지 일원</li>
<li>관로 : 13.6km</li>
<li>공급량 : 40,000㎥/일(시설용량 : 41,200㎥/일)</li>
</ul>
</div>
</div>
</section>
<div class="divider"></div>
<section id="example-sewage-pohang" class="section">
<div class="section-title"><span class="section-bar">&nbsp;</span><h3>경상북도 포항시</h3></div>
<div class="image-slider" data-autoplay="4500">
<div class="image-slider-inner">
<div class="image-slider-viewport">
<div class="image-slider-track">
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/pohang_01.png" alt="포항시 하수처리수 전경1" /></div>
<div class="image-slide"><img src="/images/landing/business/sewage/pohang_02.png" alt="포항시 하수처리수 전경2" /></div>
</div>
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/pohang_03.png" alt="포항시 하수처리수 재이용 공급계통" /></div>
</div>
</div>
</div>
<div class="image-slider-dots"></div>
</div>
</div>
<div class="content-group">
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업명</h4></div>
<ul class="bullet-list"><li>포항시 하수처리수 재이용사업</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업목적</h4></div>
<ul class="bullet-list"><li>포항시 내 공단 개발 확대로 지속적이고 안정적인 공업용수 공급 추진(포스코, 동국산업 등)</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업방식</h4></div>
<ul class="bullet-list"><li>민간투자사업(운영사 : ㈜포웰, 한국수자원공사)</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업 주요 내용</h4></div>
<ul class="bullet-list">
<li>총사업비 : 1,400억원</li>
<li>위치 : 포항시 남구 상도동 125번지 일원</li>
<li>관로 : 11.7km</li>
<li>공급량 : 100,000㎥/일(시설용량 : 100,000㎥/일)</li>
<li>처리공법 : 전처리시설(U/F) + 주처리시설(R/O)</li>
</ul>
</div>
</div>
</section>
<div class="divider"></div>
<section id="example-sewage-ulsan" class="section">
<div class="section-title"><span class="section-bar">&nbsp;</span><h3>울산광역시</h3></div>
<div class="image-slider" data-autoplay="4500">
<div class="image-slider-inner">
<div class="image-slider-viewport">
<div class="image-slider-track">
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/ulsan_01.png" alt="울산광역시 폐수처리수 전경1" /></div>
<div class="image-slide"><img src="/images/landing/business/sewage/ulsan_02.png" alt="울산광역시 폐수처리수 전경2" /></div>
</div>
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/ulsan_03.png" alt="울산광역시 폐수처리수 시설1" /></div>
<div class="image-slide"><img src="/images/landing/business/sewage/ulsan_04.png" alt="울산광역시 폐수처리수 시설2" /></div>
</div>
</div>
</div>
<div class="image-slider-dots"></div>
</div>
</div>
<div class="content-group">
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업명</h4></div>
<ul class="bullet-list"><li>울산광역시 폐수처리수 재이용사업</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업목적</h4></div>
<ul class="bullet-list"><li>’18년 갈수기시 낙동강 수질 악화로 울산석유화학단지 용수 공급 부족을 해소하기 위해 미포산단 내 한화케미칼 등 8개사 공업용수 공급</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업방식</h4></div>
<ul class="bullet-list"><li>민간투자사업(시행사 :울산화이텍워터(주))</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업 주요 내용</h4></div>
<ul class="bullet-list">
<li>총사업비 : 1,729억원</li>
<li>위치 : 울산광역시 울주군 청량읍 상개로 14 일원</li>
<li>관로 : 11.7km</li>
<li>공급량 : 17,000㎥/일(시설용량 : 86,000㎥/일)</li>
<li>처리공법 : 전처리시설(U/F) + 주처리시설(R/O, EDI)</li>
</ul>
</div>
</div>
</section>
<div class="divider"></div>
<section id="example-sewage-yeosu" class="section">
<div class="section-title"><span class="section-bar">&nbsp;</span><h3>전라남도 여수시</h3></div>
<div class="image-slider" data-autoplay="4500">
<div class="image-slider-inner">
<div class="image-slider-viewport">
<div class="image-slider-track">
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/yeosu_01.png" alt="여수시 하수처리수 재이용시설1" /></div>
<div class="image-slide"><img src="/images/landing/business/sewage/yeosu_02.png" alt="여수시 하수처리수 재이용시설2" /></div>
</div>
</div>
</div>
<div class="image-slider-dots"></div>
</div>
</div>
<div class="content-group">
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업명</h4></div>
<ul class="bullet-list"><li>여수시 하수처리수 재이용사업</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업목적</h4></div>
<ul class="bullet-list"><li>수요자가 원하는 양질의 공업용수를 저렴하게 공급하여 한정된 수자원의 효율적 이용 및 지역적 물 부족 해소, 하수재이용을 통한 오염총량 저감 및 방류수역 수질개선을 위한 여수국가산단 내 공업용수 공급(GS칼텍스, YNCC 등)</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업방식</h4></div>
<ul class="bullet-list"><li>민간투자사업(시행사 : 여수그린허브(주), 운영사 : 테크로스)</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업 주요 내용</h4></div>
<ul class="bullet-list">
<li>총사업비 : 750억원</li>
<li>위치 : 여수시 신월로 284 일원</li>
<li>관로 : 18.6km</li>
<li>공급량 : 50,000㎥/일(시설용량 : 50,000㎥/일)</li>
<li>처리공법 : 전처리시설(U/F) + 주처리시설(R/O)</li>
</ul>
</div>
</div>
</section>
<div class="divider"></div>
<section id="example-sewage-boryeong" class="section">
<div class="section-title"><span class="section-bar">&nbsp;</span><h3>충청남도 보령시</h3></div>
<div class="image-slider" data-autoplay="4500">
<div class="image-slider-inner">
<div class="image-slider-viewport">
<div class="image-slider-track">
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/boryeong_01.png" alt="보령시 하수처리수 재이용시설1" /></div>
<div class="image-slide"><img src="/images/landing/business/sewage/boryeong_02.png" alt="보령시 하수처리수 재이용시설2" /></div>
</div>
</div>
</div>
<div class="image-slider-dots"></div>
</div>
</div>
<div class="content-group">
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업명</h4></div>
<ul class="bullet-list"><li>보령시 하수처리수 재이용사업</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업목적</h4></div>
<ul class="bullet-list">
<li>한국중부발전(주)의 보령 및 신보령화력발전소에 10,000㎥/일의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업 추진</li>
<li>국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU(기후에너지환경부, 충청남도, 보령시, 한국중부발전(주))를 바탕으로 추진되어 경제성보다 공익성을 바탕으로 국가전력 기반시설의 안정적 운영 도모</li>
</ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업방식</h4></div>
<ul class="bullet-list"><li>재정사업(2017년 한국환경공단과 사업대행 위·수탁협약 체결)</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업 주요 내용</h4></div>
<ul class="bullet-list">
<li>총사업비 : 315억원</li>
<li>위치 : 보령시 대천방조제로 322 일원</li>
<li>관로 : 8.7km</li>
<li>공급량 : 10,000㎥/일(시설용량 : 10,000㎥/일)</li>
<li>처리공법 : 전처리시설(U/F) + 주처리시설(R/O)</li>
</ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>특이사항</h4></div>
<ul class="bullet-list"><li>국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU(환경부, 충청남도, 보령시, 한국중부발전㈜)를 바탕으로 추진 되어 경제성보다 공익성을 바탕으로 국가전력 기반시설의 안정적 운영 도모</li></ul>
</div>
</div>
</section>
<div class="divider"></div>
<section id="example-sewage-asan" class="section">
<div class="section-title"><span class="section-bar">&nbsp;</span><h3>충청남도 아산시</h3></div>
<div class="image-slider" data-autoplay="4500">
<div class="image-slider-inner">
<div class="image-slider-viewport">
<div class="image-slider-track">
<div class="image-slider-page">
<div class="image-slide"><img src="/images/landing/business/sewage/asan_01.png" alt="아산시 하수처리수 재이용시설" /></div>
<div class="image-slide"><img src="/images/landing/business/sewage/asan_02.png" alt="아산시 하수처리수 재이용 공정도" /></div>
</div>
</div>
</div>
<div class="image-slider-dots"></div>
</div>
</div>
<div class="content-group">
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업명</h4></div>
<ul class="bullet-list"><li>아산시 하수처리수 재이용사업</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업목적</h4></div>
<ul class="bullet-list"><li>신도시 개발에 따른 인구 급증으로 발생하는 하수를 재처리하여 탕정산업단지(삼성디스플레이)에 공업용수를 안정적으로 공급함으로써 지역기반 및 활성화를 도모</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업방식</h4></div>
<ul class="bullet-list"><li>민간투자사업(시행사 : 아산스마트원터, 운영사 : 한국수자원공사)</li></ul>
</div>
<div class="content-block">
<div class="block-title"><span class="block-title-icon"><span class="bubble bubble-sky">&nbsp;</span><span class="bubble bubble-green">&nbsp;</span></span><h4>사업 주요 내용</h4></div>
<ul class="bullet-list">
<li>총사업비 : 297억원</li>
<li>위치 : 아산시 탕정면 갈산리 807 일원</li>
<li>관로 : 4.2km</li>
<li>공급량 : 27,000㎥/일(시설용량 : 45,000㎥/일)</li>
<li>처리공법 : 전처리시설(U/F) + 주처리시설(R/O)</li>
</ul>
</div>
</div>
</section>
</div>
</div>
`;
return (
<div
ref={rootRef}
className="bg-white"
dangerouslySetInnerHTML={{ __html: contentsHtml }}
/>
);
}
}
@@ -0,0 +1,8 @@
import { ContentsPage } from "@/components/ui/contents";
export default function ExampleSection() {
const contsSn = Number(process.env.CONTENTS_EXAMPLE ?? 32);
const menuCd = "24";
return <ContentsPage contsSn={contsSn} menuCd={menuCd} />;
}
+10 -7
View File
@@ -1,13 +1,15 @@
/**
* 물 재이용 사례
*/
export const dynamic = "force-dynamic";
import SubPageHeader from "@/components/SubPageHeader";
import BusinessSubNav, { type BusinessSubNavMenu } from "@/components/BusinessSubNav";
import RainExampleSection from "./_components/RainExampleSection";
import GreywaterExampleSection from "./_components/GreywaterExampleSection";
import SewageExampleSection from "./_components/SewageExampleSection";
import GlobalCaseSection from "./_components/GlobalCaseSection";
import ExampleSection from "./exampleSection";
// import RainExampleSection from "./_components/RainExampleSection";
// import GreywaterExampleSection from "./_components/GreywaterExampleSection";
// import SewageExampleSection from "./_components/SewageExampleSection";
// import GlobalCaseSection from "./_components/GlobalCaseSection";
const menus: BusinessSubNavMenu[] = [
{
@@ -78,11 +80,12 @@ export default function ExamplePage() {
<BusinessSubNav menus={menus} offsetTop={120} />
<div className="max-w-[1320px] pb-24">
<RainExampleSection />
<ExampleSection />
{/* <RainExampleSection />
<GreywaterExampleSection />
<SewageExampleSection />
<GlobalCaseSection />
<GlobalCaseSection /> */}
</div>
</div>
);
}
}
@@ -6,14 +6,14 @@ export default function LawIntroSection() {
<div class="page-title">
<h2>
<span class="title-text">물재이용법</span>
<span class="title-bg"></span>
<span class="title-bg">&nbsp;</span>
</h2>
</div>
<div class="sections">
<section id="law-plan" class="section first">
<div class="section-title">
<span class="section-bar"></span>
<span class="section-bar">&nbsp;</span>
<h3>법률 제·개정 및 기본계획 수립</h3>
</div>
<div class="image-wrap">
@@ -25,7 +25,7 @@ export default function LawIntroSection() {
<section id="plan-performance" class="section">
<div class="section-title">
<span class="section-bar"></span>
<span class="section-bar">&nbsp;</span>
<h3>제2차 계획 대비 실적 현황</h3>
</div>
<div class="image-wrap">
@@ -37,7 +37,7 @@ export default function LawIntroSection() {
<section id="domestic-change" class="section">
<div class="section-title">
<span class="section-bar"></span>
<span class="section-bar">&nbsp;</span>
<h3>우리나라의 수자원 현황 및 물이용 여건</h3>
</div>
<div class="image-wrap">
@@ -49,7 +49,7 @@ export default function LawIntroSection() {
<section id="policy-history" class="section">
<div class="section-title">
<span class="section-bar"></span>
<span class="section-bar">&nbsp;</span>
<h3>물 재이용 정책 변화 추이</h3>
</div>
<div class="image-wrap">
@@ -61,7 +61,7 @@ export default function LawIntroSection() {
<section id="law-relation" class="section">
<div class="section-title">
<span class="section-bar"></span>
<span class="section-bar">&nbsp;</span>
<h3>타법령과의 관계 검토</h3>
</div>
<div class="image-wrap">
@@ -73,7 +73,7 @@ export default function LawIntroSection() {
<section id="domestic-linkage" class="section">
<div class="section-title">
<span class="section-bar"></span>
<span class="section-bar">&nbsp;</span>
<h3>주요 국가계획의 정책방향과 연계한 물 재이용 정책 필요</h3>
</div>
<div class="image-wrap">
+478
View File
@@ -85,3 +85,481 @@
margin: 2.75rem 0;
background: rgba(0, 0, 0, 0.1);
}
.contents-page .rain-example-sections {
padding-bottom: 120px;
}
.contents-page .image-slider {
margin-top: 2rem;
}
.contents-page .image-slider-inner {
max-width: 1240px;
margin: 0 auto;
}
.contents-page .image-slider-viewport {
overflow: hidden;
user-select: none;
touch-action: pan-y;
cursor: grab;
}
.contents-page .image-slider-viewport:active {
cursor: grabbing;
}
.contents-page .image-slider-track {
display: flex;
transition: transform 0.5s ease-out;
}
.contents-page .image-slider-page {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1.25rem;
min-width: 100%;
}
.contents-page .image-slide {
position: relative;
height: 340px;
overflow: hidden;
border-radius: 20px;
background: #f5f5f5;
}
.contents-page .image-slide img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.contents-page .image-slider-dots {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
margin-top: 1.25rem;
}
.contents-page .image-slider-dot {
width: 8px;
height: 8px;
border: 0;
border-radius: 9999px;
background: #d8dde3;
transition: all 0.2s ease;
cursor: pointer;
}
.contents-page .image-slider-dot.is-active {
background: #18bceb;
}
@media (max-width: 768px) {
.contents-page .image-slider-page {
grid-template-columns: 1fr;
}
.contents-page .image-slide {
height: 280px;
}
}
.contents-page .content-group {
margin-top: 2.5rem;
padding: 0 2.5rem;
}
.contents-page .content-block + .content-block {
margin-top: 2.25rem;
}
.contents-page .block-title {
display: flex;
align-items: center;
gap: 0.5rem;
}
.contents-page .block-title-icon {
position: relative;
display: inline-block;
width: 27px;
height: 16px;
flex-shrink: 0;
}
.contents-page .block-title-icon .bubble {
position: absolute;
top: 0;
width: 16px;
height: 16px;
border-radius: 9999px;
}
.contents-page .block-title-icon .bubble-sky {
left: 0;
background: rgba(1, 172, 229, 0.4);
}
.contents-page .block-title-icon .bubble-green {
left: 7px;
background: rgba(109, 187, 79, 0.4);
}
.contents-page .block-title h4 {
font-size: 20px;
font-weight: 600;
color: #1a1a1a;
}
.contents-page .bullet-list {
margin-top: 1rem;
padding-left: 2rem;
color: #4f4f4f;
font-size: 16px;
line-height: 1.8;
list-style: disc;
}
.contents-page .table-wrap {
margin-top: 2rem;
overflow-x: auto;
}
.contents-page .contents-table {
width: 100%;
min-width: 860px;
border-collapse: collapse;
border: 1px solid #d8dde3;
text-align: center;
}
.contents-page .contents-table th,
.contents-page .contents-table td {
border: 1px solid #d8dde3;
padding: 0.75rem 1rem;
}
.contents-page .contents-table th {
font-size: 18px;
font-weight: 500;
color: #fff;
}
.contents-page .contents-table td {
background: #fff;
font-size: 16px;
color: #4f4f4f;
}
.contents-page .contents-table .th-blue {
background: #14a9d9;
}
.contents-page .contents-table .th-teal {
background: #39b2bd;
}
.contents-page .contents-table .th-mint {
background: #58b99a;
}
.contents-page .contents-table .th-green {
background: #70bf4b;
}
.contents-page .contents-table .th-teal-greywater {
background: #40b5a5;
}
.contents-page .contents-table th.th-gray,
.contents-page .global-table th.th-gray {
background: #bfc9d2;
color: #222;
}
.contents-page .contents-table th.th-blue,
.contents-page .global-table th.th-blue {
background: #14a9d9;
color: #fff;
}
.contents-page .global-table th.th-teal-2 {
background: #55bda5;
color: #fff;
}
.contents-page .global-table th.th-green-2 {
background: #6ab84e;
color: #fff;
}
.contents-page .bg-cyan {
background: #eaf7f8 !important;
}
.contents-page .bg-green {
background: #eaf8e6 !important;
}
.contents-page .global-case-sections {
padding-bottom: 120px;
}
.contents-page .greywater-example-sections {
padding-bottom: 120px;
}
.contents-page .sewage-example-sections {
padding-bottom: 120px;
}
.contents-page .global-content-group {
gap: 0;
}
.contents-page .content-rich-list {
margin-top: 1rem;
color: #4f4f4f;
font-size: 16px;
line-height: 1.8;
}
.contents-page .content-rich-list li + li {
margin-top: 0.5rem;
}
.contents-page .content-rich-list .depth-0 {
margin-left: 1.5rem;
padding-left: 0.25rem;
list-style: disc;
font-weight: 600;
color: #555;
}
.contents-page .content-rich-list .depth-1 {
margin-left: 2.125rem;
list-style: none;
color: #777;
}
.contents-page .content-rich-list .depth-1::before {
content: "- ";
}
.contents-page .content-rich-list .depth-2 {
margin-left: 3.25rem;
list-style: none;
color: #666;
}
.contents-page .content-rich-list .depth-3 {
margin-left: 3.5rem;
list-style: none;
color: #777;
}
.contents-page .global-notice-box,
.contents-page .global-description-box {
max-width: 1000px;
margin: 1.25rem auto 0;
border-radius: 20px;
background: #eaf7f8;
}
.contents-page .global-notice-box {
padding: 1.75rem 2rem;
}
.contents-page .global-description-box {
padding: 1.5rem 1.75rem;
}
.contents-page .global-notice-row {
display: flex;
align-items: flex-start;
gap: 0.5rem;
}
.contents-page .global-water-bullet {
width: 27px;
height: 33px;
margin-top: 0.25rem;
flex-shrink: 0;
}
.contents-page .global-notice-title {
display: block;
font-size: 26px;
font-weight: 800;
color: #007fa4;
}
.contents-page .global-text-dark {
color: #1a1a1a;
}
.contents-page .global-plain-list {
margin-top: 1.25rem;
padding-left: 1.25rem;
list-style: disc;
color: #555;
font-size: 16px;
line-height: 1.7;
}
.contents-page .global-plain-list li + li {
margin-top: 0.5rem;
}
.contents-page .global-table-wrap {
overflow-x: auto;
}
.contents-page .global-overview-table-wrap {
max-width: 1000px;
margin: 1.25rem auto 0;
}
.contents-page .global-table {
width: 100%;
text-align: center;
border-collapse: collapse;
}
.contents-page .global-table th {
padding: 0.75rem 1rem;
font-size: 17px;
font-weight: 700;
color: #fff;
}
.contents-page .overview-compare-table {
min-width: 760px;
}
.contents-page .overview-compare-table th {
padding-top: 1rem;
padding-bottom: 1rem;
font-size: 18px;
font-weight: 500;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.contents-page .overview-compare-table td {
border: 1px solid #aeb9c2;
padding: 1rem;
font-size: 28px;
font-weight: 700;
color: #4f4f4f;
background: #fff;
}
.contents-page .overview-compare-table .country-cell {
font-size: 20px;
color: #333;
}
.contents-page .country-wrap {
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
}
.contents-page .country-flag {
width: 42px;
height: 28px;
object-fit: cover;
}
.contents-page .reuse-rate-table {
min-width: 960px;
}
.contents-page .reuse-rate-table th {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.contents-page .reuse-rate-table td {
border: 1px solid #aeb9c2;
padding: 0.75rem 1rem;
font-size: 15px;
color: #4f4f4f;
background: #fff;
}
.contents-page .reuse-rate-table .cell-strong {
font-weight: 600;
}
.contents-page .info-card-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 1.25rem;
margin-top: 2rem;
}
.contents-page .info-card {
border: 2px solid #01ace5;
border-radius: 8px;
background: #eaf5f5;
padding: 1.25rem;
}
.contents-page .info-card strong {
display: block;
font-size: 20px;
font-weight: 700;
color: #333;
}
.contents-page .info-card p {
margin-top: 0.75rem;
font-size: 15px;
line-height: 1.6;
color: #555;
}
.contents-page .global-table-padding {
margin-top: 2rem;
padding: 0 120px;
}
.contents-page .global-price-table td {
background: #fff;
}
.contents-page .global-accent {
color: #01ace5;
}
@media (max-width: 1200px) {
.contents-page .info-card-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.contents-page .global-table-padding {
padding: 0;
}
}
@media (max-width: 768px) {
.contents-page .global-notice-row {
flex-direction: column;
}
.contents-page .global-notice-title {
font-size: 22px;
}
.contents-page .info-card-grid {
grid-template-columns: 1fr;
}
}
@@ -1,5 +1,7 @@
"use client";
import { useEffect, useRef } from "react";
import type { ContentsDetail } from "@/src/entities/content/model/contents-interface";
type ContentsPageClientProps = {
@@ -7,9 +9,170 @@ type ContentsPageClientProps = {
};
function ContentsPageClient({ initialContent }: ContentsPageClientProps) {
const rootRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const root = rootRef.current;
if (!root) return;
const cleanups: Array<() => void> = [];
const sliders = Array.from(root.querySelectorAll(".image-slider"));
sliders.forEach((sliderElement) => {
const slider = sliderElement as HTMLElement;
const viewport = slider.querySelector(
".image-slider-viewport",
) as HTMLElement | null;
const track = slider.querySelector(
".image-slider-track",
) as HTMLElement | null;
const dotsWrap = slider.querySelector(
".image-slider-dots",
) as HTMLElement | null;
const pages = Array.from(
slider.querySelectorAll(".image-slider-page"),
) as HTMLElement[];
if (!viewport || !track || !dotsWrap || pages.length === 0) return;
let page = 0;
let dragging = false;
let isHover = false;
let startX = 0;
let deltaX = 0;
let timer: number | null = null;
const autoPlayMs = Number(slider.dataset.autoplay ?? "4500");
const canSlide = pages.length > 1;
if (!canSlide) {
dotsWrap.style.display = "none";
} else {
dotsWrap.style.display = "";
}
const render = () => {
track.style.transform = `translateX(-${page * 100}%)`;
Array.from(dotsWrap.querySelectorAll("button")).forEach((dot, index) => {
dot.classList.toggle("is-active", index === page);
});
};
const goTo = (nextPage: number) => {
if (!canSlide) return;
if (nextPage < 0) page = pages.length - 1;
else if (nextPage >= pages.length) page = 0;
else page = nextPage;
render();
};
const stopTimer = () => {
if (timer !== null) {
window.clearInterval(timer);
timer = null;
}
};
const startTimer = () => {
stopTimer();
if (!canSlide || dragging || isHover) return;
timer = window.setInterval(() => {
page = page + 1 >= pages.length ? 0 : page + 1;
render();
}, autoPlayMs);
};
const dotButtons = pages.map((_, index) => {
const button = document.createElement("button");
button.type = "button";
button.className = "image-slider-dot";
const onClick = () => {
goTo(index);
startTimer();
};
button.addEventListener("click", onClick);
dotsWrap.appendChild(button);
cleanups.push(() => {
button.removeEventListener("click", onClick);
});
return button;
});
const onMouseEnter = () => {
isHover = true;
stopTimer();
};
const onMouseLeave = () => {
isHover = false;
startTimer();
};
const onPointerDown = (event: PointerEvent) => {
if (!canSlide) return;
dragging = true;
startX = event.clientX;
deltaX = 0;
viewport.setPointerCapture(event.pointerId);
};
const onPointerMove = (event: PointerEvent) => {
if (!dragging) return;
deltaX = event.clientX - startX;
};
const onPointerUp = () => {
if (!dragging) return;
dragging = false;
if (deltaX > 50) goTo(page - 1);
if (deltaX < -50) goTo(page + 1);
deltaX = 0;
startTimer();
};
viewport.addEventListener("mouseenter", onMouseEnter);
viewport.addEventListener("mouseleave", onMouseLeave);
viewport.addEventListener("pointerdown", onPointerDown);
viewport.addEventListener("pointermove", onPointerMove);
viewport.addEventListener("pointerup", onPointerUp);
viewport.addEventListener("pointercancel", onPointerUp);
cleanups.push(() => {
stopTimer();
viewport.removeEventListener("mouseenter", onMouseEnter);
viewport.removeEventListener("mouseleave", onMouseLeave);
viewport.removeEventListener("pointerdown", onPointerDown);
viewport.removeEventListener("pointermove", onPointerMove);
viewport.removeEventListener("pointerup", onPointerUp);
viewport.removeEventListener("pointercancel", onPointerUp);
dotButtons.forEach((button) => button.remove());
});
render();
startTimer();
});
return () => {
cleanups.forEach((cleanup) => cleanup());
};
}, [initialContent?.contsCn]);
return (
<div className="card card-border !mt-6">
<div
ref={rootRef}
className="contents-page text-[16px] leading-[1.5]"
dangerouslySetInnerHTML={{
__html: initialContent?.contsCn ?? "",