Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49bcd4be0d | ||
|
|
0534e5dfc0 | ||
|
|
5f0c15399d | ||
|
|
461917a15e | ||
|
|
253c1c9ba5 | ||
|
|
468b7f3d68 | ||
|
|
1ca741eeae | ||
|
|
5261735536 | ||
|
|
20f755fee4 | ||
|
|
22270746e6 | ||
|
|
8d95d5eb93 | ||
|
|
67b2684f64 | ||
|
|
10f3d3468b | ||
|
|
9ca4160d0f | ||
|
|
edf92c93a9 | ||
|
|
6686cf1dcd | ||
|
|
e3d8d7d8e5 | ||
|
|
8d5bab43c2 | ||
|
|
1fb7634e95 | ||
|
|
4863537f6a | ||
|
|
d6385513d9 | ||
|
|
9d294bdf4a | ||
|
|
9697f5c853 | ||
|
|
d012d83d64 | ||
|
|
70c340251e | ||
|
|
e715f1c91c | ||
|
|
39f66f2966 | ||
|
|
058f427072 | ||
|
|
4059c194c5 | ||
|
|
21533c3300 |
80
README.md
80
README.md
@ -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
|
## 🛠 Tech Stack (기술 스택)
|
||||||
- [@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
|
|
||||||
|
|
||||||
## 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
69
docs/CODE_STRUCTURE.md
Normal 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)을 렌더링할 때 악성 스크립트를 제거합니다.
|
||||||
59
docs/DEPLOYMENT_GUIDE_SYNOLOGY.md
Normal file
59
docs/DEPLOYMENT_GUIDE_SYNOLOGY.md
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`, `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
73
docs/TECH_STACK.md
Normal 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` 서비스의 **암호화된 고유 토큰**을 사용하여 데이터를 전송하므로, 크롤러에 의한 이메일 주소 수집을 원천적으로 차단합니다.
|
||||||
109
docs/WORDPRESS_SETUP.md
Normal file
109
docs/WORDPRESS_SETUP.md
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.3 이상** (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` 파일에서 `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 정보를 묻지 않습니다.
|
||||||
27
index.html
27
index.html
@ -1,13 +1,16 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="ko">
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
<head>
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<link rel="icon" href="/src/assets/logo.ico" />
|
||||||
<title>SmartBiz Tech - 소상공인 스마트 기술 파트너</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
</head>
|
<title>SOKUREE</title>
|
||||||
<body>
|
</head>
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.jsx"></script>
|
<body>
|
||||||
</body>
|
<div id="root"></div>
|
||||||
</html>
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
17
package-lock.json
generated
17
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
|
"dompurify": "^3.3.1",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-router-dom": "^7.12.0"
|
"react-router-dom": "^7.12.0"
|
||||||
@ -1446,6 +1447,13 @@
|
|||||||
"@types/react": "^19.2.0"
|
"@types/react": "^19.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/trusted-types": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz",
|
||||||
@ -1787,6 +1795,15 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dompurify": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==",
|
||||||
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
|
"dompurify": "^3.3.1",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-router-dom": "^7.12.0"
|
"react-router-dom": "^7.12.0"
|
||||||
|
|||||||
8
public/.htaccess
Normal file
8
public/.htaccess
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteBase /
|
||||||
|
RewriteRule ^index\.html$ - [L]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule . /index.html [L]
|
||||||
|
</IfModule>
|
||||||
42
src/App.css
42
src/App.css
@ -1,42 +0,0 @@
|
|||||||
#root {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 6em;
|
|
||||||
padding: 1.5em;
|
|
||||||
will-change: filter;
|
|
||||||
transition: filter 300ms;
|
|
||||||
}
|
|
||||||
.logo:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #646cffaa);
|
|
||||||
}
|
|
||||||
.logo.react:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
a:nth-of-type(2) .logo {
|
|
||||||
animation: logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
padding: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.read-the-docs {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
22
src/App.jsx
22
src/App.jsx
@ -1,8 +1,9 @@
|
|||||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter as Router, Routes, Route, Navigate, Link } from 'react-router-dom';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import Layout from './components/Layout';
|
import Layout from './components/Layout';
|
||||||
|
// Home component is no longer used as index, redirected to About
|
||||||
import Home from './pages/Home';
|
import Home from './pages/Home';
|
||||||
import About from './pages/About';
|
import About from './pages/About';
|
||||||
import ServiceDetail from './pages/ServiceDetail';
|
import ServiceDetail from './pages/ServiceDetail';
|
||||||
@ -11,6 +12,7 @@ import Resources from './pages/Resources';
|
|||||||
import News from './pages/News';
|
import News from './pages/News';
|
||||||
import NewsDetail from './pages/NewsDetail';
|
import NewsDetail from './pages/NewsDetail';
|
||||||
import ContactPage from './pages/ContactPage';
|
import ContactPage from './pages/ContactPage';
|
||||||
|
import Privacy from './pages/Privacy';
|
||||||
|
|
||||||
function ScrollToTopComp() {
|
function ScrollToTopComp() {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
@ -22,14 +24,28 @@ function ScrollToTopComp() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import Platform from './pages/Platform';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
// Global Content Protection
|
||||||
|
useEffect(() => {
|
||||||
|
const handleContextMenu = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
document.addEventListener('contextmenu', handleContextMenu);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('contextmenu', handleContextMenu);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<ScrollToTopComp />
|
<ScrollToTopComp />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Layout />}>
|
<Route path="/" element={<Layout />}>
|
||||||
<Route index element={<Home />} />
|
<Route index element={<Navigate to="/about" replace />} />
|
||||||
<Route path="about" element={<About />} />
|
<Route path="about" element={<About />} />
|
||||||
|
<Route path="platform" element={<Platform />} />
|
||||||
<Route path="services" element={<ServiceDetail />} />
|
<Route path="services" element={<ServiceDetail />} />
|
||||||
<Route path="services/:serviceId" element={<ServiceDetail />} />
|
<Route path="services/:serviceId" element={<ServiceDetail />} />
|
||||||
<Route path="cases" element={<Cases />} />
|
<Route path="cases" element={<Cases />} />
|
||||||
@ -38,8 +54,10 @@ function App() {
|
|||||||
<Route path="news/:slug" element={<NewsDetail />} />
|
<Route path="news/:slug" element={<NewsDetail />} />
|
||||||
<Route path="resources/:slug" element={<NewsDetail />} /> {/* Reuse NewsDetail for resources */}
|
<Route path="resources/:slug" element={<NewsDetail />} /> {/* Reuse NewsDetail for resources */}
|
||||||
<Route path="contact" element={<ContactPage />} />
|
<Route path="contact" element={<ContactPage />} />
|
||||||
|
<Route path="privacy" element={<Privacy />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
<Link to="/contact" className="floating-cta" style={{ zIndex: 9999 }}>상담하기 💬</Link>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/assets/214742.png
Normal file
BIN
src/assets/214742.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 216 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 847 KiB |
BIN
src/assets/consultant_profile.webp
Normal file
BIN
src/assets/consultant_profile.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
BIN
src/assets/logo.ico
Normal file
BIN
src/assets/logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
BIN
src/assets/logo.webp
Normal file
BIN
src/assets/logo.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@ -1,23 +1,26 @@
|
|||||||
const About = () => {
|
const About = () => {
|
||||||
return (
|
return (
|
||||||
<section id="about" className="section-padding bg-light">
|
<>
|
||||||
<div className="container">
|
<section className="about-hero">
|
||||||
<h2 className="section-title">회사 소개</h2>
|
<div className="about-intro">
|
||||||
<div className="about-content">
|
<p>
|
||||||
<div className="about-text">
|
Sokuree Consultant는 현장의 목소리(데이터)에서 필요한 정보를 <br className="hidden md:block" />
|
||||||
<h3>소상공인과 함께 성장하는 스마트 파트너</h3>
|
시스템(System)화 하여 기업의 지속 가능한 성장을 지원합니다.
|
||||||
<p>
|
</p>
|
||||||
SmartBiz Tech는 급변하는 디지털 시대에 소상공인 여러분이 경쟁력을 갖출 수 있도록
|
|
||||||
최고의 스마트 기술을 합리적인 가격에 공급합니다.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
단순한 기기 판매가 아닌, 매장 환경 분석부터 설치, 유지보수까지
|
|
||||||
원스톱 서비스를 제공하여 사장님은 경영에만 집중하실 수 있도록 돕겠습니다.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</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>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
43
src/components/CaseItem.jsx
Normal file
43
src/components/CaseItem.jsx
Normal 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;
|
||||||
30
src/components/CasesPreview.jsx
Normal file
30
src/components/CasesPreview.jsx
Normal 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;
|
||||||
@ -24,10 +24,28 @@ const Contact = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Phone Validation
|
||||||
|
const phoneRegex = /^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$/;
|
||||||
|
if (!phoneRegex.test(formData.phone)) {
|
||||||
|
setStatus('올바른 휴대전화 번호 형식을 입력해주세요 (예: 010-1234-5678)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Rate Limiting (Prevent spamming)
|
||||||
|
const lastSubmission = localStorage.getItem('lastEmailSubmission');
|
||||||
|
if (lastSubmission) {
|
||||||
|
const timePassed = Date.now() - parseInt(lastSubmission, 10);
|
||||||
|
if (timePassed < 60000) { // 60 seconds
|
||||||
|
setStatus('잠시 후 다시 시도해주세요. (1분에 한 번만 전송 가능합니다)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setStatus('전송 중...');
|
setStatus('전송 중...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("https://formsubmit.co/ajax/contact@qideun.com", {
|
// Use Token instead of Email for security
|
||||||
|
const response = await fetch("https://formsubmit.co/ajax/212b91b0ad256186720580d133b7a9e4", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@ -44,6 +62,7 @@ const Contact = () => {
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setStatus('문의가 성공적으로 접수되었습니다! 담당자가 곧 연락드리겠습니다.');
|
setStatus('문의가 성공적으로 접수되었습니다! 담당자가 곧 연락드리겠습니다.');
|
||||||
setFormData({ name: '', phone: '', message: '', honeypot: '' });
|
setFormData({ name: '', phone: '', message: '', honeypot: '' });
|
||||||
|
localStorage.setItem('lastEmailSubmission', Date.now().toString());
|
||||||
alert('문의가 접수되었습니다. 확인 후 연락드리겠습니다!');
|
alert('문의가 접수되었습니다. 확인 후 연락드리겠습니다!');
|
||||||
} else {
|
} else {
|
||||||
setStatus('전송에 실패했습니다. 잠시 후 다시 시도해주세요.');
|
setStatus('전송에 실패했습니다. 잠시 후 다시 시도해주세요.');
|
||||||
|
|||||||
@ -6,14 +6,14 @@ const Footer = () => {
|
|||||||
<div className="container footer-container">
|
<div className="container footer-container">
|
||||||
<div className="footer-info">
|
<div className="footer-info">
|
||||||
<h3>Sokuree Consultant</h3>
|
<h3>Sokuree Consultant</h3>
|
||||||
<p>Representative: Gildong Hong | Business License: 000-00-00000</p>
|
<p>대표: 최병규 | Business License: 000-00-00000</p>
|
||||||
<p>주소: 서울특별시 ... (추후 업데이트)</p>
|
<p>주소: 전북 순창군 ... (추후 업데이트)</p>
|
||||||
<p>이메일: contact@sokuree.com | 전화: 02-0000-0000</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
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="footer-links">
|
<div className="footer-links">
|
||||||
<Link to="/news">소식</Link>
|
|
||||||
<Link to="/privacy">개인정보처리방침</Link>
|
<Link to="/privacy">개인정보처리방침</Link>
|
||||||
<Link to="/terms">이용약관</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="footer-copyright">
|
<div className="footer-copyright">
|
||||||
|
|||||||
31
src/components/LatestNews.jsx
Normal file
31
src/components/LatestNews.jsx
Normal 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;
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import logo from '../assets/logo.webp';
|
||||||
|
|
||||||
const Navbar = () => {
|
const Navbar = () => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
@ -11,10 +12,14 @@ const Navbar = () => {
|
|||||||
return (
|
return (
|
||||||
<nav className="navbar">
|
<nav className="navbar">
|
||||||
<div className="container navbar-container">
|
<div className="container navbar-container">
|
||||||
<Link to="/" className="logo">Sokuree Consultant</Link>
|
<Link to="/about" className="logo">
|
||||||
|
<img src={logo} alt="Sokuree Logo" className="logo-image" width="40" height="40" />
|
||||||
|
Sokuree Consultant
|
||||||
|
</Link>
|
||||||
|
|
||||||
<div className={`nav-links ${isOpen ? 'active' : ''}`}>
|
<div className={`nav-links ${isOpen ? 'active' : ''}`}>
|
||||||
<Link to="/about" onClick={toggleMenu}>회사소개</Link>
|
<Link to="/about" onClick={toggleMenu}>회사소개</Link>
|
||||||
|
<Link to="/platform" onClick={toggleMenu}>플랫폼</Link>
|
||||||
<Link to="/services" onClick={toggleMenu}>서비스</Link>
|
<Link to="/services" onClick={toggleMenu}>서비스</Link>
|
||||||
<Link to="/cases" onClick={toggleMenu}>사례</Link>
|
<Link to="/cases" onClick={toggleMenu}>사례</Link>
|
||||||
<Link to="/news" onClick={toggleMenu}>소식</Link>
|
<Link to="/news" onClick={toggleMenu}>소식</Link>
|
||||||
|
|||||||
@ -1,117 +0,0 @@
|
|||||||
/* PostCard.css */
|
|
||||||
|
|
||||||
.post-card {
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
||||||
overflow: hidden;
|
|
||||||
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-card:hover {
|
|
||||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
||||||
transform: translateY(-5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-card-image-wrapper {
|
|
||||||
height: 12rem;
|
|
||||||
background-color: #e5e7eb;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-card-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-card:hover .post-card-image {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-card-fallback {
|
|
||||||
font-size: 3rem;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-card-content {
|
|
||||||
padding: 1.5rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: #6b7280;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-category-badge {
|
|
||||||
color: #2563eb;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-meta-divider {
|
|
||||||
margin: 0 0.5rem;
|
|
||||||
color: #e5e7eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-title {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-title a {
|
|
||||||
color: #111827;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-title a:hover {
|
|
||||||
color: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-excerpt {
|
|
||||||
color: #4b5563;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
/* Line clamping for Webkit browsers */
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 3;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-read-more {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
color: #2563eb;
|
|
||||||
font-weight: 600;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-read-more:hover {
|
|
||||||
color: #1d4ed8;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import './PostCard.css';
|
|
||||||
|
|
||||||
const PostCard = ({ post }) => {
|
const PostCard = ({ post }) => {
|
||||||
const { title, excerpt, slug, _embedded } = post;
|
const { title, excerpt, slug, _embedded } = post;
|
||||||
|
|||||||
19
src/components/Process.jsx
Normal file
19
src/components/Process.jsx
Normal 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;
|
||||||
@ -1,93 +0,0 @@
|
|||||||
/* ResourceListItem.css */
|
|
||||||
|
|
||||||
.resource-list-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1.25rem 0;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-list-item:hover {
|
|
||||||
background-color: #f9fafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-item-date {
|
|
||||||
width: 120px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #6b7280;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-item-content {
|
|
||||||
flex: 1;
|
|
||||||
padding-right: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-item-title {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #111827;
|
|
||||||
text-decoration: none;
|
|
||||||
display: block;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-item-title:hover {
|
|
||||||
color: #2563eb;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-item-meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1.5rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-item-author {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #6b7280;
|
|
||||||
display: none;
|
|
||||||
/* Hidden on small screens */
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-item-link {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #2563eb;
|
|
||||||
text-decoration: none;
|
|
||||||
padding: 0.4rem 0.8rem;
|
|
||||||
background-color: #eff6ff;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
transition: all 0.2s;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-item-link:hover {
|
|
||||||
background-color: #dbeafe;
|
|
||||||
color: #1d4ed8;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
|
||||||
.resource-item-author {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.resource-list-item {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-item-date {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-item-meta {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import './ResourceListItem.css';
|
|
||||||
|
|
||||||
const ResourceListItem = ({ post }) => {
|
const ResourceListItem = ({ post }) => {
|
||||||
const { title, date, slug, _embedded } = post;
|
const { title, date, slug, _embedded } = post;
|
||||||
|
|||||||
69
src/components/ServiceCurriculum.jsx
Normal file
69
src/components/ServiceCurriculum.jsx
Normal 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;
|
||||||
35
src/components/ServicePurpose.jsx
Normal file
35
src/components/ServicePurpose.jsx
Normal 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
28
src/components/WhyUs.jsx
Normal 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;
|
||||||
353
src/index.css
353
src/index.css
@ -1,353 +0,0 @@
|
|||||||
:root {
|
|
||||||
--primary-color: #0056b3;
|
|
||||||
--secondary-color: #004494;
|
|
||||||
--text-color: #333333;
|
|
||||||
--light-gray: #f8f9fa;
|
|
||||||
--white: #ffffff;
|
|
||||||
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
--transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: var(--text-color);
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Utilities */
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-padding {
|
|
||||||
padding: 80px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-light {
|
|
||||||
background-color: var(--light-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 2.5rem;
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 12px 30px;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
transition: var(--transition);
|
|
||||||
cursor: pointer;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
color: var(--white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navbar */
|
|
||||||
.navbar {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
background-color: var(--white);
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
z-index: 1000;
|
|
||||||
height: 70px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--primary-color);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links a {
|
|
||||||
margin-left: 30px;
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--text-color);
|
|
||||||
font-weight: 500;
|
|
||||||
transition: var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links a:hover {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hamburger {
|
|
||||||
display: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hamburger .bar {
|
|
||||||
display: block;
|
|
||||||
width: 25px;
|
|
||||||
height: 3px;
|
|
||||||
background-color: var(--text-color);
|
|
||||||
margin: 5px auto;
|
|
||||||
transition: var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hero */
|
|
||||||
.hero {
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('https://images.unsplash.com/photo-1556740758-90de374c12ad?ixlib=rb-1.2.1&auto=format&fit=crop&w=1950&q=80');
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
color: var(--white);
|
|
||||||
text-align: center;
|
|
||||||
padding-top: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-content h1 {
|
|
||||||
font-size: 3rem;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-content p {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Solutions */
|
|
||||||
.solutions-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
gap: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.solution-card {
|
|
||||||
background: var(--white);
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
text-align: center;
|
|
||||||
transition: var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.solution-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.solution-icon {
|
|
||||||
font-size: 3rem;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Contact */
|
|
||||||
.contact-form {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: var(--white);
|
|
||||||
padding: 40px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input,
|
|
||||||
.form-group textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-status {
|
|
||||||
margin-top: 20px;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buttons */
|
|
||||||
.btn-outline {
|
|
||||||
background-color: transparent;
|
|
||||||
border: 2px solid var(--white);
|
|
||||||
color: var(--white);
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-outline:hover {
|
|
||||||
background-color: var(--white);
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-nav {
|
|
||||||
padding: 8px 16px;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
color: var(--white) !important;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-nav:hover {
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Service Link */
|
|
||||||
.service-link {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 15px;
|
|
||||||
color: var(--primary-color);
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-link:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer Dark */
|
|
||||||
.footer-dark {
|
|
||||||
background-color: #222;
|
|
||||||
color: #ccc;
|
|
||||||
padding: 50px 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
border-bottom: 1px solid #444;
|
|
||||||
padding-bottom: 30px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-info h3 {
|
|
||||||
color: var(--white);
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links a {
|
|
||||||
color: #ccc;
|
|
||||||
text-decoration: none;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links a:hover {
|
|
||||||
color: var(--white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-copyright {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Floating CTA */
|
|
||||||
.floating-cta {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 30px;
|
|
||||||
right: 30px;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
color: var(--white);
|
|
||||||
padding: 15px 30px;
|
|
||||||
border-radius: 50px;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
|
||||||
z-index: 1000;
|
|
||||||
transition: var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-cta:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.hero-btns {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-outline {
|
|
||||||
margin-left: 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-container {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links a {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links {
|
|
||||||
position: absolute;
|
|
||||||
top: 70px;
|
|
||||||
left: -100%;
|
|
||||||
background-color: var(--white);
|
|
||||||
width: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px 0;
|
|
||||||
transition: 0.3s;
|
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links.active {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links a {
|
|
||||||
display: block;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-content h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import './index.css'
|
import './styles/main.css'
|
||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
|
|||||||
@ -1,59 +1,31 @@
|
|||||||
import consultantProfile from '../assets/consultant_profile.png';
|
import consultantProfile from '../assets/consultant_profile.webp';
|
||||||
|
import AboutIntro from '../components/About';
|
||||||
|
|
||||||
const About = () => {
|
const About = () => {
|
||||||
return (
|
return (
|
||||||
<div className="container" style={{ marginTop: '70px', minHeight: '80vh' }}>
|
<div className="container" style={{ marginTop: '70px', minHeight: '80vh' }}>
|
||||||
{/* Intro Section with Background */}
|
{/* Intro Section with Background */}
|
||||||
<section style={{
|
<AboutIntro />
|
||||||
marginBottom: '60px',
|
|
||||||
textAlign: 'center',
|
|
||||||
padding: '80px 20px',
|
|
||||||
background: `linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.8)), url('https://images.unsplash.com/photo-1581091226825-a6a2a5aee158?ixlib=rb-1.2.1&auto=format&fit=crop&w=1950&q=80')`,
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'center',
|
|
||||||
borderRadius: '20px',
|
|
||||||
boxShadow: '0 10px 30px rgba(0,0,0,0.05)'
|
|
||||||
}}>
|
|
||||||
<h1 className="section-title" style={{ marginBottom: '30px' }}>회사 소개</h1>
|
|
||||||
<div className="about-intro" style={{ maxWidth: '800px', margin: '0 auto' }}>
|
|
||||||
<p style={{ fontSize: '1.2rem', lineHeight: '1.8', fontWeight: '500', color: '#333' }}>
|
|
||||||
Sokuree Consultant는 현장의 목소리(데이터)에서 필요한 정보를 <br className="hidden md:block" />
|
|
||||||
시스템(System)화 하여 기업의 지속 가능한 성장을 지원합니다.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="vision-mission" style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '30px', marginBottom: '80px' }}>
|
<section className="expert-profile">
|
||||||
<div style={{ padding: '30px', background: '#f8f9fa', borderRadius: '10px' }}>
|
|
||||||
<h3>Vision</h3>
|
|
||||||
<p>누구나 쉽게 스마트 경영을 실현하는 세상</p>
|
|
||||||
</div>
|
|
||||||
<div style={{ padding: '30px', background: '#f8f9fa', borderRadius: '10px' }}>
|
|
||||||
<h3>Mission</h3>
|
|
||||||
<p>복잡한 기술을 현장에 맞게 단순화하고, 실질적인 성과를 창출하는 것</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="expert-profile" style={{ marginBottom: '80px' }}>
|
|
||||||
<h2 className="section-title">대표 컨설턴트</h2>
|
<h2 className="section-title">대표 컨설턴트</h2>
|
||||||
<div style={{ display: 'flex', gap: '50px', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'center' }}>
|
<div className="profile-wrapper">
|
||||||
<div style={{ flex: '0 0 350px' }}>
|
<div className="profile-image-container">
|
||||||
<img
|
<img
|
||||||
src={consultantProfile}
|
src={consultantProfile}
|
||||||
alt="대표 컨설턴트"
|
alt="대표 컨설턴트"
|
||||||
style={{
|
className="profile-image"
|
||||||
width: '100%',
|
loading="lazy"
|
||||||
borderRadius: '20px',
|
width="400"
|
||||||
boxShadow: '0 15px 30px rgba(0,0,0,0.1)'
|
height="500"
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: '1', minWidth: '300px' }}>
|
<div className="profile-content-container">
|
||||||
<div style={{ background: 'white', padding: '30px', borderRadius: '20px', border: '1px solid #eee' }}>
|
<div className="profile-card">
|
||||||
<ul style={{ listStyle: 'none', padding: 0, lineHeight: '2.5', fontSize: '1.1rem' }}>
|
<ul className="profile-list">
|
||||||
<li className="flex items-center">
|
<li className="flex items-center">
|
||||||
<span style={{ marginRight: '10px' }}>✅</span>
|
<span style={{ marginRight: '10px' }}>✅</span>
|
||||||
품질관리 기술사
|
품질관리 기술사 / 6 Sigma MBB
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center">
|
<li className="flex items-center">
|
||||||
<span style={{ marginRight: '10px' }}>✅</span>
|
<span style={{ marginRight: '10px' }}>✅</span>
|
||||||
|
|||||||
@ -1,138 +0,0 @@
|
|||||||
/* Cases.css */
|
|
||||||
|
|
||||||
.cases-container {
|
|
||||||
padding-top: 100px;
|
|
||||||
min-height: 100vh;
|
|
||||||
background-color: #f9fafb;
|
|
||||||
/* Gray-50 */
|
|
||||||
padding-bottom: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cases-content-wrapper {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cases-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cases-title {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #111827;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cases-subtitle {
|
|
||||||
color: #4b5563;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cases-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.cases-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-card {
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
||||||
overflow: hidden;
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-image-wrapper {
|
|
||||||
height: 16rem;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: #e5e7eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
transition: transform 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-card:hover .case-image {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-category-badge {
|
|
||||||
position: absolute;
|
|
||||||
top: 1rem;
|
|
||||||
left: 1rem;
|
|
||||||
background-color: #2563eb;
|
|
||||||
/* Blue-600 */
|
|
||||||
color: white;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border-radius: 9999px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-bar {
|
|
||||||
height: 0.5rem;
|
|
||||||
background-color: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-content {
|
|
||||||
padding: 2rem;
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-category-text {
|
|
||||||
color: #2563eb;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-card-title {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #111827;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-result-badge {
|
|
||||||
display: inline-block;
|
|
||||||
background-color: #eff6ff;
|
|
||||||
/* Blue-50 */
|
|
||||||
color: #1d4ed8;
|
|
||||||
/* Blue-700 */
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-details {
|
|
||||||
color: #4b5563;
|
|
||||||
line-height: 1.625;
|
|
||||||
}
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import './Cases.css';
|
import CaseItem from '../components/CaseItem';
|
||||||
|
|
||||||
|
|
||||||
const Cases = () => {
|
const Cases = () => {
|
||||||
const caseStudies = [
|
const caseStudies = [
|
||||||
@ -49,41 +50,7 @@ const Cases = () => {
|
|||||||
|
|
||||||
<div className="cases-grid">
|
<div className="cases-grid">
|
||||||
{caseStudies.map(study => (
|
{caseStudies.map(study => (
|
||||||
<div key={study.id} className="case-card">
|
<CaseItem key={study.id} study={study} />
|
||||||
{/* 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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,112 +1,23 @@
|
|||||||
import Hero from '../components/Hero';
|
import Hero from '../components/Hero';
|
||||||
import Solutions from '../components/Solutions';
|
import Solutions from '../components/Solutions';
|
||||||
import Contact from '../components/Contact';
|
import Contact from '../components/Contact';
|
||||||
import { Link } from 'react-router-dom';
|
import WhyUs from '../components/WhyUs';
|
||||||
import { usePosts } from '../hooks/usePosts';
|
import Process from '../components/Process';
|
||||||
import PostCard from '../components/PostCard';
|
import CasesPreview from '../components/CasesPreview';
|
||||||
|
import LatestNews from '../components/LatestNews';
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Hero />
|
<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 />
|
<Solutions />
|
||||||
|
<Process />
|
||||||
{/* Process Section */}
|
<CasesPreview />
|
||||||
<section className="section-padding bg-light">
|
<LatestNews />
|
||||||
<div className="container">
|
|
||||||
<h2 className="section-title">진행 프로세스</h2>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', textAlign: 'center', gap: '20px' }}>
|
|
||||||
{['진단', '설계', '실행', '정착', '고도화'].map((step, index) => (
|
|
||||||
<div key={index} style={{ flex: 1, minWidth: '120px', background: 'white', padding: '20px', borderRadius: '10px', boxShadow: '0 4px 6px rgba(0,0,0,0.1)' }}>
|
|
||||||
<div style={{ fontSize: '2rem', marginBottom: '10px', color: '#0056b3' }}>{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 />
|
<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;
|
export default Home;
|
||||||
|
|||||||
@ -1,92 +0,0 @@
|
|||||||
/* News.css */
|
|
||||||
|
|
||||||
.news-container {
|
|
||||||
padding-top: 100px;
|
|
||||||
padding-bottom: 80px;
|
|
||||||
min-height: 100vh;
|
|
||||||
/* Ensures footer stays at the bottom */
|
|
||||||
background-color: #f9fafb;
|
|
||||||
/* Gray-50 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-content-wrapper {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-title {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #111827;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-subtitle {
|
|
||||||
color: #4b5563;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
max-width: 42rem;
|
|
||||||
margin: 0 auto;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.news-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.news-grid {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loading & Error States */
|
|
||||||
.news-loading,
|
|
||||||
.news-error,
|
|
||||||
.news-empty {
|
|
||||||
min-height: 100vh;
|
|
||||||
padding-top: 100px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
width: 3rem;
|
|
||||||
height: 3rem;
|
|
||||||
border: 4px solid #e5e7eb;
|
|
||||||
border-top-color: #2563eb;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-title {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #dc2626;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
color: #4b5563;
|
|
||||||
}
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { usePosts } from '../hooks/usePosts';
|
import { usePosts } from '../hooks/usePosts';
|
||||||
import PostCard from '../components/PostCard';
|
import PostCard from '../components/PostCard';
|
||||||
import './News.css';
|
|
||||||
|
|
||||||
const News = () => {
|
const News = () => {
|
||||||
// Pass the category slug 'news' to filter posts
|
// Pass the category slug 'news' to filter posts
|
||||||
|
|||||||
@ -1,165 +0,0 @@
|
|||||||
/* NewsDetail.css */
|
|
||||||
|
|
||||||
.news-detail-container {
|
|
||||||
padding-top: 100px;
|
|
||||||
padding-bottom: 80px;
|
|
||||||
min-height: 100vh;
|
|
||||||
background-color: #f9fafb;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.news-article {
|
|
||||||
background-color: white;
|
|
||||||
max-width: 800px;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 20px;
|
|
||||||
padding: 3rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.news-article {
|
|
||||||
padding: 1.5rem;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loading & Error */
|
|
||||||
.news-detail-loading,
|
|
||||||
.news-detail-error {
|
|
||||||
min-height: 100vh;
|
|
||||||
padding-top: 100px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-link {
|
|
||||||
color: #2563eb;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header */
|
|
||||||
.article-header {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-meta {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #6b7280;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-meta-divider {
|
|
||||||
margin: 0 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-title {
|
|
||||||
font-size: 2.25rem;
|
|
||||||
font-weight: 800;
|
|
||||||
color: #111827;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Image */
|
|
||||||
.article-image-wrapper {
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-image {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Content */
|
|
||||||
.article-content {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
line-height: 1.8;
|
|
||||||
color: #374151;
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-content p {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-content h2 {
|
|
||||||
font-size: 1.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #111827;
|
|
||||||
margin-top: 2.5rem;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-content h3 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1f2937;
|
|
||||||
margin-top: 2rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-content ul,
|
|
||||||
.article-content ol {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
padding-left: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-content li {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-content a {
|
|
||||||
color: #2563eb;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-content blockquote {
|
|
||||||
border-left: 4px solid #2563eb;
|
|
||||||
padding-left: 1rem;
|
|
||||||
font-style: italic;
|
|
||||||
color: #4b5563;
|
|
||||||
margin: 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-content img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
margin: 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer */
|
|
||||||
.article-footer {
|
|
||||||
border-top: 1px solid #e5e7eb;
|
|
||||||
padding-top: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-button {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.75rem 2rem;
|
|
||||||
background-color: #f3f4f6;
|
|
||||||
color: #374151;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-button:hover {
|
|
||||||
background-color: #e5e7eb;
|
|
||||||
}
|
|
||||||
@ -1,7 +1,14 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useParams, Link } from 'react-router-dom';
|
import { useParams, Link } from 'react-router-dom';
|
||||||
import { wpApi } from '../api/wordpress';
|
import { wpApi } from '../api/wordpress';
|
||||||
import './NewsDetail.css';
|
|
||||||
|
|
||||||
|
// 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 NewsDetail = () => {
|
||||||
const { slug } = useParams();
|
const { slug } = useParams();
|
||||||
@ -81,7 +88,7 @@ const NewsDetail = () => {
|
|||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div
|
<div
|
||||||
className="article-content"
|
className="article-content"
|
||||||
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
|
dangerouslySetInnerHTML={{ __html: formatContent(post.content.rendered) }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Footer / Navigation */}
|
{/* Footer / Navigation */}
|
||||||
|
|||||||
141
src/pages/Platform.jsx
Normal file
141
src/pages/Platform.jsx
Normal 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;
|
||||||
56
src/pages/Privacy.jsx
Normal file
56
src/pages/Privacy.jsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Privacy = () => {
|
||||||
|
return (
|
||||||
|
<div className="container" style={{ padding: '120px 20px 80px', maxWidth: '800px' }}>
|
||||||
|
<h1 className="section-title" style={{ textAlign: 'left', marginBottom: '40px' }}>개인정보처리방침</h1>
|
||||||
|
|
||||||
|
<div className="privacy-content" style={{ lineHeight: '1.8', color: 'var(--text-color)' }}>
|
||||||
|
<p><strong>Sokuree Consultant</strong>(이하 '회사')는 고객님의 개인정보를 중요시하며, "개인정보 보호법"을 준수하고 있습니다.</p>
|
||||||
|
<p>회사는 개인정보처리방침을 통하여 고객님께서 제공하시는 개인정보가 어떠한 용도와 방식으로 이용되고 있으며, 개인정보보호를 위해 어떠한 조치가 취해지고 있는지 알려드립니다.</p>
|
||||||
|
|
||||||
|
<h3 style={{ marginTop: '30px', marginBottom: '15px', fontSize: '1.2rem', fontWeight: 'bold' }}>1. 개인정보의 수집 및 이용목적</h3>
|
||||||
|
<p>회사는 다음의 목적을 위하여 개인정보를 처리합니다. 처리하고 있는 개인정보는 다음의 목적 이외의 용도로는 이용되지 않으며, 이용 목적이 변경되는 경우에는 개인정보 보호법 제18조에 따라 별도의 동의를 받는 등 필요한 조치를 이행할 예정입니다.</p>
|
||||||
|
<ul style={{ listStyleType: 'disc', paddingLeft: '20px', margin: '10px 0' }}>
|
||||||
|
<li><strong>고객 문의 처리:</strong> 서비스 관련 문의, 컨설팅 요청, 견적 상담 및 결과 회신</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 style={{ marginTop: '30px', marginBottom: '15px', fontSize: '1.2rem', fontWeight: 'bold' }}>2. 수집하는 개인정보의 항목</h3>
|
||||||
|
<p>회사는 상담 및 서비스 신청 등을 위해 아래와 같은 개인정보를 수집하고 있습니다.</p>
|
||||||
|
<ul style={{ listStyleType: 'disc', paddingLeft: '20px', margin: '10px 0' }}>
|
||||||
|
<li><strong>수집항목:</strong> 이름, 연락처(휴대전화번호)</li>
|
||||||
|
<li><strong>수집방법:</strong> 홈페이지 내 문의하기(Contact) 양식</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 style={{ marginTop: '30px', marginBottom: '15px', fontSize: '1.2rem', fontWeight: 'bold' }}>3. 개인정보의 보유 및 이용기간</h3>
|
||||||
|
<p>회사는 원칙적으로 개인정보 수집 및 이용목적이 달성된 후에는 해당 정보를 지체 없이 파기합니다.</p>
|
||||||
|
<ul style={{ listStyleType: 'disc', paddingLeft: '20px', margin: '10px 0' }}>
|
||||||
|
<li><strong>웹사이트 문의 정보:</strong> 본 웹사이트를 통해 접수된 개인정보는 별도의 데이터베이스(DB)에 저장되지 않으며, 담당자 이메일로 전송된 후 상담 완료 시 즉시 삭제(파기)됩니다.</li>
|
||||||
|
<li><strong>계약 및 발주 정보:</strong> 상담 후 실제 계약이 체결되어 업무가 진행되는 경우, 해당 정보는 웹사이트가 아닌 별도의 내부 시스템(오프라인/사내 서버)에서 관련 법령에 따라 관리됩니다.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 style={{ marginTop: '30px', marginBottom: '15px', fontSize: '1.2rem', fontWeight: 'bold' }}>4. 개인정보의 파기절차 및 방법</h3>
|
||||||
|
<p>회사는 원칙적으로 개인정보 수집 및 이용목적이 달성된 후에는 해당 정보를 지체 없이 파기합니다.</p>
|
||||||
|
<ul style={{ listStyleType: 'disc', paddingLeft: '20px', margin: '10px 0' }}>
|
||||||
|
<li><strong>파기절차:</strong> 상담 완료 후 불필요해진 개인정보가 포함된 이메일은 즉시 삭제됩니다.</li>
|
||||||
|
<li><strong>파기방법:</strong> 전자적 파일 형태(이메일)로 존재하므로, 담당자가 해당 이메일을 직접 삭제(휴지통 비우기 포함)하여 파기합니다.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 style={{ marginTop: '30px', marginBottom: '15px', fontSize: '1.2rem', fontWeight: 'bold' }}>5. 정보주체의 권리·의무 및 행사방법</h3>
|
||||||
|
<p>본 웹사이트는 별도의 회원가입 기능을 제공하지 않습니다. 따라서 정보주체는 개인정보 보호책임자에게 전화 또는 이메일로 연락하여 개인정보의 열람, 정정, 삭제, 처리정지를 요구할 수 있으며, 회사는 이에 대해 지체 없이 조치하겠습니다.</p>
|
||||||
|
|
||||||
|
<h3 style={{ marginTop: '30px', marginBottom: '15px', fontSize: '1.2rem', fontWeight: 'bold' }}>6. 개인정보 보호책임자</h3>
|
||||||
|
<p>회사는 개인정보 처리에 관한 업무를 총괄해서 책임지고, 개인정보 처리와 관련한 정보주체의 불만처리 및 피해구제 등을 위하여 아래와 같이 개인정보 보호책임자를 지정하고 있습니다.</p>
|
||||||
|
<ul style={{ listStyleType: 'none', paddingLeft: '0', margin: '10px 0', background: '#f9f9f9', padding: '15px', borderRadius: '8px' }}>
|
||||||
|
<li><strong>책임자:</strong> Sokuree Consultant 대표</li>
|
||||||
|
<li><strong>연락처:</strong> contact@sokuree.com</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 style={{ marginTop: '30px', marginBottom: '15px', fontSize: '1.2rem', fontWeight: 'bold' }}>7. 개인정보 처리방침 변경</h3>
|
||||||
|
<p>이 개인정보처리방침은 2026. 1. 19부터 적용됩니다.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Privacy;
|
||||||
@ -1,121 +0,0 @@
|
|||||||
/* Resources.css */
|
|
||||||
|
|
||||||
.resources-container {
|
|
||||||
padding-top: 100px;
|
|
||||||
padding-bottom: 80px;
|
|
||||||
min-height: 100vh;
|
|
||||||
background-color: #f9fafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resources-content-wrapper {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resources-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resources-title {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #111827;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resources-subtitle {
|
|
||||||
color: #4b5563;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tabs */
|
|
||||||
.resources-tabs {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
gap: 1rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button {
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
border-radius: 9999px;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1rem;
|
|
||||||
background-color: white;
|
|
||||||
color: #4b5563;
|
|
||||||
border: 1px solid #e5e7eb;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button:hover {
|
|
||||||
background-color: #f3f4f6;
|
|
||||||
color: #111827;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button.active {
|
|
||||||
background-color: #2563eb;
|
|
||||||
color: white;
|
|
||||||
border-color: #2563eb;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(37, 99, 235, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Two Column Layout for 'All' tab */
|
|
||||||
.resources-all-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.resources-all-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.resources-section {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* List Layout */
|
|
||||||
.resources-list {
|
|
||||||
background: white;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Section specific */
|
|
||||||
.section-heading {
|
|
||||||
border-left: 4px solid #2563eb;
|
|
||||||
padding-left: 1rem;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #111827;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-message {
|
|
||||||
text-align: center;
|
|
||||||
padding: 3rem;
|
|
||||||
color: #6b7280;
|
|
||||||
background: white;
|
|
||||||
border-radius: 1rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-helper-text {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
background-color: #eff6ff;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
color: #1e40af;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { usePosts } from '../hooks/usePosts';
|
import { usePosts } from '../hooks/usePosts';
|
||||||
import ResourceListItem from '../components/ResourceListItem';
|
import ResourceListItem from '../components/ResourceListItem';
|
||||||
import './Resources.css';
|
|
||||||
|
|
||||||
// IMPORTANT: Slugs must match your WordPress Category Slugs
|
// IMPORTANT: Slugs must match your WordPress Category Slugs
|
||||||
const CATEGORIES = {
|
const CATEGORIES = {
|
||||||
|
|||||||
@ -1,241 +0,0 @@
|
|||||||
/* ServiceDetail.css */
|
|
||||||
|
|
||||||
.service-container {
|
|
||||||
padding-top: 80px;
|
|
||||||
min-height: 100vh;
|
|
||||||
background-color: #ffffff;
|
|
||||||
padding-bottom: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header */
|
|
||||||
.service-header {
|
|
||||||
text-align: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-title {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #111827;
|
|
||||||
/* Gray-900 */
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-subtitle {
|
|
||||||
color: #4b5563;
|
|
||||||
/* Gray-600 */
|
|
||||||
font-size: 1.125rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-content-wrapper {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Section Common */
|
|
||||||
.service-section {
|
|
||||||
margin-bottom: 5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-label {
|
|
||||||
font-size: 1.875rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
color: #111827;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-desc {
|
|
||||||
color: #4b5563;
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Purpose Grid (Top 3 Cards) */
|
|
||||||
.purpose-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(1, 1fr);
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.purpose-grid {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.purpose-card {
|
|
||||||
background-color: #f9fafb;
|
|
||||||
/* Gray-50 */
|
|
||||||
border-radius: 1rem;
|
|
||||||
padding: 2rem;
|
|
||||||
height: 16rem;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.purpose-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.purpose-card-title {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1f2937;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.purpose-card-text {
|
|
||||||
color: #6b7280;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.purpose-icon {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
right: 1rem;
|
|
||||||
font-size: 4rem;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Curriculum Grid (Bottom 4 Columns) */
|
|
||||||
.curriculum-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(1, 1fr);
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.curriculum-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.curriculum-grid {
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.curriculum-card {
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid #e5e7eb;
|
|
||||||
border-radius: 1rem;
|
|
||||||
padding: 2rem;
|
|
||||||
transition: box-shadow 0.3s ease;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.curriculum-card:hover {
|
|
||||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.curriculum-number {
|
|
||||||
display: block;
|
|
||||||
color: #2563eb;
|
|
||||||
/* Blue-600 */
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.curriculum-title {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #111827;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
min-height: 3.5rem;
|
|
||||||
/* Align heights */
|
|
||||||
}
|
|
||||||
|
|
||||||
.curriculum-list {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: #4b5563;
|
|
||||||
}
|
|
||||||
|
|
||||||
.curriculum-list li {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.curriculum-bullet {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.curriculum-btn {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
background-color: #f9fafb;
|
|
||||||
border: 1px solid #e5e7eb;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
color: #374151;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.curriculum-btn:hover {
|
|
||||||
background-color: #f3f4f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CTA Banner */
|
|
||||||
.cta-banner {
|
|
||||||
background-color: #f9fafb;
|
|
||||||
border-radius: 1rem;
|
|
||||||
padding: 3.5rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 2rem;
|
|
||||||
margin-bottom: 5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.cta-banner {
|
|
||||||
flex-direction: row;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cta-title {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #111827;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cta-text {
|
|
||||||
color: #6b7280;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cta-button {
|
|
||||||
background-color: #2563eb;
|
|
||||||
color: white;
|
|
||||||
padding: 0.75rem 2rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(37, 99, 235, 0.3);
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cta-button:hover {
|
|
||||||
background-color: #1d4ed8;
|
|
||||||
}
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import './ServiceDetail.css';
|
import ServicePurpose from '../components/ServicePurpose';
|
||||||
|
import ServiceCurriculum from '../components/ServiceCurriculum';
|
||||||
|
|
||||||
const ServiceDetail = () => {
|
const ServiceDetail = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -27,97 +28,10 @@ const ServiceDetail = () => {
|
|||||||
<div className="service-content-wrapper">
|
<div className="service-content-wrapper">
|
||||||
|
|
||||||
{/* Section 1: Education Purpose (3 Cards) */}
|
{/* Section 1: Education Purpose (3 Cards) */}
|
||||||
<div className="service-section">
|
<ServicePurpose />
|
||||||
<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) */}
|
{/* Section 2: Curriculum (4 Numbered Columns) */}
|
||||||
<div className="service-section">
|
<ServiceCurriculum onDownloadGuide={handleDownloadGuide} />
|
||||||
<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 */}
|
{/* Bottom CTA Banner */}
|
||||||
<div className="cta-banner">
|
<div className="cta-banner">
|
||||||
|
|||||||
1619
src/styles/main.css
Normal file
1619
src/styles/main.css
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user