Compare commits
No commits in common. "main" and "v1.1.0" have entirely different histories.
80
README.md
80
README.md
@ -1,78 +1,16 @@
|
||||
# Sokuree Consultant Web Project (v1.1.0)
|
||||
# React + Vite
|
||||
|
||||
**소쿠리 컨설턴트(Sokuree Consultant)**의 공식 웹사이트 프로젝트입니다.
|
||||
React 기반의 모던 프론트엔드와 WordPress Headless CMS 백엔드를 결합하여 제작되었습니다.
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
---
|
||||
Currently, two official plugins are available:
|
||||
|
||||
## 🛠 Tech Stack (기술 스택)
|
||||
- [@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
|
||||
|
||||
* **Frontend**: React, Vite
|
||||
* **Backend (CMS)**: WordPress (Headless API)
|
||||
* **Deployment**: Synology Web Station (Static Hosting)
|
||||
* **Key Libraries**: `react-router-dom`, `dompurify`, `formsubmit-co`
|
||||
## React Compiler
|
||||
|
||||
---
|
||||
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).
|
||||
|
||||
## 🚀 Getting Started (시작하기)
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
이 프로젝트를 처음부터 설치하고 실행하는 방법입니다.
|
||||
|
||||
### 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**: 초기 런칭 버전.
|
||||
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,69 +0,0 @@
|
||||
# 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)을 렌더링할 때 악성 스크립트를 제거합니다.
|
||||
59
docs/DEPLOYMENT_GUIDE_SYNOLOGY.md.resolved
Normal file
59
docs/DEPLOYMENT_GUIDE_SYNOLOGY.md.resolved
Normal file
@ -0,0 +1,59 @@
|
||||
# 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로 만든 멋진 사이트가 뜰 것입니다!
|
||||
@ -1,73 +0,0 @@
|
||||
# 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` 서비스의 **암호화된 고유 토큰**을 사용하여 데이터를 전송하므로, 크롤러에 의한 이메일 주소 수집을 원천적으로 차단합니다.
|
||||
@ -7,7 +7,7 @@ React 앱(`sokuree.com`)이 데이터를 가져오기 위해 필요한 백엔드
|
||||
다음 패키지들이 설치되어 있어야 합니다.
|
||||
- **Web Station**
|
||||
- **Apache HTTP Server 2.4** (또는 Nginx)
|
||||
- **PHP 8.3 이상** (Headless 모드는 최신 PHP 권장)
|
||||
- **PHP 8.0 이상** (Headless 모드는 최신 PHP 권장)
|
||||
- **MariaDB 10** (데이터베이스)
|
||||
- **phpMyAdmin** (DB 관리용, 선택사항이나 강력 추천)
|
||||
|
||||
|
||||
109
docs/WORDPRESS_SETUP.md.resolved
Normal file
109
docs/WORDPRESS_SETUP.md.resolved
Normal file
@ -0,0 +1,109 @@
|
||||
# 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 정보를 묻지 않습니다.
|
||||
29
docs/project_status.md
Normal file
29
docs/project_status.md
Normal file
@ -0,0 +1,29 @@
|
||||
# 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
|
||||
29
docs/task.md.resolved
Normal file
29
docs/task.md.resolved
Normal file
@ -0,0 +1,29 @@
|
||||
# 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
|
||||
@ -3,9 +3,9 @@
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/src/assets/logo.ico" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SOKUREE</title>
|
||||
<title>SmartBiz Tech - 소상공인 스마트 기술 파트너</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@ -24,8 +24,6 @@ function ScrollToTopComp() {
|
||||
return null;
|
||||
}
|
||||
|
||||
import Platform from './pages/Platform';
|
||||
|
||||
function App() {
|
||||
// Global Content Protection
|
||||
useEffect(() => {
|
||||
@ -45,7 +43,6 @@ 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 />} />
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 216 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 230 KiB |
@ -1,26 +1,23 @@
|
||||
const About = () => {
|
||||
return (
|
||||
<>
|
||||
<section className="about-hero">
|
||||
<div className="about-intro">
|
||||
<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>
|
||||
Sokuree Consultant는 현장의 목소리(데이터)에서 필요한 정보를 <br className="hidden md:block" />
|
||||
시스템(System)화 하여 기업의 지속 가능한 성장을 지원합니다.
|
||||
SmartBiz Tech는 급변하는 디지털 시대에 소상공인 여러분이 경쟁력을 갖출 수 있도록
|
||||
최고의 스마트 기술을 합리적인 가격에 공급합니다.
|
||||
</p>
|
||||
<p>
|
||||
단순한 기기 판매가 아닌, 매장 환경 분석부터 설치, 유지보수까지
|
||||
원스톱 서비스를 제공하여 사장님은 경영에만 집중하실 수 있도록 돕겠습니다.
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
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;
|
||||
@ -1,30 +0,0 @@
|
||||
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;
|
||||
@ -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> | 전화: 010-2125-0217
|
||||
이메일: <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
|
||||
</p>
|
||||
</div>
|
||||
<div className="footer-links">
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
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;
|
||||
@ -19,7 +19,6 @@ 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>
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
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;
|
||||
@ -1,69 +0,0 @@
|
||||
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;
|
||||
@ -1,35 +0,0 @@
|
||||
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;
|
||||
@ -1,28 +0,0 @@
|
||||
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;
|
||||
@ -1,11 +1,29 @@
|
||||
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 */}
|
||||
<AboutIntro />
|
||||
<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>
|
||||
|
||||
<section className="expert-profile">
|
||||
<h2 className="section-title">대표 컨설턴트</h2>
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import CaseItem from '../components/CaseItem';
|
||||
|
||||
|
||||
const Cases = () => {
|
||||
@ -50,7 +49,41 @@ const Cases = () => {
|
||||
|
||||
<div className="cases-grid">
|
||||
{caseStudies.map(study => (
|
||||
<CaseItem key={study.id} study={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>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,23 +1,112 @@
|
||||
import Hero from '../components/Hero';
|
||||
import Solutions from '../components/Solutions';
|
||||
import Contact from '../components/Contact';
|
||||
import WhyUs from '../components/WhyUs';
|
||||
import Process from '../components/Process';
|
||||
import CasesPreview from '../components/CasesPreview';
|
||||
import LatestNews from '../components/LatestNews';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { usePosts } from '../hooks/usePosts';
|
||||
import PostCard from '../components/PostCard';
|
||||
|
||||
const Home = () => {
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
<WhyUs />
|
||||
|
||||
{/* 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>
|
||||
|
||||
<Solutions />
|
||||
<Process />
|
||||
<CasesPreview />
|
||||
<LatestNews />
|
||||
|
||||
{/* 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 />
|
||||
|
||||
<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;
|
||||
|
||||
@ -3,13 +3,6 @@ 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);
|
||||
@ -88,7 +81,7 @@ const NewsDetail = () => {
|
||||
{/* Content */}
|
||||
<div
|
||||
className="article-content"
|
||||
dangerouslySetInnerHTML={{ __html: formatContent(post.content.rendered) }}
|
||||
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
|
||||
/>
|
||||
|
||||
{/* Footer / Navigation */}
|
||||
|
||||
@ -1,141 +0,0 @@
|
||||
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;
|
||||
@ -1,7 +1,5 @@
|
||||
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();
|
||||
@ -28,10 +26,97 @@ const ServiceDetail = () => {
|
||||
<div className="service-content-wrapper">
|
||||
|
||||
{/* Section 1: Education Purpose (3 Cards) */}
|
||||
<ServicePurpose />
|
||||
<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>
|
||||
|
||||
|
||||
{/* Section 2: Curriculum (4 Numbered Columns) */}
|
||||
<ServiceCurriculum onDownloadGuide={handleDownloadGuide} />
|
||||
<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>
|
||||
|
||||
{/* Bottom CTA Banner */}
|
||||
<div className="cta-banner">
|
||||
|
||||
@ -67,6 +67,7 @@ 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 */
|
||||
}
|
||||
@ -1326,7 +1327,7 @@ img {
|
||||
|
||||
.news-article {
|
||||
background-color: var(--white);
|
||||
max-width: 1200px;
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
margin: 0 20px;
|
||||
padding: 3rem;
|
||||
@ -1373,16 +1374,6 @@ 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 ... */
|
||||
@ -1483,137 +1474,3 @@ img {
|
||||
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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user