๐ฏ g5se ์ค๋์ ์์ ์ฌ๋ฆฌ ํ ์คํธ - ๋๋ฅผ ํํํ๋ ์ปฌ๋ฌ ์ฐพ๊ธฐ
g5se-color-psychology-quiz
๋ฌธ์ ์ค๋ช
๐จ ํ๋ก์ ํธ ๊ฐ์
์ฌ์ฉ์๊ฐ ๊ฐ๋จํ ์ง๋ฌธ์ ๋ตํ๋ฉด ์ฑ๊ฒฉ์ ๋ง๋ ์์์ ์ถ์ฒํด์ฃผ๋ ์ฌ๋ฆฌ ํ ์คํธ ํ์ด์ง์ ๋๋ค. g5se์ modern shell์ ํ์ฉํ์ฌ ์ฌ์ดํธ ์ ์ฒด ๋์์ธ๊ณผ ์ผ๊ด์ฑ์ ์ ์งํ๋ฉฐ, ๋คํฌ๋ชจ๋์์๋ ์๋ฒฝํ๊ฒ ์๋ํฉ๋๋ค.
โจ ํ์ ๊ธฐ๋ฅ
- 5๊ฐ์ง ์ฌ๋ฆฌ ์ง๋ฌธ: ์ฑ๊ฒฉ, ์ ํธ๋, ๊ธฐ๋ถ ๋ฑ์ ๋ฌป๋ ๊ฐ๊ด์ ์ง๋ฌธ (๊ฐ 4๊ฐ ์ ํ์ง)
- ๊ฒฐ๊ณผ ํ์ด์ง: ์ ํ์ ๋ฐ๋ผ ์ถ์ฒ ์์ ํ์ (์์ ๊ฒฌ๋ณธ + ์ฌ๋ฆฌ ํด์ ํ ์คํธ)
- ๊ฒฐ๊ณผ ๊ณต์ ํ๊ธฐ: ๊ฒฐ๊ณผ URL ๋ณต์ฌ ๋ฒํผ (์ฟผ๋ฆฌ์คํธ๋ง์ผ๋ก ๊ฒฐ๊ณผ ์ ๋ฌ)
- ๋ค์ ํ๊ธฐ ๋ฒํผ: ํ ์คํธ ์ด๊ธฐํ
- ๊ฒฐ๊ณผ ํ์คํ ๋ฆฌ: SQLite์ ํ ์คํธ ๊ฒฐ๊ณผ ์ ์ฅ (์์๋ช , ํ์์คํฌํ, IP)
- ์ธ๊ธฐ ์์ ์์: ๊ฐ์ฅ ๋ง์ด ๋์จ ๊ฒฐ๊ณผ TOP 3 ํ์ (๋ฉ์ธ ํ์ด์ง ํ๋จ)
๐ ์ ํ ์ถ๊ฐ ๊ธฐ๋ฅ (๋ณด๋์ค)
- ๊ฒฐ๊ณผ ์์์ผ๋ก ๊ทธ๋ผ๋ฐ์ด์ ๋ฐฐ๊ฒฝ ์ ๋๋ฉ์ด์ ์ ์ฉ
- SNS ๊ณต์ ๋ฉํํ๊ทธ (Open Graph) ์ถ๊ฐ
- ๊ฒฐ๊ณผ ์ด๋ฏธ์ง ๋ค์ด๋ก๋ ๊ธฐ๋ฅ (HTML2Canvas)
- ์์๋ณ ์ด์ธ๋ฆฌ๋ ์์ ์ฅ๋ฅด ์ถ์ฒ
- ๊ณ์ ๋ณ ์ถ์ฒ ์์ ํํฐ๋ง
๐ฏ ํ์ต ๋ชฉํ
- g5se ํด๋ ๊ธฐ๋ฐ ๋ผ์ฐํ
์ดํด (
app/modules/color-quiz/index.php) - modern shell ์ ์ฉ์ผ๋ก ์ฌ์ดํธ ์ผ๊ด์ฑ ์ ์ง
- SQLite๋ฅผ ํ์ฉํ ๊ฐ๋จํ ๋ฐ์ดํฐ ์ ์ฅ/์กฐํ
- PHP ์กฐ๊ฑด๋ฌธ์ผ๋ก ์ง๋ฌธ-๊ฒฐ๊ณผ ๋ก์ง ๊ตฌํ
- CSS ๋ณ์๋ฅผ ํ์ฉํ ๋คํฌ๋ชจ๋ ๋์
- ์ฟผ๋ฆฌ์คํธ๋ง ๊ธฐ๋ฐ ๊ฒฐ๊ณผ ๊ณต์ ๊ตฌํ
์ ์ฝ ์กฐ๊ฑด
- โข g5se ์ค์น ํ์ (https://github.com/gnuboard/g5se main ๋ธ๋์น)
- โข app/modules/color-quiz/index.php ๋จ์ผ ํ์ผ๋ก ๊ตฌํ
- โข modern shell (_head.inc.php + m-shell + _nav.inc.php + m-container + _footer.inc.php) ํ์ ์ฌ์ฉ
- โข SQLite DB๋ G5_DATA_PATH.'/modules/color-quiz.sqlite'์ ์ ์ฅ
- โข ๋ผ์ฐํธ ์๋ ๋ฑ๋ก ๊ธ์ง (ํด๋ ์๋ ๋งคํ ํ์ฉ)
- โข ์ธ๋ถ ๋น๋ ๋๊ตฌ(npm/webpack) ์ฌ์ฉ ๊ธ์ง
- โข CDN ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ ์ต์ํ (์์ HTML/CSS/JS ๊ถ์ฅ)
- โข ๋คํฌ๋ชจ๋ ๋์์ ์ํด var(--m-surface), var(--m-text) ๋ฑ CSS ๋ณ์ ์ฌ์ฉ
- โข XSS ๋ฐฉ์ง๋ฅผ ์ํด htmlspecialchars() ํ์ ์ ์ฉ
- โข prepared statement๋ก SQL ์ธ์ ์ ๋ฐฉ์ง
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
g5se๊ฐ ์ค์น๋ ๋๋ ํฐ๋ฆฌ(๋ฃจํธ์ `index.php`ยท`app/`์ด ์๋ ๊ณณ)์์ ์์
์ ์์ํฉ๋๋ค. (g5se ์ค์น๋ https://github.com/gnuboard/g5se ์ฐธ๊ณ )
**1๋จ๊ณ: ์์ ์ฌ๋ฆฌ ํ
์คํธ ํ์ด์ง ๊ธฐ๋ณธ ๊ณจ๊ฒฉ ์์ฑ**
`app/modules/color-quiz/index.php` ํ์ผ์ ์์ฑํ๊ณ ๋ค์ ๋ด์ฉ์ ์์ฑํ์ธ์:
```php
<?php
include_once('./_common.php');
// SQLite DB ์ด๊ธฐํ
$db_dir = G5_DATA_PATH.'/modules';
@mkdir($db_dir, 0707, true);
$db_path = $db_dir.'/color-quiz.sqlite';
$pdo = new PDO('sqlite:'.$db_path);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// ํ
์ด๋ธ ์์ฑ (์ต์ด 1ํ)
$pdo->exec("
CREATE TABLE IF NOT EXISTS quiz_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
color_name TEXT NOT NULL,
ip_address TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
");
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;">
<div style="max-width: 700px; margin: 0 auto;">
<h1 style="font-size: 2rem; font-weight: 700; margin-bottom: 12px; color: var(--m-text);">๐จ ์ค๋์ ์์ ์ฌ๋ฆฌ ํ
์คํธ</h1>
<p style="color: var(--m-text-muted); margin-bottom: 32px;">5๊ฐ์ง ์ง๋ฌธ์ผ๋ก ๋๋ฅผ ํํํ๋ ์์์ ์ฐพ์๋ณด์ธ์!</p>
<div id="quiz-container">
<!-- ์ง๋ฌธ์ด ์ฌ๊ธฐ์ ๋์ ์ผ๋ก ๋ ๋๋ง๋ฉ๋๋ค -->
</div>
<div id="result-container" style="display: none;">
<!-- ๊ฒฐ๊ณผ๊ฐ ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค -->
</div>
<div style="margin-top: 48px; padding: 24px; background: var(--m-surface); border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow);">
<h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 16px; color: var(--m-text);">๐ ์ธ๊ธฐ ์์ ์์</h3>
<div id="popular-colors">
<!-- ์ธ๊ธฐ ์์ TOP 3์ด ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค -->
</div>
</div>
</div>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```
์ด ํ์ผ์ ์ ์ฅํ๊ณ ๋ธ๋ผ์ฐ์ ์์ `/color-quiz`๋ก ์ ์ํ์ฌ ํ์ด์ง๊ฐ ์ ์์ ์ผ๋ก ๋ก๋๋๋์ง ํ์ธํ์ธ์. modern shell์ด ์ ์ฉ๋์ด ๋ค๋น๊ฒ์ด์
, ํธํฐ, ๋คํฌ๋ชจ๋ ํ ๊ธ์ด ์๋์ผ๋ก ํ์๋์ด์ผ ํฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: - `/color-quiz` URL๋ก ์ ๊ทผ ๊ฐ๋ฅ - modern shell์ด ์ ์ฉ๋์ด ์ฌ์ดํธ ์ ์ฒด ๋์์ธ๊ณผ ์ผ๊ด์ฑ ์ ์ง - SQLite DB ํ์ผ์ด `data/modules/color-quiz.sqlite`์ ์๋ ์์ฑ - quiz_results ํ ์ด๋ธ์ด ์๋ ์์ฑ - ํ์ด์ง ์ ๋ชฉ๊ณผ ์ค๋ช ์ด ํ์๋จ - ๋คํฌ๋ชจ๋ ์ ํ ์ ๋ฐฐ๊ฒฝ/ํ ์คํธ ์์์ด ์๋์ผ๋ก ๋ณ๊ฒฝ๋จ
**2๋จ๊ณ: ์ง๋ฌธ ๋ฐ์ดํฐ ๊ตฌ์กฐ ๋ฐ ๋ ๋๋ง ๋ก์ง ๊ตฌํ**
`app/modules/color-quiz/index.php` ํ์ผ์ ์๋จ(SQLite ์ด๊ธฐํ ์งํ)์ ์ง๋ฌธ ๋ฐฐ์ด์ ์ถ๊ฐํ๊ณ , ํ๋จ์ JavaScript๋ก ์ง๋ฌธ์ ๋์ ๋ ๋๋งํ๋ ์ฝ๋๋ฅผ ์์ฑํ์ธ์:
PHP ๋ถ๋ถ (ํ์ผ ์๋จ, `$pdo->exec()` ์งํ์ ์ถ๊ฐ):
```php
// ์ง๋ฌธ ๋ฐ์ดํฐ (5๊ฐ ์ง๋ฌธ, ๊ฐ 4๊ฐ ์ ํ์ง)
$questions = [
[
'id' => 1,
'question' => '์ฃผ๋ง์ ๊ฐ์ฅ ํ๊ณ ์ถ์ ํ๋์?',
'options' => [
['text' => '์กฐ์ฉํ ์นดํ์์ ์ฑ
์ฝ๊ธฐ', 'point' => 'blue'],
['text' => '์น๊ตฌ๋ค๊ณผ ํ๊ธฐ์ฐฌ ํํฐ', 'point' => 'red'],
['text' => '์์ฐ ์ ์ฐ์ฑ
', 'point' => 'green'],
['text' => '์ง์์ ์ํ ๋ง๋ผํค', 'point' => 'purple']
]
],
[
'id' => 2,
'question' => '์คํธ๋ ์ค ๋ฐ์ ๋ ์ด๋ป๊ฒ ํด์ํ๋์?',
'options' => [
['text' => '์ด๋์ผ๋ก ๋ ํ๋ฆฌ๊ธฐ', 'point' => 'red'],
['text' => '๋ช
์์ด๋ ์๊ฐ', 'point' => 'blue'],
['text' => '๋ง์๋ ์์ ๋จน๊ธฐ', 'point' => 'orange'],
['text' => '์์
๋ฃ๊ธฐ', 'point' => 'purple']
]
],
[
'id' => 3,
'question' => '๊ฐ์ฅ ์ข์ํ๋ ๊ณ์ ์?',
'options' => [
['text' => '๋ด (๋ฐ๋ปํ๊ณ ํ์ฌํ)', 'point' => 'pink'],
['text' => '์ฌ๋ฆ (๋จ๊ฒ๊ณ ์ด์ ์ ์ธ)', 'point' => 'orange'],
['text' => '๊ฐ์ (์ฐจ๋ถํ๊ณ ๊ฐ์ฑ์ ์ธ)', 'point' => 'brown'],
['text' => '๊ฒจ์ธ (๊ณ ์ํ๊ณ ํํ๋ก์ด)', 'point' => 'blue']
]
],
[
'id' => 4,
'question' => '์น๊ตฌ๋ค์ด ๋๋ฅผ ์ด๋ป๊ฒ ํํํ ๊น์?',
'options' => [
['text' => '๋ฐ๊ณ ๊ธ์ ์ ์ธ ์ฌ๋', 'point' => 'yellow'],
['text' => '์นจ์ฐฉํ๊ณ ์ ์คํ ์ฌ๋', 'point' => 'blue'],
['text' => '์ด์ ์ ์ด๊ณ ํ๋ฐํ ์ฌ๋', 'point' => 'red'],
['text' => '์ฐฝ์์ ์ด๊ณ ๋
ํนํ ์ฌ๋', 'point' => 'purple']
]
],
[
'id' => 5,
'question' => '์ด์์ ์ธ ํด๊ฐ์ง๋?',
'options' => [
['text' => 'ํ์ ํ ์๊ณจ ๋ง์', 'point' => 'green'],
['text' => '๋ถ์ ์ด๋ ๋๋์', 'point' => 'red'],
['text' => '๊ณ ์ํ ํด๋ณ', 'point' => 'blue'],
['text' => '์ญ์ฌ์ ์ธ ์ ์ ์ง', 'point' => 'brown']
]
]
];
// ์์๋ณ ๊ฒฐ๊ณผ ๋ฐ์ดํฐ
$color_results = [
'blue' => ['name' => '๋ธ๋ฃจ (ํํ)', 'hex' => '#4A90E2', 'desc' => '์ฐจ๋ถํ๊ณ ์ ์คํ ๋น์ ์ ํํ๋ก์ด ๋ธ๋ฃจ์
๋๋ค. ๊น์ ์๊ฐ๊ณผ ํต์ฐฐ๋ ฅ์ ๊ฐ์ง ์ฌ์๊ฐ ํ์
์ด์์.'],
'red' => ['name' => '๋ ๋ (์ด์ )', 'hex' => '#E74C3C', 'desc' => '์๋์ง ๋์น๊ณ ํ๋ฐํ ๋น์ ์ ์ด์ ์ ์ธ ๋ ๋์
๋๋ค. ๋์ ์ ์ฆ๊ธฐ๊ณ ๋ฆฌ๋์ญ์ด ๊ฐํ ํ์
์ด์์.'],
'green' => ['name' => '๊ทธ๋ฆฐ (์์ฐ)', 'hex' => '#27AE60', 'desc' => '์์ฐ์ ์ฌ๋ํ๊ณ ๊ท ํ์ ์ค์ํ๋ ๋น์ ์ ๊ทธ๋ฆฐ์
๋๋ค. ์กฐํ๋กญ๊ณ ์์ ์ ์ธ ์ฑ๊ฒฉ์ ์์ ์์์.'],
'purple' => ['name' => 'ํผํ (์ฐฝ์)', 'hex' => '#9B59B6', 'desc' => '์ฐฝ์์ ์ด๊ณ ๋
์ฐฝ์ ์ธ ๋น์ ์ ์ ๋น๋ก์ด ํผํ์
๋๋ค. ์์ ์ ๊ฐ๊ฐ๊ณผ ์์๋ ฅ์ด ํ๋ถํ ํ์
์ด์์.'],
'yellow' => ['name' => '์๋ก์ฐ (ํ๊ธฐ)', 'hex' => '#F39C12', 'desc' => '๋ฐ๊ณ ๊ธ์ ์ ์ธ ๋น์ ์ ํ๋ณตํ ์๋ก์ฐ์
๋๋ค. ์ฃผ๋ณ์ ์ฆ๊ฒ๊ฒ ๋ง๋๋ ์๋์ง๋ฅผ ๊ฐ์ง ํ์
์ด์์.'],
'orange' => ['name' => '์ค๋ ์ง (์ด์ )', 'hex' => '#E67E22', 'desc' => '๋ฐ๋ปํ๊ณ ์ฌ๊ต์ ์ธ ๋น์ ์ ํ๊ธฐ์ฐฌ ์ค๋ ์ง์
๋๋ค. ์ฌ๋๋ค๊ณผ ์ด์ธ๋ฆฌ๊ธธ ์ข์ํ๋ ํ์
์ด์์.'],
'pink' => ['name' => 'ํํฌ (์จํ)', 'hex' => '#EC87C0', 'desc' => '๋ถ๋๋ฝ๊ณ ๋ฐฐ๋ ค์ฌ ๊น์ ๋น์ ์ ์ฌ๋์ค๋ฌ์ด ํํฌ์
๋๋ค. ๊ฐ์ฑ์ ์ด๊ณ ๋ฐ๋ปํ ์ฑ๊ฒฉ์ ์์ ์์์.'],
'brown' => ['name' => '๋ธ๋ผ์ด (์์ )', 'hex' => '#8B6F47', 'desc' => 'ํ์ค์ ์ด๊ณ ๋ฏฟ์์งํ ๋น์ ์ ์์ ์ ์ธ ๋ธ๋ผ์ด์
๋๋ค. ์ฑ
์๊ฐ์ด ๊ฐํ๊ณ ์ ๋ขฐํ ์ ์๋ ํ์
์ด์์.']
];
// JSON์ผ๋ก ์ ๋ฌ
$questions_json = json_encode($questions, JSON_UNESCAPED_UNICODE);
$colors_json = json_encode($color_results, JSON_UNESCAPED_UNICODE);
```
JavaScript ๋ถ๋ถ (ํ์ผ ํ๋จ, `</main>` ์ง์ ์ ์ถ๊ฐ):
```php
<script>
const questions = <?= $questions_json ?>;
const colorResults = <?= $colors_json ?>;
let currentQuestion = 0;
let answers = [];
function renderQuestion() {
const container = document.getElementById('quiz-container');
const q = questions[currentQuestion];
container.innerHTML = `
<div style="background: var(--m-surface); padding: 32px; border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow);">
<div style="margin-bottom: 24px;">
<span style="color: var(--m-text-muted); font-size: 0.875rem;">์ง๋ฌธ ${currentQuestion + 1} / ${questions.length}</span>
<div style="width: 100%; height: 4px; background: var(--m-text-muted); opacity: 0.2; border-radius: 2px; margin-top: 8px;">
<div style="width: ${((currentQuestion + 1) / questions.length) * 100}%; height: 100%; background: #4A90E2; border-radius: 2px; transition: width 0.3s;"></div>
</div>
</div>
<h2 style="font-size: 1.5rem; font-weight: 600; margin-bottom: 24px; color: var(--m-text);">${q.question}</h2>
<div style="display: grid; gap: 12px;">
${q.options.map((opt, idx) => `
<button onclick="selectAnswer('${opt.point}')"
style="padding: 16px 20px; background: var(--m-surface); border: 2px solid var(--m-text-muted); opacity: 0.3; border-radius: 8px; text-align: left; cursor: pointer; transition: all 0.2s; color: var(--m-text); font-size: 1rem;"
onmouseover="this.style.borderColor='#4A90E2'; this.style.transform='translateX(4px)';"
onmouseout="this.style.borderColor='var(--m-text-muted)'; this.style.opacity='0.3'; this.style.transform='translateX(0)';">
${opt.text}
</button>
`).join('')}
</div>
</div>
`;
}
function selectAnswer(point) {
answers.push(point);
currentQuestion++;
if (currentQuestion < questions.length) {
renderQuestion();
} else {
showResult();
}
}
function showResult() {
// ๊ฐ์ฅ ๋ง์ด ์ ํ๋ ์์ ์ฐพ๊ธฐ
const count = {};
answers.forEach(c => count[c] = (count[c] || 0) + 1);
const resultColor = Object.keys(count).reduce((a, b) => count[a] > count[b] ? a : b);
const result = colorResults[resultColor];
document.getElementById('quiz-container').style.display = 'none';
const resultContainer = document.getElementById('result-container');
resultContainer.style.display = 'block';
resultContainer.innerHTML = `
<div style="background: var(--m-surface); padding: 40px; border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow); text-align: center;">
<div style="width: 120px; height: 120px; background: ${result.hex}; border-radius: 50%; margin: 0 auto 24px; box-shadow: 0 8px 24px rgba(0,0,0,0.15);"></div>
<h2 style="font-size: 2rem; font-weight: 700; margin-bottom: 12px; color: var(--m-text);">${result.name}</h2>
<p style="font-size: 1.125rem; color: var(--m-text-muted); line-height: 1.6; margin-bottom: 32px;">${result.desc}</p>
<div style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">
<button onclick="copyResult()" style="padding: 12px 24px; background: #4A90E2; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 600;">๐ ๊ฒฐ๊ณผ ๊ณต์ ํ๊ธฐ</button>
<button onclick="location.reload()" style="padding: 12px 24px; background: var(--m-surface); color: var(--m-text); border: 2px solid var(--m-text-muted); opacity: 0.5; border-radius: 8px; cursor: pointer; font-weight: 600;">๐ ๋ค์ ํ๊ธฐ</button>
</div>
</div>
`;
// ๊ฒฐ๊ณผ ์ ์ฅ
fetch('/color-quiz?action=save', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({color: resultColor})
});
}
function copyResult() {
const url = window.location.origin + window.location.pathname + '?result=' + answers.join(',');
navigator.clipboard.writeText(url).then(() => {
alert('โ
๊ฒฐ๊ณผ URL์ด ๋ณต์ฌ๋์์ต๋๋ค!');
});
}
// ์ด๊ธฐ ๋ ๋๋ง
renderQuestion();
</script>
```
์ ์ฅ ํ ๋ธ๋ผ์ฐ์ ์์ `/color-quiz`๋ฅผ ์๋ก๊ณ ์นจํ์ฌ ์ง๋ฌธ์ด ์์ฐจ์ ์ผ๋ก ํ์๋๋์ง ํ์ธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: - 5๊ฐ์ ์ง๋ฌธ์ด ์์ฐจ์ ์ผ๋ก ํ์๋จ - ๊ฐ ์ง๋ฌธ๋ง๋ค 4๊ฐ์ ์ ํ์ง ๋ฒํผ์ด ํ์๋จ - ์งํ๋ฅ ๋ฐ๊ฐ ์๋จ์ ํ์๋๊ณ ์ง๋ฌธ์ด ์งํ๋ ์๋ก ์ฑ์์ง - ๋ฒํผ์ ๋ง์ฐ์ค ์ค๋ฒ ์ ํธ๋ฒ ํจ๊ณผ ์๋ - ๋ชจ๋ ์ง๋ฌธ์ ๋ตํ๋ฉด ๊ฒฐ๊ณผ ํ๋ฉด์ผ๋ก ์ ํ๋จ - ๊ฒฐ๊ณผ ํ๋ฉด์ ์์ ์ํ, ์์๋ช , ์ฌ๋ฆฌ ํด์์ด ํ์๋จ - '๊ฒฐ๊ณผ ๊ณต์ ํ๊ธฐ' ๋ฒํผ ํด๋ฆญ ์ URL์ด ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ๋จ - '๋ค์ ํ๊ธฐ' ๋ฒํผ ํด๋ฆญ ์ ํ์ด์ง ์๋ก๊ณ ์นจ๋จ
**3๋จ๊ณ: ๊ฒฐ๊ณผ ์ ์ฅ ๋ฐ ์ธ๊ธฐ ์์ ์์ ํ์**
`app/modules/color-quiz/index.php` ํ์ผ์ ์๋จ(์ง๋ฌธ ๋ฐฐ์ด ์ ์ ์ด์ )์ AJAX ์์ฒญ ์ฒ๋ฆฌ ๋ก์ง์ ์ถ๊ฐํ๊ณ , ํ๋จ์ ์ธ๊ธฐ ์์ ์์๋ฅผ ๋ ๋๋งํ๋ ์ฝ๋๋ฅผ ์์ฑํ์ธ์:
PHP ๋ถ๋ถ (ํ์ผ ์๋จ, `$pdo->exec()` ์งํ์ ์ถ๊ฐ):
```php
// AJAX ์์ฒญ ์ฒ๋ฆฌ: ๊ฒฐ๊ณผ ์ ์ฅ
if (isset($_GET['action']) && $_GET['action'] === 'save') {
$input = json_decode(file_get_contents('php://input'), true);
if (isset($input['color'])) {
$color = htmlspecialchars($input['color']);
$ip = $_SERVER['REMOTE_ADDR'];
$stmt = $pdo->prepare("INSERT INTO quiz_results (color_name, ip_address) VALUES (?, ?)");
$stmt->execute([$color, $ip]);
echo json_encode(['success' => true]);
exit;
}
}
// AJAX ์์ฒญ ์ฒ๋ฆฌ: ์ธ๊ธฐ ์์ ์กฐํ
if (isset($_GET['action']) && $_GET['action'] === 'popular') {
$stmt = $pdo->query("
SELECT color_name, COUNT(*) as count
FROM quiz_results
GROUP BY color_name
ORDER BY count DESC
LIMIT 3
");
$popular = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($popular);
exit;
}
```
JavaScript ๋ถ๋ถ (ํ์ผ ํ๋จ, ๊ธฐ์กด ์คํฌ๋ฆฝํธ ๋์ ์ถ๊ฐ):
```javascript
// ์ธ๊ธฐ ์์ ์์ ๋ก๋
function loadPopularColors() {
fetch('/color-quiz?action=popular')
.then(r => r.json())
.then(data => {
const container = document.getElementById('popular-colors');
if (data.length === 0) {
container.innerHTML = '<p style="color: var(--m-text-muted); text-align: center;">์์ง ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ์ฐธ์ฌ์๊ฐ ๋์ด๋ณด์ธ์!</p>';
return;
}
container.innerHTML = data.map((item, idx) => {
const colorInfo = colorResults[item.color_name];
if (!colorInfo) return '';
const medals = ['๐ฅ', '๐ฅ', '๐ฅ'];
return `
<div style="display: flex; align-items: center; gap: 16px; padding: 16px; background: var(--m-surface); border-radius: 8px; margin-bottom: 12px; border: 2px solid var(--m-text-muted); opacity: 0.2;">
<span style="font-size: 2rem;">${medals[idx]}</span>
<div style="width: 40px; height: 40px; background: ${colorInfo.hex}; border-radius: 50%; flex-shrink: 0;"></div>
<div style="flex: 1;">
<div style="font-weight: 600; color: var(--m-text);">${colorInfo.name}</div>
<div style="font-size: 0.875rem; color: var(--m-text-muted);">${item.count}๋ช
์ด ์ ํ</div>
</div>
</div>
`;
}).join('');
});
}
// ํ์ด์ง ๋ก๋ ์ ์ธ๊ธฐ ์์ ๋ก๋
loadPopularColors();
```
์ ์ฅ ํ ๋ธ๋ผ์ฐ์ ์์ `/color-quiz`๋ฅผ ์๋ก๊ณ ์นจํ๊ณ ํ
์คํธ๋ฅผ ์๋ฃํ์ฌ ๊ฒฐ๊ณผ๊ฐ ์ ์ฅ๋๋์ง, ์ธ๊ธฐ ์์ ์์๊ฐ ์
๋ฐ์ดํธ๋๋์ง ํ์ธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: - ํ ์คํธ ์๋ฃ ์ ๊ฒฐ๊ณผ๊ฐ SQLite DB์ ์๋ ์ ์ฅ๋จ (IP ์ฃผ์์ ํจ๊ป) - ํ์ด์ง ํ๋จ์ '์ธ๊ธฐ ์์ ์์' ์น์ ์ด ํ์๋จ - TOP 3 ์์์ด ๋ฉ๋ฌ(๐ฅ๐ฅ๐ฅ)๊ณผ ํจ๊ป ํ์๋จ - ๊ฐ ์์๋ง๋ค ์์ ์ํ, ์ด๋ฆ, ์ ํ ํ์๊ฐ ํ์๋จ - ์์ง ๊ฒฐ๊ณผ๊ฐ ์์ ๊ฒฝ์ฐ '์์ง ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค' ๋ฉ์์ง ํ์ - ์ฌ๋ฌ ๋ฒ ํ ์คํธ๋ฅผ ์๋ฃํ๋ฉด ์์๊ฐ ์ค์๊ฐ์ผ๋ก ์ ๋ฐ์ดํธ๋จ - ๋คํฌ๋ชจ๋ ์ ํ ์์๋ ๋ชจ๋ ์์๊ฐ ์ ์ ์๋ํจ
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!