๐ฏ ํ๋ก ์นด๋ ๋ฆฌ๋ฉ - ์ค๋์ ์ด์ธ & ์กฐ์ธ
tarot-card-reading
๋ฌธ์ ์ค๋ช
๐ฎ ํ๋ก ์นด๋๋ก ์ค๋์ ์ด์ธ๋ฅผ ๋ฝ์๋ณด๋ ์ ๋น๋ก์ด ํ์ด์ง
๊ทธ๋๋ณด๋5 SE ์์์ ๋์ํ๋ ์ธํฐ๋ํฐ๋ธ ํ๋ก ์นด๋ ๋ฆฌ๋ฉ ํ์ด์ง์ ๋๋ค. ์ฌ์ฉ์๊ฐ ์นด๋๋ฅผ ์ ํํ๋ฉด ๋ค์งํ๋ ์ ๋๋ฉ์ด์ ๊ณผ ํจ๊ป ์ด์ธ ๋ฉ์์ง๊ฐ ๋ํ๋ฉ๋๋ค.
ํ์ ๊ธฐ๋ฅ
- ํ๋ก ์นด๋ ๋ฑ ๋์คํ๋ ์ด: 22์ฅ์ ๋ฉ์ด์ ์๋ฅด์นด๋ ์นด๋๋ฅผ ๋ท๋ฉด ์ํ๋ก ๋์ด
- ์นด๋ ์ ํ ์ธํฐ๋์ : ์นด๋ ํด๋ฆญ ์ ๋ค์ง๊ธฐ ์ ๋๋ฉ์ด์ (CSS transform ํ์ฉ)
- ์ด์ธ ๋ฉ์์ง ์ถ๋ ฅ: ์ ํํ ์นด๋์ ๋ฐ๋ฅธ ์ค๋์ ์ด์ธ, ์กฐ์ธ, ํค์๋ ํ์
- ๋คํฌ๋ชจ๋ ๋์: modern shell์ ๋์์ธ ํ ํฐ ํ์ฉ์ผ๋ก ์๋ ๋์
- ์ฌ์๋ ๋ฒํผ: ๋ค์ ๋ฝ๊ธฐ ๊ธฐ๋ฅ (ํ์ด์ง ์๋ก๊ณ ์นจ)
์ ํ ์ถ๊ฐ ๊ธฐ๋ฅ (๋ณด๋์ค)
- ์นด๋ ์ค๋ช ๋ชจ๋ฌ: ๊ฐ ์นด๋์ ์์ธ ์๋ฏธ๋ฅผ ํ์ ์ผ๋ก ๋ณด๊ธฐ
- 3์ฅ ์คํ๋ ๋: ๊ณผ๊ฑฐ-ํ์ฌ-๋ฏธ๋ 3์ฅ ๋ฆฌ๋ฉ ๋ชจ๋ ์ถ๊ฐ
- ๊ณต์ ํ๊ธฐ: ๋ฝ์ ์นด๋ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ฏธ์ง๋ก ์์ฑํ์ฌ SNS ๊ณต์ (html2canvas ํ์ฉ)
- ์ผ์ผ ๋ฆฌ๋ฉ ๊ธฐ๋ก: SQLite DB์ ๋ ์ง๋ณ ๋ฝ์ ์นด๋ ์ ์ฅ (์ฌ๋ฐฉ๋ฌธ ์ ์ค๋ ์ด๋ฏธ ๋ฝ์๋์ง ํ์ธ)
๊ธฐ์ ์คํ
- ๋ฐฑ์๋: ๊ทธ๋๋ณด๋5 SE (PHP 8.x)
- ํ๋ก ํธ์๋: HTML5, CSS3 (CSS ๋ณ์ + transform ์ ๋๋ฉ์ด์ ), Vanilla JavaScript
- ๋์์ธ: modern shell + ๋์์ธ ํ ํฐ (๋คํฌ๋ชจ๋ ์๋ ๋์)
- DB: ์ ํ ๊ธฐ๋ฅ์์๋ง SQLite ์ฌ์ฉ (์ผ์ผ ๊ธฐ๋ก ์ ์ฅ ์)
ํ์ต ํฌ์ธํธ
- g5se ํด๋ ๊ธฐ๋ฐ ์๋ ๋ผ์ฐํ
(
app/modules/tarot-card-reading/index.php) - modern shell ์ ์ฉ์ผ๋ก ์ฌ์ดํธ chrome ํต์ผ
- CSS transform์ ํ์ฉํ ์นด๋ ๋ค์ง๊ธฐ ์ ๋๋ฉ์ด์
- JavaScript ์ด๋ฒคํธ ํธ๋ค๋ง (ํด๋ฆญ, ์ ๋๋ฉ์ด์ ์๋ฃ ๊ฐ์ง)
- ๋๋ค ๋ฐ์ดํฐ ์ถ๋ ฅ (์นด๋ ์๊ธฐ, ์ด์ธ ๋ฉ์์ง ์ ํ)
- (์ ํ) SQLite PDO๋ฅผ ํ์ฉํ ๊ฐ๋จํ ๋ฐ์ดํฐ ์ ์ฅ
์ ์ฝ ์กฐ๊ฑด
- โข ๊ทธ๋๋ณด๋5 SE ์ค์น๋ณธ ์์์ ์์ (https://github.com/gnuboard/g5se main ๋ธ๋์น)
- โข ํด๋ ๊ธฐ๋ฐ ์๋ ๋ผ์ฐํ ์ฌ์ฉ โ app/modules/tarot-card-reading/index.php ์์ฑ, app/router.php ์์ ๋ถํ์
- โข modern shell ํ์ ์ ์ฉ (_head.inc.php + m-shell + _nav.inc.php + m-container + _footer.inc.php + tail.sub.php)
- โข ๋์์ธ ํ ํฐ ์ฌ์ฉ (--m-surface, --m-text, --m-text-muted, --m-shadow, --m-radius-lg ๋ฑ) โ ๋คํฌ๋ชจ๋ ์๋ ๋์
- โข ์์ PHP + HTML/CSS/JS๋ง ์ฌ์ฉ (Laravel/Composer/npm/Vite/React/Vue ๊ธ์ง)
- โข ์ธ๋ถ CDN ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ ๊ธ์ง (์์ Vanilla JS๋ก ๊ตฌํ)
- โข ํ๋ก ์นด๋ ์ด๋ฏธ์ง๋ CSS๋ก ํํ (๋ฐฐ๊ฒฝ์/๊ทธ๋ผ๋ฐ์ด์ /์์ด์ฝ ํฐํธ ํ์ฉ) ๋๋ Data URI ์๋ฒ ๋ฉ
- โข (์ ํ ๊ธฐ๋ฅ) DB ์ฌ์ฉ ์ G5_DATA_PATH.'/modules/tarot.sqlite' ์์น์ SQLite ํ์ผ ์์ฑ, prepared statement ํ์
- โข ์ฌ์ฉ์ ์ ๋ ฅ ์ถ๋ ฅ ์ htmlspecialchars() ์ด์ค์ผ์ดํ ํ์
- โข ํจ์/๋ฉ์๋ ์์์ require/include ๊ธ์ง (๊ธ๋ก๋ฒ ์ค์ฝํ์์๋ง)
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
g5se๊ฐ ์ค์น๋ ๋๋ ํฐ๋ฆฌ(๋ฃจํธ์ `index.php`ยท`app/`์ด ์๋ ๊ณณ)์์ ์์
์ ์์ํฉ๋๋ค. (g5se ์ค์น๋ https://github.com/gnuboard/g5se ์ฐธ๊ณ )
**Step 1: ํ๋ก ์นด๋ ๋ฆฌ๋ฉ ํ์ด์ง ๊ธฐ๋ณธ ๊ณจ๊ฒฉ ์์ฑ**
`app/modules/tarot-card-reading/index.php` ํ์ผ์ ์์ฑํ๊ณ modern shell ๊ณจ๊ฒฉ์ ์ ์ฉํ์ธ์.
์๊ตฌ์ฌํญ:
1. ํ์ผ ๊ฒฝ๋ก: `app/modules/tarot-card-reading/index.php`
2. modern shell ๊ณจ๊ฒฉ ํฌํจ:
- `include_once('./_common.php');` (๋งจ ์)
- `require_once(G5_THEME_PATH.'/modern/_head.inc.php');`
- `include_once('./head.sub.php');`
- `<div class="m-shell">` ๋ํผ
- `<?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>`
- `<main class="m-container" style="padding: 32px 20px 48px;">`
- ๋ณธ๋ฌธ ์์ญ
- `<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>`
- `</div>` (m-shell ๋ซ๊ธฐ)
- `<?php include_once('./tail.sub.php');`
3. ๋ณธ๋ฌธ์๋ ๋ค์ ์์ ํฌํจ:
- ํ์ด์ง ์ ๋ชฉ: `<h1>` ํ๊ทธ๋ก "๐ฎ ํ๋ก ์นด๋ ๋ฆฌ๋ฉ"
- ์๋ด ๋ฌธ๊ตฌ: "์นด๋๋ฅผ ํ ์ฅ ์ ํํ๋ฉด ์ค๋์ ์ด์ธ๋ฅผ ์๋ ค๋๋ฆฝ๋๋ค."
- ์นด๋ ๋ฑ ์์ญ: `<div id="card-deck">` (๋น์ด์์ โ ๋ค์ ๋จ๊ณ์์ JS๋ก ์ฑ์ธ ์์ )
- ๊ฒฐ๊ณผ ์์ญ: `<div id="result" style="display:none;">` (์ด๊ธฐ์๋ ์จ๊น)
4. ๋์์ธ ํ ํฐ ์ฌ์ฉ:
- ๋ณธ๋ฌธ ๋ฐฐ๊ฒฝ: `background: var(--m-surface);`
- ํ
์คํธ: `color: var(--m-text);`
- ์๋ด ๋ฌธ๊ตฌ: `color: var(--m-text-muted);`
5. ๊ธฐ๋ณธ ์คํ์ผ:
- ํ์ด์ง ์ ๋ชฉ ์ค์ ์ ๋ ฌ, ๋ง์ง ์ ์ฉ
- ์๋ด ๋ฌธ๊ตฌ ์ค์ ์ ๋ ฌ, ์์ ํฐํธ ์ฌ์ด์ฆ
- ์นด๋ ๋ฑ ์์ญ์ flexbox๋ก ์นด๋๋ฅผ ๊ฐ๋ก๋ก ๋์ดํ ์ค๋น (๋ค์ ๋จ๊ณ์์ ๊ตฌํ)
์ด ๋จ๊ณ๊ฐ ์๋ฃ๋๋ฉด `/tarot-card-reading` URL๋ก ์ ์ ์ ๋น ํ์ด์ง ๊ณจ๊ฒฉ์ด ๋ณด์ฌ์ผ ํฉ๋๋ค (nav/footer ํฌํจ).
๊ธฐ๋ ๊ฒฐ๊ณผ: modern shell์ด ์ ์ฉ๋ ํ๋ก ์นด๋ ๋ฆฌ๋ฉ ํ์ด์ง ๊ณจ๊ฒฉ์ด ์์ฑ๋ฉ๋๋ค. `/tarot-card-reading` ์ ์ ์ ์๋จ nav, ํ์ด์ง ์ ๋ชฉ, ์๋ด ๋ฌธ๊ตฌ, ๋น ์นด๋ ๋ฑ ์์ญ, ํ๋จ footer๊ฐ ํ์๋ฉ๋๋ค. ๋คํฌ๋ชจ๋ ์ ํ ์ ๋ฐฐ๊ฒฝ/ํ ์คํธ ์์์ด ์๋์ผ๋ก ๋ณ๊ฒฝ๋ฉ๋๋ค.
**Step 2: ํ๋ก ์นด๋ 22์ฅ ๋ฐ์ดํฐ ์ค๋น ๋ฐ ์นด๋ ๋ฑ ๋ ๋๋ง**
๋ฉ์ด์ ์๋ฅด์นด๋ 22์ฅ์ ์นด๋ ๋ฐ์ดํฐ๋ฅผ JavaScript ๋ฐฐ์ด๋ก ์ ์ํ๊ณ , ์นด๋ ๋ฑ์ ํ๋ฉด์ ๋ ๋๋งํ์ธ์.
์๊ตฌ์ฌํญ:
1. `app/modules/tarot-card-reading/index.php` ํ์ผ์ `</main>` ์ง์ ์ `<script>` ๋ธ๋ก ์ถ๊ฐ
2. JavaScript ๋ฐฐ์ด๋ก ํ๋ก ์นด๋ 22์ฅ ์ ์:
```javascript
const tarotCards = [
{ id: 0, name: '๋ฐ๋ณด(The Fool)', keyword: '์๋ก์ด ์์', message: '๋๋ ค์ ์์ด ์๋ก์ด ๋์ ์ ์์ํ์ธ์.' },
{ id: 1, name: '๋ง๋ฒ์ฌ(The Magician)', keyword: '์ฐฝ์กฐ', message: '๋น์ ์ ์ฌ๋ฅ์ ๋ฐํํ ๋์
๋๋ค.' },
{ id: 2, name: '์ฌ์ฌ์ (The High Priestess)', keyword: '์ง๊ด', message: '๋ด๋ฉด์ ๋ชฉ์๋ฆฌ์ ๊ท ๊ธฐ์ธ์ด์ธ์.' },
// ... (์ด 22์ฅ, ๋๋จธ์ง๋ AI๊ฐ ์ฑ์์ฃผ์ธ์: ์ฌํฉ์ , ํฉ์ , ๊ตํฉ, ์ฐ์ธ, ์ ์ฐจ, ํ, ์๋์, ์ด๋ช
์ ์๋ ๋ฐํด, ์ ์, ๋งค๋ฌ๋ฆฐ ์ฌ๋, ์ฃฝ์, ์ ์ , ์
๋ง, ํ, ๋ณ, ๋ฌ, ํ์, ์ฌํ, ์ธ๊ณ)
];
```
3. ์นด๋ ๋ฑ ๋ ๋๋ง ํจ์ ์์ฑ:
- `renderCardDeck()` ํจ์ ์ ์
- `#card-deck` ์์์ ์นด๋ 22์ฅ์ ๋์ ์ผ๋ก ์์ฑ
- ๊ฐ ์นด๋๋ `<div class="tarot-card">` ์์๋ก ํํ
- ์นด๋ ๋ท๋ฉด ๋์์ธ: CSS๋ก ํํ (๋ณด๋ผ์ ๊ทธ๋ผ๋ฐ์ด์
๋ฐฐ๊ฒฝ + ์ค์์ "?" ์์ด์ฝ)
- ๊ฐ ์นด๋์ `data-card-id` ์์ฑ์ผ๋ก ์นด๋ ID ์ ์ฅ
- ์นด๋ ํด๋ฆญ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก (๋ค์ ๋จ๊ณ์์ ๊ตฌํ)
4. CSS ์คํ์ผ ์ถ๊ฐ (`<style>` ๋ธ๋ก์ `<main>` ์์ ์ถ๊ฐ):
- `#card-deck`: `display: flex; flex-wrap: wrap; justify-content: center; gap: 16px; margin: 32px 0;`
- `.tarot-card`: `width: 80px; height: 120px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: var(--m-radius-lg); cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 32px; color: white; box-shadow: var(--m-shadow); transition: transform 0.2s;`
- `.tarot-card:hover`: `transform: translateY(-8px);`
5. ํ์ด์ง ๋ก๋ ์ `renderCardDeck()` ํธ์ถ
์ด ๋จ๊ณ๊ฐ ์๋ฃ๋๋ฉด 22์ฅ์ ์นด๋๊ฐ ๋ท๋ฉด ์ํ๋ก ํ๋ฉด์ ๋์ด๋๊ณ , ๋ง์ฐ์ค ์ค๋ฒ ์ ์ด์ง ๋ ์ค๋ฅด๋ ํจ๊ณผ๊ฐ ์ ์ฉ๋ฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ํ๋ก ์นด๋ 22์ฅ์ด ๋ณด๋ผ์ ๊ทธ๋ผ๋ฐ์ด์ ๋ฐฐ๊ฒฝ์ ๋ท๋ฉด ์ํ๋ก ํ๋ฉด์ ํ์๋ฉ๋๋ค. ์นด๋์ ๋ง์ฐ์ค๋ฅผ ์ฌ๋ฆฌ๋ฉด ์ด์ง ์๋ก ๋ ์ค๋ฅด๋ hover ํจ๊ณผ๊ฐ ์ ์ฉ๋ฉ๋๋ค. ์์ง ํด๋ฆญ ์ด๋ฒคํธ๋ ๋์ํ์ง ์์ต๋๋ค.
**Step 3: ์นด๋ ์ ํ ๋ฐ ๋ค์ง๊ธฐ ์ ๋๋ฉ์ด์
๊ตฌํ**
์ฌ์ฉ์๊ฐ ์นด๋๋ฅผ ํด๋ฆญํ๋ฉด ์นด๋๊ฐ ๋ค์งํ๋ฉด์ ์๋ฉด(์นด๋ ์ด๋ฆ)์ด ๋ํ๋๊ณ , ์ด์ธ ๊ฒฐ๊ณผ๊ฐ ํ์๋๋๋ก ๊ตฌํํ์ธ์.
์๊ตฌ์ฌํญ:
1. ์นด๋ ํด๋ฆญ ์ด๋ฒคํธ ํธ๋ค๋ฌ ์์ฑ:
- `handleCardClick(event)` ํจ์ ์ ์
- ํด๋ฆญ๋ ์นด๋์ `data-card-id`๋ฅผ ๊ฐ์ ธ์์ ํด๋น ์นด๋ ๋ฐ์ดํฐ ์ฐพ๊ธฐ
- ์นด๋ ๋ค์ง๊ธฐ ์ ๋๋ฉ์ด์
ํธ๋ฆฌ๊ฑฐ
- ๊ฒฐ๊ณผ ์์ญ์ ์ด์ธ ๋ฉ์์ง ํ์
- ํ ๋ฒ ์นด๋๋ฅผ ์ ํํ๋ฉด ๋ค๋ฅธ ์นด๋ ํด๋ฆญ ๋นํ์ฑํ (์ด๋ฏธ ๋ฝ์์)
2. ์นด๋ ๋ค์ง๊ธฐ ์ ๋๋ฉ์ด์
:
- CSS `transform: rotateY(180deg)` ์ฌ์ฉ
- `.tarot-card.flipped` ํด๋์ค ์ถ๊ฐ ์ ๋ค์งํ
- ์ ๋๋ฉ์ด์
์ง์์๊ฐ: 0.6s
- ๋ค์งํ ํ ์นด๋ ์๋ฉด์๋ ์นด๋ ์ด๋ฆ ํ์ (ํฐ์ ๋ฐฐ๊ฒฝ + ๊ฒ์์ ํ
์คํธ)
3. CSS ์คํ์ผ ์ถ๊ฐ:
- `.tarot-card`: `transform-style: preserve-3d; transition: transform 0.6s;`
- `.tarot-card.flipped`: `transform: rotateY(180deg);`
- ์นด๋ ์๋ฉด/๋ท๋ฉด์ ์ํ `.card-front`, `.card-back` ์์ ์ถ๊ฐ (์ ๋ ์์น, `backface-visibility: hidden;`)
- `.card-front`: `transform: rotateY(180deg);` (์ด๊ธฐ ์ํ์์ ๋ค์งํ ์์)
- `.card-back`: ๊ธฐ์กด ๋ณด๋ผ์ ๊ทธ๋ผ๋ฐ์ด์
์ ์ง
4. ๊ฒฐ๊ณผ ์์ญ ํ์:
- `#result` ์์์ `display: none;`์ `display: block;`์ผ๋ก ๋ณ๊ฒฝ
- ๊ฒฐ๊ณผ ์์ญ์ ๋ค์ ์ ๋ณด ํ์:
- ์ ํํ ์นด๋ ์ด๋ฆ (ํฐ ์ ๋ชฉ)
- ํค์๋ (์์ ๋ฐฐ์ง ํํ)
- ์ด์ธ ๋ฉ์์ง (๋ณธ๋ฌธ ํ
์คํธ)
- "๋ค์ ๋ฝ๊ธฐ" ๋ฒํผ (ํ์ด์ง ์๋ก๊ณ ์นจ)
5. ๊ฒฐ๊ณผ ์์ญ ์คํ์ผ:
- ์ค์ ์ ๋ ฌ, ์๋จ ๋ง์ง
- ์นด๋ ์ด๋ฆ: ํฐ ํฐํธ, ๊ตต๊ฒ
- ํค์๋: ์์ ๋ฐฐ์ง (`background: var(--m-primary); color: white; padding: 4px 12px; border-radius: 12px;`)
- ๋ฉ์์ง: ์ผ๋ฐ ํ
์คํธ, ์ค๊ฐ๊ฒฉ ๋๊ฒ
- "๋ค์ ๋ฝ๊ธฐ" ๋ฒํผ: `background: var(--m-primary); color: white; padding: 12px 24px; border: none; border-radius: var(--m-radius-lg); cursor: pointer;`
์ด ๋จ๊ณ๊ฐ ์๋ฃ๋๋ฉด ์นด๋ ํด๋ฆญ ์ ๋ค์งํ๋ฉด์ ์ด์ธ ๋ฉ์์ง๊ฐ ๋ํ๋๊ณ , "๋ค์ ๋ฝ๊ธฐ" ๋ฒํผ์ผ๋ก ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํ ์ ์์ต๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ์นด๋๋ฅผ ํด๋ฆญํ๋ฉด 0.6์ด๊ฐ ๋ค์ง๊ธฐ ์ ๋๋ฉ์ด์ ์ด ์ฌ์๋๋ฉฐ ์นด๋ ์๋ฉด(์ด๋ฆ)์ด ๋ํ๋ฉ๋๋ค. ๋์์ ํ๋จ์ ์ ํํ ์นด๋์ ํค์๋์ ์ด์ธ ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค. ํ ๋ฒ ์นด๋๋ฅผ ์ ํํ๋ฉด ๋ค๋ฅธ ์นด๋๋ ํด๋ฆญํ ์ ์์ผ๋ฉฐ, "๋ค์ ๋ฝ๊ธฐ" ๋ฒํผ์ ํด๋ฆญํ๋ฉด ํ์ด์ง๊ฐ ์๋ก๊ณ ์นจ๋ฉ๋๋ค. ๋คํฌ๋ชจ๋์์๋ ๊ฒฐ๊ณผ ์์ญ์ด ์์ฐ์ค๋ฝ๊ฒ ํ์๋ฉ๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!