Compare commits

...

9 Commits
v1.1.0 ... main

29 changed files with 808 additions and 502 deletions

View File

@ -1,16 +1,78 @@
# React + Vite
# Sokuree Consultant Web Project (v1.1.0)
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
**소쿠리 컨설턴트(Sokuree Consultant)**의 공식 웹사이트 프로젝트입니다.
React 기반의 모던 프론트엔드와 WordPress Headless CMS 백엔드를 결합하여 제작되었습니다.
Currently, two official plugins are available:
---
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## 🛠 Tech Stack (기술 스택)
## React Compiler
* **Frontend**: React, Vite
* **Backend (CMS)**: WordPress (Headless API)
* **Deployment**: Synology Web Station (Static Hosting)
* **Key Libraries**: `react-router-dom`, `dompurify`, `formsubmit-co`
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
---
## Expanding the ESLint configuration
## 🚀 Getting Started (시작하기)
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
이 프로젝트를 처음부터 설치하고 실행하는 방법입니다.
### 1. 사전 요구사항 (Prerequisites)
* [Node.js](https://nodejs.org/) (v16 이상 권장)
* [Git](https://git-scm.com/)
### 2. 설치 (Installation)
터미널(CMD/PowerShell)을 열고 다음 명령어를 순서대로 실행하세요.
```bash
# 1. 프로젝트 복제 (Clone)
git clone https://github.com/your-repo/antigravity.git
cd antigravity
# 2. 의존성 패키지 설치
npm install
```
### 3. 설정 (Configuration)
백엔드(워드프레스) 연결을 위해 설정 파일 확인이 필요할 수 있습니다.
* `src/api/wordpress.js` 파일에서 API 주소가 올바른지 확인하세요.
* 기본값: `https://api.sokuree.com/wp-json` (예시)
### 4. 개발 서버 실행 (Run Dev Server)
로컬 환경에서 웹사이트를 미리 확인합니다.
```bash
npm run dev
```
이제 브라우저에서 `http://localhost:5173` 으로 접속하면 사이트가 뜹니다.
---
## 📚 Documentation (상세 문서)
프로젝트에 대한 자세한 내용은 `docs/` 폴더 내의 가이드 문서를 참고하세요.
| 문서 | 설명 |
|---|---|
| 📄 **[Code Structure](docs/CODE_STRUCTURE.md)** | **코드 구조 및 컴포넌트 설명** (개발자 필독) |
| 🛠 **[WordPress Setup](docs/WORDPRESS_SETUP.md)** | 백엔드(Wordpress) 설치 및 Headless 설정 가이드 |
| ☁️ **[Deployment Guide](docs/DEPLOYMENT_GUIDE_SYNOLOGY.md)** | 시놀로지 NAS에 최종 배포하는 방법 |
---
## ✨ Features (주요 기능)
* **반응형 디자인**: PC/모바일 모두 최적화된 UI.
* **소식(News) 연동**: 워드프레스 글을 실시간으로 가져와 보여줌.
* **문의하기(Contact)**: 스팸 방지(Rate Limit) 및 알림 기능이 탑재된 이메일 폼.
* **보안(Security)**: 우클릭 방지, 이미지 드래그 방지, XSS 방어 적용.
* **개인정보보호**: 별도의 DB 없이 이메일로만 통신하며, 즉시 파기 정책 준수.
---
## 📦 Version History
* **v1.1.1** (Current): 문서 현행화 및 최종 정리.
* **v1.1.0**: 보안 및 기능 최종 수정.
* **v1.0.0**: 초기 런칭 버전.

69
docs/CODE_STRUCTURE.md Normal file
View File

@ -0,0 +1,69 @@
# Sokuree Web - Code Structure & Components Guide
이 문서는 프로젝트의 소스 코드 구조와 주요 컴포넌트들의 역할을 설명합니다.
## 📁 Directory Structure (폴더 구조)
```
d:/antigravity/
├── dist/ # 빌드 결과물 (배포용 파일들)
├── docs/ # 프로젝트 문서 (설치, 배포, 설정 가이드)
├── public/ # 정적 파일 (favicon 등)
├── src/ # 소스 코드 메인 디렉토리
│ ├── api/ # 외부 API 연동 로직
│ ├── assets/ # 이미지, 폰트 등 리소스
│ ├── components/ # 재사용 가능한 UI 컴포넌트
│ ├── config/ # 설정 파일 (상수 등)
│ ├── pages/ # 라우트(Route)별 페이지 컴포넌트
│ ├── styles/ # 전역 CSS 스타일
│ └── utils/ # 유틸리티 함수
├── index.html # 앱 진입점 HTML
└── vite.config.js # Vite 빌드 설정
```
---
## 🧩 Key Components (주요 컴포넌트)
### 1. `src/components/`
공통적으로 사용되는 UI 요소들입니다.
* **`Navbar.jsx`**: 상단 네비게이션 바. 로고와 메뉴 링크를 포함합니다.
* **`Footer.jsx`**: 하단 푸터. 사업자 정보, 연락처, 개인정보방침 링크(`mailto:` 기능 포함)가 있습니다.
* **`Contact.jsx`**: 문의하기 폼.
* **Rate Limiting**: `localStorage`를 이용해 1분 내 재전송을 막습니다.
* **FormSubmit**: 이메일 발송 서비스와 연동됩니다.
* **`PostCard.jsx`**: 뉴스 목록에서 각 게시글을 보여주는 카드형 UI.
* **`Layout.jsx`**: `Navbar``Outlet`(페이지 내용), `Footer`를 감싸는 레이아웃 래퍼입니다.
### 2. `src/pages/`
각 URL 경로에 해당하는 페이지들입니다.
* **`About.jsx`**: 회사 소개 및 대표 프로필 페이지. (`/about`)
* **`Services.jsx` / `ServiceDetail.jsx`**: 제공 서비스 목록 및 상세 설명.
* **`News.jsx`**: 워드프레스 API(`wpApi`)를 호출하여 소식 목록을 보여줍니다.
* **`NewsDetail.jsx`**: 개별 소식의 상세 내용을 보여줍니다.
* `slug`를 이용해 워드프레스에서 글을 가져옵니다.
* 하단에 "목록으로 돌아가기" 버튼이 있습니다.
* **`Privacy.jsx`**: 개인정보처리방침 페이지. 법적 요구사항에 맞춘 텍스트가 포함되어 있습니다.
### 3. `src/api/`
* **`wordpress.js`**: 워드프레스 REST API와 통신하는 함수들(`getPosts`, `getPostBySlug`)이 모여 있습니다.
* Backend URL: `https://api.sokuree.com/wp-json` (설정 필요)
### 4. `src/styles/`
* **`main.css`**: 전체 사이트의 스타일이 통합된 CSS 파일입니다.
* CSS 변수(`:root`)로 색상 테마 관리.
* 반응형 미디어 쿼리 포함.
* **보안 설정**: 드래그 방지, 텍스트 선택 방지(Contact/Footer 예외 처리) 등의 스타일이 정의되어 있습니다.
---
## 🔒 Security Features (보안 기능)
이 프로젝트에는 기본적인 프론트엔드 보안 조치가 적용되어 있습니다.
1. **우클릭 차단**: `App.jsx`에서 `contextmenu` 이벤트를 차단하여 "다른 이름으로 저장" 등을 방지합니다.
2. **드래그/선택 방지**: CSS(`main.css`)를 통해 이미지 드래그와 텍스트 선택을 막습니다.
* *예외*: 사용자 편의를 위해 입력폼(`input`, `textarea`)과 푸터 연락처(`footer-info`)는 선택 가능합니다.
3. **XSS 방지**: `dompurify` 라이브러리를 사용하여 워드프레스 본문(HTML)을 렌더링할 때 악성 스크립트를 제거합니다.

View File

@ -1,59 +0,0 @@
# Synology NAS React 앱 배포 가이드
이제 개발하신 웹사이트를 실제 인터넷(`sokuree.com`)에 공개하는 과정입니다.
로컬에 있는 코드 전체를 올리는 것이 아니라, **최적화된 빌드 파일(`dist` 폴더)**만 올리는 것입니다.
## 1. React 앱 빌드 (내 컴퓨터에서)
개발 모드(`dev`)는 테스트용입니다. 배포를 위해서는 '빌드'를 해야 합니다.
1. VS Code 터미널에서 기존 서버가 켜져 있다면 `Ctrl + C`로 끕니다.
2. 다음 명령어를 입력합니다:
```bash
npm run build
```
3. 잠시 후 `dist` 라는 폴더가 새로 생깁니다.
- 이 **`dist` 폴더 안에 있는 파일들**([index.html](file:///d:/antigravity/index.html), `assets` 폴더 등)이 실제 서버에 올라갈 주인공입니다.
## 2. Synology에 폴더 생성 및 업로드
1. Synology **File Station**을 엽니다.
2. `/web` 폴더 아래에 새 폴더 `sokuree_front`를 만듭니다. (워드프레스 폴더와 섞이지 않게 따로 만듭니다)
3. 방금 만든 내 컴퓨터의 `dist` 폴더 **안에 있는 모든 내용물**을 `sokuree_front` 폴더 안으로 업로드합니다.
- 결과 경로 예시: `/web/sokuree_front/index.html` 이 있어야 함.
## 3. Web Station 설정 (프론트엔드용)
이제 `sokuree.com` 주소를 이 폴더에 연결합니다.
1. **Web Station** -> **웹 서비스 포털** -> **생성**.
2. **서비스**: 정적 웹 사이트 (또는 가상 호스트).
3. **설정**:
- 호스트 이름: `sokuree.com` (메인 도메인)
- 문서 루트: `/web/sokuree_front`
- 백엔드 서버: Nginx (추천) 또는 Apache
4. **확인**.
## 4. 새로고침 시 404 에러 방지 (중요!)
React 대시보드는 "단일 페이지 앱(SPA)"이라서, 새로고침을 하면 시놀로지가 "어? 뉴스 페이지 파일은 없는데?" 하고 404 에러를 냅니다. 이를 방지하는 설정 파일이 필요합니다.
### 방법 A: Apache 사용 시 (추천)
1. 메모장을 열고 다음 내용을 붙여넣습니다:
```apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
```
2. 파일 이름을 `.htaccess`로 저장합니다. (파일 형식을 '모든 파일'로 선택)
3. 이 `.htaccess` 파일을 시놀로지의 `/web/sokuree_front` 폴더에 업로드합니다.
### 방법 B: Nginx 사용 시
1. Web Station 설정에서 해당 포털 편집 -> **스크립트 언어 설정** 탭 (또는 고급 설정).
2. `try_files $uri $uri/ /index.html;` 규칙을 추가할 수 있는 곳이 있는지 확인합니다. (시놀로지 버전마다 UI가 다름)
3. Nginx가 복잡하다면 마음 편하게 **백엔드 서버를 Apache로 선택**하고 위의 A방법(.htaccess)을 쓰는 것이 가장 쉽습니다.
## 5. 완료
이제 스마트폰이나 다른 PC에서 `http://sokuree.com` (도메인 연결이 되어 있다면) 으로 접속해보세요.
React로 만든 멋진 사이트가 뜰 것입니다!

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

@ -1,109 +0,0 @@
# Synology NAS Headless WordPress 설치 및 설정 가이드
본 가이드는 Synology NAS (DS920+) 환경에서 Headless WordPress를 구축하기 위한 절차입니다.
React 앱(`sokuree.com`)이 데이터를 가져오기 위해 필요한 백엔드 설정을 다룹니다.
## 1. 사전 준비 (Synology 패키지 센터)
다음 패키지들이 설치되어 있어야 합니다.
- **Web Station**
- **Apache HTTP Server 2.4** (또는 Nginx)
- **PHP 8.0 이상** (Headless 모드는 최신 PHP 권장)
- **MariaDB 10** (데이터베이스)
- **phpMyAdmin** (DB 관리용, 선택사항이나 강력 추천)
## 2. 데이터베이스 생성 (MariaDB 10)
1. Synology에서 `MariaDB 10`을 실행하고 root 비밀번호를 설정합니다.
2. `phpMyAdmin`을 실행하여 root로 로그인합니다.
3. 상단 메뉴의 **[데이터베이스]** 탭을 클릭합니다.
4. **새 데이터베이스 만들기**:
- 데이터베이스 이름: `sokuree`
- 데이터와(collation): `utf8mb4_unicode_ci` (한글 및 이모지 지원)
- [만들기] 클릭.
5. (보안 권장) `wp_user` 같은 전용 사용자를 만들고 `sokuree` DB에 대한 모든 권한을 부여합니다.
## 3. WordPress 파일 설치
1. [WordPress.org](https://ko.wordpress.org/download/)에서 최신 zip 파일을 다운로드합니다.
2. Synology File Station을 통해 `/web` 폴더 아래에 새 폴더 `sokuree_wp`를 만듭니다. (React 앱과 구분하기 위해 이름 뒤에 `_wp` 추천)
3. zip 파일을 `sokuree_wp` 폴더에 업로드하고 압축을 풉니다.
4. 최종 경로가 `/web/sokuree_wp/index.php` 등이 보이도록 정리합니다.
## 4. Web Station 설정
1. **Web Station** 실행 -> **웹 서비스 포털** -> **생성** -> **웹 서비스 포털**.
2. **서비스**: `가상 호스트` 선택.
3. **호스트 이름 기반**:
- 호스트 이름: `api.sokuree.com` (React 앱은 `sokuree.com`을 쓸 것이므로, WP는 서브도메인이나 별도 포트 추천. 여기서는 API 전용임을 명확히 하기 위해 `api` 예시)
- 포트: 80 / 443
4. **문서 루트**: 찾아보기 -> `/web/sokuree_wp` 선택.
5. **백엔드 서버**: Apache HTTP Server 2.4 / PHP 8.x 프로필 선택.
6. **확인**을 눌러 저장합니다.
> **주의**: 도메인 `api.sokuree.com`의 DNS A 레코드가 Synology NAS의 공인 IP를 가리키고 있어야 합니다. 공유기 포트포워딩(80, 443)도 필수입니다.
## 5. WordPress 설치 진행
1. 브라우저에서 `http://api.sokuree.com` (설정한 주소) 접속.
2. 언어 선택 (한국어).
3. DB 연결 정보 입력:
- 데이터베이스 이름: `sokuree`
- 사용자명: `root` 또는 생성한 유저
- 비밀번호: MariaDB 비밀번호
- 데이터베이스 호스트: `localhost:/run/mysqld/mysqld10.sock` (Synology MariaDB 10 소켓 경로. 보통 `localhost`로 안 되면 `127.0.0.1:3307` 등을 시도)
4. 사이트 제목, 관리자 계정 생성 후 설치 완료.
## 6. Headless 설정 (필수!)
React 앱이 데이터를 잘 가져오기 위해 다음 설정이 필요합니다.
### A. 고유주소 (Permalinks) 설정
1. 워드프레스 관리자 -> **설정** -> **고유주소**.
2. **글 이름 (Post name)** 선택.
3. [변경 사항 저장].
- *이 설정을 안 하면 JSON API가 404 에러를 낼 수 있습니다.*
### B. CORS 허용 (중요)
React 앱(다른 도메인)에서 WP(API 도메인)로 요청을 보내려면 CORS 허용이 필요합니다.
`functions.php` 파일은 **워드프레스 최상위 폴더가 아니라, 테마 폴더 안**에 있습니다.
---
### [추천] Headless용 테마 선택
Headless 방식에서는 **테마의 디자인이 사용자에게 보이지 않습니다**. (우리가 만든 React 앱이 보이기 때문입니다.)
따라서 관리자 페이지가 빠르고 가벼운 테마가 가장 좋습니다.
**추천 테마:**
1. **Twenty Twenty-Four (기본 테마)**: 가장 무난하고 관리가 잘 됩니다. 기본 설치되어 있으니 그대로 쓰셔도 충분합니다.
2. **Ollie**: 매우 가볍고 모던한 블록 테마입니다.
3. **Frost**: WP Engine에서 만든 미니멀 테마로 불필요한 기능이 없어 Headless용으로 인기가 많습니다.
**설정 방법:**
1. `wp-content` 폴더 더블클릭.
2. `themes` 폴더 더블클릭.
3. 선택한 테마(예: `twentytwentyfour`) 폴더로 들어갑니다.
4. 그 안에 있는 `functions.php` 파일을 엽니다.
5. 파일 맨 끝에 다음 코드를 추가합니다:
```php
add_action( 'init', function() {
header("Access-Control-Allow-Origin: *"); // 실제 운영 시 https://sokuree.com 으로 변경 권장
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
});
```
## 7. React 앱 연결 확인
React 앱의 [src/config/wordpress.js](file:///d:/antigravity/src/config/wordpress.js) 파일에서 `BASE_URL`이 위에서 설정한 워드프레스 주소와 맞는지 확인합니다.
예: `https://api.sokuree.com/wp-json`
이제 모든 준비가 되었습니다! React 앱에서 글을 불러올 수 있습니다.
## 8. 문제 해결 (Troubleshooting)
### Q. 테마/플러그인 삭제 시 FTP/SSH 정보를 물어봅니다.
이것은 워드프레스가 파일 권한 문제로 직접 쓰기를 못할 때 발생합니다. `wp-config.php` 파일에 한 줄만 추가하면 해결됩니다.
1. Synology File Station에서 `/web/sokuree_wp/wp-config.php` 파일을 엽니다.
2. 파일 **맨 마지막** 또는 `/* That's all, stop editing! ... */` 위에 다음 줄을 추가하세요:
```php
define('FS_METHOD', 'direct');
```
3. 저장하면 더 이상 FTP 정보를 묻지 않습니다.

View File

@ -1,29 +0,0 @@
# Site Polish & Completion
- [x] **Wordpress Integration (Backend)** <!-- id: 8 -->
- [x] API Setup
- [x] News Page Created
- [x] **Frontend Integration** <!-- id: 9 -->
- [x] Add "News" link to Navbar <!-- id: 10 -->
- [x] Add "Latest News" section to Homepage (`Home.jsx`) <!-- id: 11 -->
- [x] Update Footer with News link <!-- id: 12 -->
- [ ] **Content Updates** <!-- id: 14 -->
- [x] Update About Page (Profile, Intro)
- [x] **Update Services Page** <!-- id: 15 -->
- [x] Add "Training Courses" section (ISO, Auditor, Core Tools)
- [x] Add visual elements/images for training
- [x] **Resources Page Upgrade**
- [x] Implement Category Filtering (Downloads/Columns)
- [x] Update API to support categories
- [x] **Contact Page & Global UI**
- [x] Remove global "Consultation" buttons
- [x] Implement EmailJS/Formsubmit for Contact Form
- [x] **Deployment & Git**
- [x] Initialize Local Git Repository
- [x] Create Remote Gitea Repository
- [x] Push to Remote (Tag v1.0.0)
- [ ] Deploy to Synology
- [ ] **Deployment** <!-- id: 7 -->
- [x] Create `DEPLOYMENT_GUIDE_SYNOLOGY.md`
- [ ] Build React App (`npm run build`)
- [ ] Configure Synology Web Station for Frontend

View File

@ -1,29 +0,0 @@
# Site Polish & Completion
- [x] **Wordpress Integration (Backend)** <!-- id: 8 -->
- [x] API Setup
- [x] News Page Created
- [x] **Frontend Integration** <!-- id: 9 -->
- [x] Add "News" link to Navbar <!-- id: 10 -->
- [x] Add "Latest News" section to Homepage ([Home.jsx](file:///d:/antigravity/src/pages/Home.jsx)) <!-- id: 11 -->
- [x] Update Footer with News link <!-- id: 12 -->
- [ ] **Content Updates** <!-- id: 14 -->
- [x] Update About Page (Profile, Intro)
- [x] **Update Services Page** <!-- id: 15 -->
- [x] Add "Training Courses" section (ISO, Auditor, Core Tools)
- [x] Add visual elements/images for training
- [x] **Resources Page Upgrade**
- [x] Implement Category Filtering (Downloads/Columns)
- [x] Update API to support categories
- [x] **Contact Page & Global UI**
- [x] Remove global "Consultation" buttons
- [x] Implement EmailJS/Formsubmit for Contact Form
- [x] **Deployment & Git**
- [x] Initialize Local Git Repository
- [x] Create Remote Gitea Repository
- [x] Push to Remote (Tag v1.0.0)
- [ ] Deploy to Synology
- [ ] **Deployment** <!-- id: 7 -->
- [x] Create [DEPLOYMENT_GUIDE_SYNOLOGY.md](file:///C:/Users/choib/.gemini/antigravity/brain/9bc9f20a-cce8-4ffe-8a11-80ec51a849d7/DEPLOYMENT_GUIDE_SYNOLOGY.md)
- [ ] Build React App (`npm run build`)
- [ ] Configure Synology Web Station for Frontend

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;
}