Compare commits

...

7 Commits
v1.1.1 ... main

23 changed files with 668 additions and 267 deletions

73
docs/TECH_STACK.md Normal file
View File

@ -0,0 +1,73 @@
# Technology Stack & Architecture
이 프로젝트는 **Headless CMS** 아키텍처를 기반으로 하며, 프론트엔드와 백엔드가 분리되어 운영됩니다. 다음은 프로젝트에 사용된 주요 기술과 도구에 대한 명세입니다.
## 1. Frontend (사용자 인터페이스)
사용자에게 보여지는 웹사이트 화면은 **React**를 사용하여 구축되었습니다.
### **Core Framework & Language**
* **React 19**: 사용자 인터페이스를 만들기 위한 JavaScript 라이브러리입니다.
* *이용 방식*: 컴포넌트(Component) 단위로 UI를 쪼개어 개발(예: `Navbar`, `Footer`, `PostCard`)함으로써 재사용성과 유지보수성을 높였습니다.
* **JavaScript (ES6+)**: 최신 자바스크립트 문법을 사용합니다.
* *주요 기능*: `Async/Await` (비동기 처리), `Arrow Functions`, `Destructuring` 등을 적극 활용했습니다.
* **Vite**: 차세대 프론트엔드 빌드 도구입니다.
* *역할*: 개발 서버를 빠르게 구동하고, 배포를 위해 코드를 최적화하여 빌드(Build)하는 역할을 합니다. (기존 Create-React-App보다 훨씬 빠름)
### **Routing & Navigation**
* **React Router DOM**: SPA(Single Page Application) 구현을 위한 라우팅 라이브러리입니다.
* *역할*: 페이지를 새로고침하지 않고도 URL(`url/about`, `url/news` 등)에 따라 다른 화면을 보여줍니다.
### **Styling (디자인)**
* **Vanilla CSS**: 별도의 CSS 프레임워크(Tailwind, Bootstrap 등) 없이 순수 CSS를 사용했습니다.
* *파일*: `src/styles/main.css` 하나로 통합 관리됩니다.
* *특징*: CSS Variables(`var(--primary-color)`)를 사용하여 색상 테마를 일관되게 관리하며, 반응형 디자인(`@media`)이 적용되어 있습니다.
### **Data Communication (통신)**
* **Axios**: 서버와 통신하기 위한 HTTP 클라이언트입니다.
* *역할*: 워드프레스 API(`wp-json`)에 요청을 보내 뉴스 글이나 데이터를 가져옵니다.
### **Security & Utilities**
* **DOMPurify**: 워드프레스에서 가져온 HTML 본문을 안전하게 렌더링하기 위해 XSS 공격(악성 스크립트)을 방지하는 라이브러리입니다.
* **Context Menu Blocking**: 마우스 우클릭 및 드래그를 방지하여 콘텐츠 무단 복사를 막는 로직이 포함되어 있습니다.
---
## 2. Backend (CMS & Data)
데이터 관리와 콘텐츠 작성은 **WordPress**를 사용합니다.
* **WordPress (Headless Mode)**: 전통적인 워드프레스 테마를 사용하지 않고, 오직 **데이터 저장소****관리자 페이지** 역할만 수행합니다.
* **REST API**: 워드프레스에 내장된 API를 통해 프론트엔드(React)로 JSON 데이터를 전달합니다.
* **PHP 8.3**: 워드프레스 구동을 위한 서버 사이드 언어입니다.
* **MariaDB 10**: 게시글, 댓글 등 모든 데이터가 저장되는 데이터베이스입니다.
---
## 3. Infrastructure (인프라)
* **Synology Web Station**: 웹 서버 호스팅 환경입니다.
* **Apache HTTP Server 2.4**: React 빌드 파일(정적 파일)과 워드프레스(PHP)를 서비스하는 웹 서버 소프트웨어입니다.
* **Gitea**: 소스 코드의 버전 관리를 담당하는 자체 호스팅 Git 서버입니다.
---
## 4. Contact Form Security (문의하기 보안 정책)
문의하기 기능(`Contact.jsx`)에는 스팸 봇(Bot) 및 어뷰징 방지를 위해 **4단계 보안 시스템**이 적용되어 있습니다.
### **1. Honeypot Field (허니팟)**
* **원리**: 사용자(사람)의 눈에는 보이지 않는 숨겨진 입력 필드(`honeypot`)를 폼에 배치합니다.
* **방어**: 자동화된 봇은 모든 필드를 채우려는 특성이 있습니다. 이 숨겨진 필드에 값이 입력되면 즉시 **스팸으로 간주하여 전송을 차단**합니다.
### **2. Rate Limiting (전송 빈도 제한)**
* **원리**: 브라우저의 `localStorage`를 활용하여 마지막으로 문의를 전송한 시각을 기록합니다.
* **방어**: 동일한 브라우저에서 **1분(60초) 이내**에 연속으로 문의를 보낼 수 없도록 차단하여, 메일 폭탄(Email Bombing) 공격을 예방합니다.
### **3. Input Validation (입력값 검증)**
* **원리**: 사용자가 입력한 데이터가 유효한 형식인지 클라이언트 단에서 1차 검증합니다.
* **방어**: 특히 연락처 필드는 한국 휴대폰 번호 정규식(`^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$`)을 통과해야만 전송 버튼이 작동하여, 무의미한 데이터 전송을 막습니다.
### **4. Secure Token Submission**
* **원리**: 이메일 주소를 HTML 소스코드에 직접 노출(`mailto:` 등)하지 않습니다.
* **방어**: `FormSubmit` 서비스의 **암호화된 고유 토큰**을 사용하여 데이터를 전송하므로, 크롤러에 의한 이메일 주소 수집을 원천적으로 차단합니다.

View File

@ -7,7 +7,7 @@ React 앱(`sokuree.com`)이 데이터를 가져오기 위해 필요한 백엔드
다음 패키지들이 설치되어 있어야 합니다.
- **Web Station**
- **Apache HTTP Server 2.4** (또는 Nginx)
- **PHP 8.0 이상** (Headless 모드는 최신 PHP 권장)
- **PHP 8.3 이상** (Headless 모드는 최신 PHP 권장)
- **MariaDB 10** (데이터베이스)
- **phpMyAdmin** (DB 관리용, 선택사항이나 강력 추천)

View File

@ -3,9 +3,9 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" href="/src/assets/logo.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SmartBiz Tech - 소상공인 스마트 기술 파트너</title>
<title>SOKUREE</title>
</head>
<body>

View File

@ -24,6 +24,8 @@ function ScrollToTopComp() {
return null;
}
import Platform from './pages/Platform';
function App() {
// Global Content Protection
useEffect(() => {
@ -43,6 +45,7 @@ function App() {
<Route path="/" element={<Layout />}>
<Route index element={<Navigate to="/about" replace />} />
<Route path="about" element={<About />} />
<Route path="platform" element={<Platform />} />
<Route path="services" element={<ServiceDetail />} />
<Route path="services/:serviceId" element={<ServiceDetail />} />
<Route path="cases" element={<Cases />} />

BIN
src/assets/214742.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
src/assets/logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

View File

@ -1,23 +1,26 @@
const About = () => {
return (
<section id="about" className="section-padding bg-light">
<div className="container">
<h2 className="section-title">회사 소개</h2>
<div className="about-content">
<div className="about-text">
<h3>소상공인과 함께 성장하는 스마트 파트너</h3>
<p>
SmartBiz Tech는 급변하는 디지털 시대에 소상공인 여러분이 경쟁력을 갖출 있도록
최고의 스마트 기술을 합리적인 가격에 공급합니다.
</p>
<p>
단순한 기기 판매가 아닌, 매장 환경 분석부터 설치, 유지보수까지
원스톱 서비스를 제공하여 사장님은 경영에만 집중하실 있도록 돕겠습니다.
</p>
</div>
<>
<section className="about-hero">
<div className="about-intro">
<p>
Sokuree Consultant는 현장의 목소리(데이터)에서 필요한 정보를 <br className="hidden md:block" />
시스템(System) 하여 기업의 지속 가능한 성장을 지원합니다.
</p>
</div>
</div>
</section>
</section>
<section className="vision-mission-grid">
<div className="vision-card">
<h3>Vision</h3>
<p>누구나 쉽게 스마트 경영을 실현하는 세상</p>
</div>
<div className="vision-card">
<h3>Mission</h3>
<p>복잡한 기술을 현장에 맞게 단순화하고, 실질적인 성과를 창출하는 </p>
</div>
</section>
</>
);
};

View File

@ -0,0 +1,43 @@
import React from 'react';
const CaseItem = ({ study }) => {
return (
<div className="case-card">
{/* Image Section */}
{study.image ? (
<div className="case-image-wrapper">
<img
src={study.image}
alt={study.title}
className="case-image"
/>
<div className="case-category-badge">
{study.category.split('/')[0]}
</div>
</div>
) : (
<div className="case-bar"></div>
)}
{/* Content Section */}
<div className="case-content">
{!study.image && (
<div className="case-category-text">
{study.category}
</div>
)}
<h3 className="case-card-title">{study.title}</h3>
<div>
<span className="case-result-badge">
성과: {study.result}
</span>
</div>
<p className="case-details">
{study.details}
</p>
</div>
</div>
);
};
export default CaseItem;

View File

@ -0,0 +1,30 @@
import { Link } from 'react-router-dom';
const CasesPreview = () => {
return (
<section className="section-padding">
<div className="container">
<h2 className="section-title">성공 사례</h2>
<div className="solutions-grid">
<div className="solution-card">
<h3>H사 스마트공장 구축</h3>
<p>생산성 30% 향상, 불량률 50% 감소</p>
</div>
<div className="solution-card">
<h3>D사 ISO 인증 획득</h3>
<p>내부 심사원 양성 프로세스 정립</p>
</div>
<div className="solution-card">
<h3>K사 그룹웨어 도입</h3>
<p>재고 관리 자동화 업무 효율화</p>
</div>
</div>
<div style={{ textAlign: 'center', marginTop: '40px' }}>
<Link to="/cases" className="btn btn-primary">사례 보기</Link>
</div>
</div>
</section>
);
};
export default CasesPreview;

View File

@ -6,10 +6,10 @@ const Footer = () => {
<div className="container footer-container">
<div className="footer-info">
<h3>Sokuree Consultant</h3>
<p>代表: 홍길동 | Business License: 000-00-00000</p>
<p>주소: 서울특별시 ... (추후 업데이트)</p>
<p>대표: 최병규 | Business License: 000-00-00000</p>
<p>주소: 전북 순창군 ... (추후 업데이트)</p>
<p>
이메일: <a href={`mailto:${'contact'}@${'sokuree.com'}`} style={{ color: '#ccc', textDecoration: 'none' }} onClick={(e) => { e.currentTarget.href = `mailto:contact@sokuree.com`; }}>{'contact'}@{'sokuree.com'}</a> | 전화: 02-0000-0000
이메일: <a href={`mailto:${'contact'}@${'sokuree.com'}`} style={{ color: '#ccc', textDecoration: 'none' }} onClick={(e) => { e.currentTarget.href = `mailto:contact@sokuree.com`; }}>{'contact'}@{'sokuree.com'}</a> | 전화: 010-2125-0217
</p>
</div>
<div className="footer-links">

View File

@ -0,0 +1,31 @@
import { Link } from 'react-router-dom';
import { usePosts } from '../hooks/usePosts';
import PostCard from './PostCard';
const LatestNews = () => {
const { posts, loading, error } = usePosts(1, 3); // Fetch 3 latest posts
if (loading) return null;
if (error) return null;
if (posts.length === 0) return null;
return (
<section className="section-padding bg-light">
<div className="container">
<div className="flex justify-between items-end mb-8">
<h2 className="section-title mb-0">최신 소식</h2>
<Link to="/news" className="text-primary font-semibold hover:underline">
전체 보기
</Link>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
</div>
</div>
</section>
);
};
export default LatestNews;

View File

@ -19,6 +19,7 @@ const Navbar = () => {
<div className={`nav-links ${isOpen ? 'active' : ''}`}>
<Link to="/about" onClick={toggleMenu}>회사소개</Link>
<Link to="/platform" onClick={toggleMenu}>플랫폼</Link>
<Link to="/services" onClick={toggleMenu}>서비스</Link>
<Link to="/cases" onClick={toggleMenu}>사례</Link>
<Link to="/news" onClick={toggleMenu}>소식</Link>

View File

@ -0,0 +1,19 @@
const Process = () => {
return (
<section className="section-padding bg-light">
<div className="container">
<h2 className="section-title">진행 프로세스</h2>
<div className="process-grid">
{['진단', '설계', '실행', '정착', '고도화'].map((step, index) => (
<div key={index} className="process-step-card">
<div className="process-step-number">{index + 1}</div>
<h3>{step}</h3>
</div>
))}
</div>
</div>
</section>
);
};
export default Process;

View File

@ -0,0 +1,69 @@
import React from 'react';
const ServiceCurriculum = ({ onDownloadGuide }) => {
return (
<div className="service-section">
<h2 className="section-label">
Sokuree Consultant<br />교육과정
</h2>
<p className="section-desc">현장 전문가로 성장할 있는 다양한 교육 과정을 경험해보세요.</p>
<div className="curriculum-grid">
{/* Col 1 */}
<div className="curriculum-card">
<span className="curriculum-number">01</span>
<h3 className="curriculum-title">경영시스템<br />요구사항 해설</h3>
<ul className="curriculum-list">
<li><span className="curriculum-bullet"></span> ISO 9001 (품질)</li>
<li><span className="curriculum-bullet"></span> ISO 14001 (환경)</li>
<li><span className="curriculum-bullet"></span> ISO 45001 (안전보건)</li>
<li><span className="curriculum-bullet"></span> 시스템 문서화 실무</li>
</ul>
</div>
{/* Col 2 */}
<div className="curriculum-card">
<span className="curriculum-number">02</span>
<h3 className="curriculum-title">내부심사원<br />양성 과정</h3>
<ul className="curriculum-list">
<li><span className="curriculum-bullet"></span> 심사 기법 절차</li>
<li><span className="curriculum-bullet"></span> 체크리스트 작성</li>
<li><span className="curriculum-bullet"></span> 부적합 보고서 작성</li>
<li><span className="curriculum-bullet"></span> Role-Play 실습</li>
</ul>
</div>
{/* Col 3 */}
<div className="curriculum-card">
<span className="curriculum-number">03</span>
<h3 className="curriculum-title">IATF 16949<br />Core Tools</h3>
<ul className="curriculum-list">
<li><span className="curriculum-bullet"></span> APQP / PPAP</li>
<li><span className="curriculum-bullet"></span> FMEA (고장모드)</li>
<li><span className="curriculum-bullet"></span> SPC (통계적공정)</li>
<li><span className="curriculum-bullet"></span> MSA (측정분석)</li>
</ul>
</div>
{/* Col 4 */}
<div className="curriculum-card" style={{ justifyContent: 'space-between' }}>
<div>
<span className="curriculum-number">04</span>
<h3 className="curriculum-title">교육문의 <br />신청안내</h3>
<ul className="curriculum-list">
<li> 32 과정 운영 </li>
<li>맞춤형 출장 교육 가능</li>
</ul>
</div>
<button className="curriculum-btn" onClick={onDownloadGuide}>
<span style={{ fontSize: '1.2rem' }}>📥</span> 교육안내서 보기
</button>
</div>
</div>
</div>
);
};
export default ServiceCurriculum;

View File

@ -0,0 +1,35 @@
import React from 'react';
const ServicePurpose = () => {
return (
<div className="service-section">
<h2 className="section-label">교육 목적</h2>
<p className="section-desc">현장 중심의 실무형 인재 양성을 목표로 하고 있어요.</p>
<div className="purpose-grid">
{/* Purpose Card 1 */}
<div className="purpose-card">
<h3 className="purpose-card-title">스마트 제조 혁신 지원</h3>
<p className="purpose-card-text">중소기업의 스마트한<br />제조 환경 구축을 돕습니다.</p>
<div className="purpose-icon">🏭</div>
</div>
{/* Purpose Card 2 */}
<div className="purpose-card">
<h3 className="purpose-card-title">분야별 기술전문가 양성</h3>
<p className="purpose-card-text">전문 기술 역량 강화를 통한<br />핵심 인재 육성</p>
<div className="purpose-icon">👥</div>
</div>
{/* Purpose Card 3 */}
<div className="purpose-card">
<h3 className="purpose-card-title">현장 중심 실무 학습</h3>
<p className="purpose-card-text">이론과 실무를 겸비한<br />맞춤형 커리큘럼</p>
<div className="purpose-icon">📘</div>
</div>
</div>
</div>
);
};
export default ServicePurpose;

28
src/components/WhyUs.jsx Normal file
View File

@ -0,0 +1,28 @@
const WhyUs = () => {
return (
<section className="section-padding">
<div className="container">
<h2 className="section-title">Why Sokuree?</h2>
<div className="solutions-grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))' }}>
<div className="solution-card">
<div className="solution-icon">🏆</div>
<h3>전문성</h3>
<p>품질/ISO 기반의 검증된 컨설팅</p>
</div>
<div className="solution-card">
<div className="solution-icon">🏭</div>
<h3>현장 중심</h3>
<p>제조 현장에 최적화된 맞춤형 솔루션</p>
</div>
<div className="solution-card">
<div className="solution-icon">🚀</div>
<h3>실행력</h3>
<p>데이터 시스템의 실질적 구현과 안착</p>
</div>
</div>
</div>
</section>
);
};
export default WhyUs;

View File

@ -1,29 +1,11 @@
import consultantProfile from '../assets/consultant_profile.webp';
import AboutIntro from '../components/About';
const About = () => {
return (
<div className="container" style={{ marginTop: '70px', minHeight: '80vh' }}>
{/* Intro Section with Background */}
<section className="about-hero">
<h1 className="section-title" style={{ marginBottom: '30px' }}>회사 소개</h1>
<div className="about-intro">
<p>
Sokuree Consultant는 현장의 목소리(데이터)에서 필요한 정보를 <br className="hidden md:block" />
시스템(System) 하여 기업의 지속 가능한 성장을 지원합니다.
</p>
</div>
</section>
<section className="vision-mission-grid">
<div className="vision-card">
<h3>Vision</h3>
<p>누구나 쉽게 스마트 경영을 실현하는 세상</p>
</div>
<div className="vision-card">
<h3>Mission</h3>
<p>복잡한 기술을 현장에 맞게 단순화하고, 실질적인 성과를 창출하는 </p>
</div>
</section>
<AboutIntro />
<section className="expert-profile">
<h2 className="section-title">대표 컨설턴트</h2>

View File

@ -1,4 +1,5 @@
import React from 'react';
import CaseItem from '../components/CaseItem';
const Cases = () => {
@ -49,41 +50,7 @@ const Cases = () => {
<div className="cases-grid">
{caseStudies.map(study => (
<div key={study.id} className="case-card">
{/* Image Section */}
{study.image ? (
<div className="case-image-wrapper">
<img
src={study.image}
alt={study.title}
className="case-image"
/>
<div className="case-category-badge">
{study.category.split('/')[0]}
</div>
</div>
) : (
<div className="case-bar"></div>
)}
{/* Content Section */}
<div className="case-content">
{!study.image && (
<div className="case-category-text">
{study.category}
</div>
)}
<h3 className="case-card-title">{study.title}</h3>
<div>
<span className="case-result-badge">
성과: {study.result}
</span>
</div>
<p className="case-details">
{study.details}
</p>
</div>
</div>
<CaseItem key={study.id} study={study} />
))}
</div>
</div>

View File

@ -1,112 +1,23 @@
import Hero from '../components/Hero';
import Solutions from '../components/Solutions';
import Contact from '../components/Contact';
import { Link } from 'react-router-dom';
import { usePosts } from '../hooks/usePosts';
import PostCard from '../components/PostCard';
import WhyUs from '../components/WhyUs';
import Process from '../components/Process';
import CasesPreview from '../components/CasesPreview';
import LatestNews from '../components/LatestNews';
const Home = () => {
return (
<>
<Hero />
{/* Why Us Section */}
<section className="section-padding">
<div className="container">
<h2 className="section-title">Why Sokuree?</h2>
<div className="solutions-grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))' }}>
<div className="solution-card">
<div className="solution-icon">🏆</div>
<h3>전문성</h3>
<p>품질/ISO 기반의 검증된 컨설팅</p>
</div>
<div className="solution-card">
<div className="solution-icon">🏭</div>
<h3>현장 중심</h3>
<p>제조 현장에 최적화된 맞춤형 솔루션</p>
</div>
<div className="solution-card">
<div className="solution-icon">🚀</div>
<h3>실행력</h3>
<p>데이터 시스템의 실질적 구현과 안착</p>
</div>
</div>
</div>
</section>
<WhyUs />
<Solutions />
{/* Process Section */}
<section className="section-padding bg-light">
<div className="container">
<h2 className="section-title">진행 프로세스</h2>
<div className="process-grid">
{['진단', '설계', '실행', '정착', '고도화'].map((step, index) => (
<div key={index} className="process-step-card">
<div className="process-step-number">{index + 1}</div>
<h3>{step}</h3>
</div>
))}
</div>
</div>
</section>
{/* Cases Preview */}
<section className="section-padding">
<div className="container">
<h2 className="section-title">성공 사례</h2>
<div className="solutions-grid">
<div className="solution-card">
<h3>H사 스마트공장 구축</h3>
<p>생산성 30% 향상, 불량률 50% 감소</p>
</div>
<div className="solution-card">
<h3>D사 ISO 인증 획득</h3>
<p>내부 심사원 양성 프로세스 정립</p>
</div>
<div className="solution-card">
<h3>K사 그룹웨어 도입</h3>
<p>재고 관리 자동화 업무 효율화</p>
</div>
</div>
<div style={{ textAlign: 'center', marginTop: '40px' }}>
<Link to="/cases" className="btn btn-primary">사례 보기</Link>
</div>
</div>
</section>
{/* Latest News Section */}
<LatestNewsSection />
<Process />
<CasesPreview />
<LatestNews />
<Contact />
</>
);
};
const LatestNewsSection = () => {
const { posts, loading, error } = usePosts(1, 3); // Fetch 3 latest posts
if (loading) return null;
if (error) return null;
if (posts.length === 0) return null;
return (
<section className="section-padding bg-light">
<div className="container">
<div className="flex justify-between items-end mb-8">
<h2 className="section-title mb-0">최신 소식</h2>
<Link to="/news" className="text-primary font-semibold hover:underline">
전체 보기
</Link>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
</div>
</div>
</section>
);
}
export default Home;

View File

@ -3,6 +3,13 @@ import { useParams, Link } from 'react-router-dom';
import { wpApi } from '../api/wordpress';
// Helper to handle raw markdown formatting if present
const formatContent = (htmlContent) => {
if (!htmlContent) return '';
// Replace **text** with <strong>text</strong> (Basic bold support)
return htmlContent.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
};
const NewsDetail = () => {
const { slug } = useParams();
const [post, setPost] = useState(null);
@ -81,7 +88,7 @@ const NewsDetail = () => {
{/* Content */}
<div
className="article-content"
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
dangerouslySetInnerHTML={{ __html: formatContent(post.content.rendered) }}
/>
{/* Footer / Navigation */}

141
src/pages/Platform.jsx Normal file
View File

@ -0,0 +1,141 @@
import React from 'react';
import '../styles/main.css';
import smartAssetImage from '../assets/214742.png';
const Platform = () => {
return (
<div className="service-container">
{/* Header Section */}
<div className="service-header" style={{ paddingBottom: '3rem' }}>
<h1 className="service-title">플랫폼</h1>
<p className="service-subtitle">
소쿠리 컨설턴트의 혁신적인 솔루션을 만나보세요.
</p>
</div>
<div className="service-content-wrapper">
{/* On-premise Solution Section (Moved to Top) */}
<div className="service-section" style={{ marginBottom: '8rem' }}>
<h2 className="section-label text-center" style={{ textAlign: 'center' }}>구축형 서버 기반 중소기업 스마트 솔루션</h2>
<div className="title-underline" style={{ width: '60px', height: '4px', backgroundColor: '#4A628A', margin: '0 auto 3rem' }}></div>
<div className="content-wrapper" style={{ width: '100%', textAlign: 'center' }}>
{/* Reduced fontSize from 1.125rem to 1rem for better line breaks */}
<p style={{ fontSize: '1rem', lineHeight: '1.8', color: '#475569', marginBottom: '4rem', maxWidth: '900px', margin: '0 auto 4rem', wordBreak: 'keep-all' }}>
중소기업 환경에 최적화된 구축형 서버 기반 스마트 솔루션은 합리적인 비용으로 안정성, 보안성, 확장성을 동시에 제공합니다.<br className="hidden md:block" />
기업의 규모와 업무 특성에 맞춰 자유롭게 설계·운영할 있는 자체 인프라를 통해 지속 가능한 디지털 경쟁력을 확보할 있습니다.
</p>
<div className="features-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: '2rem', textAlign: 'left' }}>
{/* Feature 1 */}
<div className="feature-item" style={{ padding: '2.5rem', backgroundColor: '#fff', borderRadius: '1rem', border: '1px solid #cbd5e1', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)' }}>
{/* Reduced fontSize from 1.4rem to 1.25rem */}
<h4 style={{ fontWeight: 'bold', marginBottom: '1.2rem', color: '#4A628A', fontSize: '1.25rem' }}>자체 서버 구축</h4>
{/* Reduced fontSize from 1rem to 0.95rem */}
<ul style={{ paddingLeft: '1.2rem', color: '#475569', lineHeight: '1.8', fontSize: '0.85rem' }}>
<li>외부 클라우드 의존 없이 사내 인프라 직접 운영</li>
<li>기업 데이터에 대한 완전한 통제 보안 강화</li>
<li>고객 정보·기술 자료·업무 데이터의 안전한 관리</li>
</ul>
</div>
{/* Feature 2 */}
<div className="feature-item" style={{ padding: '2.5rem', backgroundColor: '#fff', borderRadius: '1rem', border: '1px solid #cbd5e1', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)' }}>
<h4 style={{ fontWeight: 'bold', marginBottom: '1.2rem', color: '#4A628A', fontSize: '1.25rem' }}>맞춤형 그룹웨어 운영</h4>
<ul style={{ paddingLeft: '1.2rem', color: '#475569', lineHeight: '1.8', fontSize: '0.85rem' }}>
<li>전자결재, 문서관리, 일정·프로젝트 관리 통합</li>
<li>조직 구조와 업무 방식에 맞춘 자유로운 구성</li>
<li>불필요한 기능 제거, 필요한 기능만 선택 도입</li>
</ul>
</div>
{/* Feature 3 */}
<div className="feature-item" style={{ padding: '2.5rem', backgroundColor: '#fff', borderRadius: '1rem', border: '1px solid #cbd5e1', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)' }}>
<h4 style={{ fontWeight: 'bold', marginBottom: '1.2rem', color: '#4A628A', fontSize: '1.25rem' }}>기업 맞춤 업무 시스템 확장</h4>
<ul style={{ paddingLeft: '1.2rem', color: '#475569', lineHeight: '1.8', fontSize: '0.85rem' }}>
<li>품질/생산관리, 재고관리 단계적 구축</li>
<li> 서비스·데이터베이스·업무 시스템 통합 운영</li>
<li>외주·상용 솔루션 종속 없는 자립형 구조</li>
</ul>
</div>
{/* Feature 4 */}
<div className="feature-item" style={{ padding: '2.5rem', backgroundColor: '#fff', borderRadius: '1rem', border: '1px solid #cbd5e1', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)' }}>
<h4 style={{ fontWeight: 'bold', marginBottom: '1.2rem', color: '#4A628A', fontSize: '1.25rem' }}>안정적인 운영과 관리 편의성</h4>
<ul style={{ paddingLeft: '1.2rem', color: '#475569', lineHeight: '1.8', fontSize: '0.85rem' }}>
<li> 기반 관리 환경으로 손쉬운 서버 운영</li>
<li>자동 백업, 장애 알림, 신속한 복구 체계</li>
</ul>
</div>
{/* Feature 5 */}
<div className="feature-item" style={{ padding: '2.5rem', backgroundColor: '#fff', borderRadius: '1rem', border: '1px solid #cbd5e1', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)' }}>
<h4 style={{ fontWeight: 'bold', marginBottom: '1.2rem', color: '#4A628A', fontSize: '1.25rem' }}>무한한 확장 가능성</h4>
<ul style={{ paddingLeft: '1.2rem', color: '#475569', lineHeight: '1.8', fontSize: '0.85rem' }}>
<li>소규모 도입 단계적 확장 가능</li>
<li>사용자·서비스 증가에 유연하게 대응</li>
</ul>
</div>
</div>
<div className="conclusion-box" style={{ marginTop: '4rem', padding: '2.5rem', backgroundColor: '#f1f5f9', borderRadius: '1rem', borderLeft: '5px solid #4A628A', textAlign: 'left' }}>
<p style={{ fontSize: '1.05rem', lineHeight: '1.8', color: '#334155', margin: 0, fontWeight: '500', wordBreak: 'keep-all' }}>
구축형 서버 기반 스마트 솔루션은 단순한 시스템 도입이 아닌, 기업 운영 방식에 최적화된 디지털 기반을 직접 설계하는 전략적 선택입니다.<br />
중소기업의 현재와 미래를 모두 고려한 현실적인 스마트 인프라 해답을 제시합니다.
</p>
</div>
</div>
</div>
{/* Smart Asset Section (Moved to Bottom) */}
<div className="service-section">
<h2 className="section-label text-center" style={{ textAlign: 'center' }}>Smart Asset 모듈</h2>
<div className="title-underline" style={{ width: '60px', height: '4px', backgroundColor: '#4A628A', margin: '0 auto 3rem' }}></div>
<div className="smart-asset-container" style={{ display: 'flex', flexDirection: 'column', gap: '3rem' }}>
{/* Image Wrapper - Inside Container, Matching Header Width */}
<div className="image-wrapper" style={{ width: '100%', display: 'flex', justifyContent: 'center' }}>
<img
src={smartAssetImage}
alt="Smart Asset Module Interface"
style={{
maxWidth: '100%',
height: 'auto',
display: 'block'
/* No shadow/border as requested */
}}
/>
</div>
<div className="content-wrapper" style={{ width: '100%', textAlign: 'center' }}>
{/* Features Grid - Updated font sizes for consistency if needed, but original request was specifically about the text overflowing width */}
{/* User mainly complained about the new section text exceeding width. But for consistency, let's keep these as they were or slightly adjust if they look too big.
Given the request context, I will slightly reduce these too for visual balance. */}
<div className="features-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '1.5rem', textAlign: 'left' }}>
<div className="feature-item" style={{ padding: '2rem', backgroundColor: '#fff', borderRadius: '1rem', border: '1px solid #cbd5e1', boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)' }}>
<h4 style={{ fontWeight: 'bold', marginBottom: '1rem', color: '#4A628A', fontSize: '1.15rem' }}>자산 현황 파악</h4>
<p style={{ fontSize: '0.85rem', color: '#475569', lineHeight: '1.6' }}>실시간으로 자산을 추적하고 관리합니다.</p>
</div>
<div className="feature-item" style={{ padding: '2rem', backgroundColor: '#fff', borderRadius: '1rem', border: '1px solid #cbd5e1', boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)' }}>
<h4 style={{ fontWeight: 'bold', marginBottom: '1rem', color: '#4A628A', fontSize: '1.15rem' }}>이력 관리</h4>
<p style={{ fontSize: '0.85rem', color: '#475569', lineHeight: '1.6' }}>구입부터 폐기까지 자산의 이력을 기록합니다.</p>
</div>
<div className="feature-item" style={{ padding: '2rem', backgroundColor: '#fff', borderRadius: '1rem', border: '1px solid #cbd5e1', boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)' }}>
<h4 style={{ fontWeight: 'bold', marginBottom: '1rem', color: '#4A628A', fontSize: '1.15rem' }}>효율성 증대</h4>
<p style={{ fontSize: '0.85rem', color: '#475569', lineHeight: '1.6' }}>체계적인 데이터 관리로 업무 효율성을 극대화합니다.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default Platform;

View File

@ -1,5 +1,7 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import ServicePurpose from '../components/ServicePurpose';
import ServiceCurriculum from '../components/ServiceCurriculum';
const ServiceDetail = () => {
const navigate = useNavigate();
@ -26,97 +28,10 @@ const ServiceDetail = () => {
<div className="service-content-wrapper">
{/* Section 1: Education Purpose (3 Cards) */}
<div className="service-section">
<h2 className="section-label">교육 목적</h2>
<p className="section-desc">현장 중심의 실무형 인재 양성을 목표로 하고 있어요.</p>
<div className="purpose-grid">
{/* Purpose Card 1 */}
<div className="purpose-card">
<h3 className="purpose-card-title">스마트 제조 혁신 지원</h3>
<p className="purpose-card-text">중소기업의 스마트한<br />제조 환경 구축을 돕습니다.</p>
<div className="purpose-icon">🏭</div>
</div>
{/* Purpose Card 2 */}
<div className="purpose-card">
<h3 className="purpose-card-title">분야별 기술전문가 양성</h3>
<p className="purpose-card-text">전문 기술 역량 강화를 통한<br />핵심 인재 육성</p>
<div className="purpose-icon">👥</div>
</div>
{/* Purpose Card 3 */}
<div className="purpose-card">
<h3 className="purpose-card-title">현장 중심 실무 학습</h3>
<p className="purpose-card-text">이론과 실무를 겸비한<br />맞춤형 커리큘럼</p>
<div className="purpose-icon">📘</div>
</div>
</div>
</div>
<ServicePurpose />
{/* Section 2: Curriculum (4 Numbered Columns) */}
<div className="service-section">
<h2 className="section-label">
Sokuree Consultant<br />교육과정
</h2>
<p className="section-desc">현장 전문가로 성장할 있는 다양한 교육 과정을 경험해보세요.</p>
<div className="curriculum-grid">
{/* Col 1 */}
<div className="curriculum-card">
<span className="curriculum-number">01</span>
<h3 className="curriculum-title">경영시스템<br />요구사항 해설</h3>
<ul className="curriculum-list">
<li><span className="curriculum-bullet"></span> ISO 9001 (품질)</li>
<li><span className="curriculum-bullet"></span> ISO 14001 (환경)</li>
<li><span className="curriculum-bullet"></span> ISO 45001 (안전보건)</li>
<li><span className="curriculum-bullet"></span> 시스템 문서화 실무</li>
</ul>
</div>
{/* Col 2 */}
<div className="curriculum-card">
<span className="curriculum-number">02</span>
<h3 className="curriculum-title">내부심사원<br />양성 과정</h3>
<ul className="curriculum-list">
<li><span className="curriculum-bullet"></span> 심사 기법 절차</li>
<li><span className="curriculum-bullet"></span> 체크리스트 작성</li>
<li><span className="curriculum-bullet"></span> 부적합 보고서 작성</li>
<li><span className="curriculum-bullet"></span> Role-Play 실습</li>
</ul>
</div>
{/* Col 3 */}
<div className="curriculum-card">
<span className="curriculum-number">03</span>
<h3 className="curriculum-title">IATF 16949<br />Core Tools</h3>
<ul className="curriculum-list">
<li><span className="curriculum-bullet"></span> APQP / PPAP</li>
<li><span className="curriculum-bullet"></span> FMEA (고장모드)</li>
<li><span className="curriculum-bullet"></span> SPC (통계적공정)</li>
<li><span className="curriculum-bullet"></span> MSA (측정분석)</li>
</ul>
</div>
{/* Col 4 */}
<div className="curriculum-card" style={{ justifyContent: 'space-between' }}>
<div>
<span className="curriculum-number">04</span>
<h3 className="curriculum-title">교육문의 <br />신청안내</h3>
<ul className="curriculum-list">
<li> 32 과정 운영 </li>
<li>맞춤형 출장 교육 가능</li>
</ul>
</div>
<button className="curriculum-btn" onClick={handleDownloadGuide}>
<span style={{ fontSize: '1.2rem' }}>📥</span> 교육안내서 보기
</button>
</div>
</div>
</div>
<ServiceCurriculum onDownloadGuide={handleDownloadGuide} />
{/* Bottom CTA Banner */}
<div className="cta-banner">

View File

@ -67,7 +67,6 @@ img {
-khtml-user-drag: none;
-moz-user-drag: none;
-o-user-drag: none;
user-drag: none;
pointer-events: none;
/* Prevents right-click context menu on images */
}
@ -1327,7 +1326,7 @@ img {
.news-article {
background-color: var(--white);
max-width: 800px;
max-width: 1200px;
width: 100%;
margin: 0 20px;
padding: 3rem;
@ -1374,6 +1373,16 @@ img {
border-radius: 0.75rem;
overflow: hidden;
box-shadow: var(--shadow);
display: flex;
justify-content: center;
background-color: var(--light-gray);
/* Optional: background for transparent images */
}
.article-image {
max-width: 100%;
height: auto;
object-fit: contain;
}
/* ... NewsDetail content styles ... */
@ -1473,4 +1482,138 @@ img {
border-color: var(--primary-color);
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
/* =========================================
6. ARTICLE CONTENT STYLES (WordPress)
========================================= */
.article-content {
line-height: 1.8;
color: var(--text-color);
font-size: 1.05rem;
}
.article-content p {
margin-bottom: 1.5rem;
}
.article-content h1,
.article-content h2,
.article-content h3,
.article-content h4,
.article-content h5,
.article-content h6 {
margin-top: 2.5rem;
margin-bottom: 1rem;
font-weight: 700;
line-height: 1.3;
color: var(--text-color);
}
.article-content h1 {
font-size: 2rem;
}
.article-content h2 {
font-size: 1.75rem;
border-bottom: 2px solid var(--border-color);
padding-bottom: 0.5rem;
}
.article-content h3 {
font-size: 1.5rem;
}
.article-content h4 {
font-size: 1.25rem;
}
.article-content h5 {
font-size: 1.1rem;
}
.article-content h6 {
font-size: 1rem;
}
.article-content ul,
.article-content ol {
margin-bottom: 1.5rem;
padding-left: 1.5rem;
}
.article-content ul {
list-style-type: disc;
}
.article-content ol {
list-style-type: decimal;
}
.article-content li {
margin-bottom: 0.5rem;
}
.article-content strong,
.article-content b {
font-weight: 700;
color: #0f172a;
}
.article-content a {
color: var(--primary-color);
text-decoration: underline;
}
.article-content img {
max-width: 100%;
height: auto;
border-radius: 8px;
margin: 2rem 0;
box-shadow: var(--shadow);
}
.article-content blockquote {
border-left: 4px solid var(--primary-color);
padding: 1rem;
margin: 1.5rem 0;
background-color: var(--light-gray);
border-radius: 0 8px 8px 0;
font-style: italic;
}
/* Helper Classes for Alignments */
.article-content .aligncenter {
display: block;
margin: 0 auto 2rem;
text-align: center;
}
.article-content .alignright {
float: right;
margin-left: 1.5rem;
margin-bottom: 1.5rem;
}
.article-content .alignleft {
float: left;
margin-right: 1.5rem;
margin-bottom: 1.5rem;
}
/* Gutenberg Text Alignment */
.article-content .has-text-align-center {
text-align: center;
}
.article-content .has-text-align-right {
text-align: right;
}
.article-content .has-text-align-left {
text-align: left;
}
.article-content .has-text-align-justify {
text-align: justify;
}