<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
$db_file = "F:\\ratings_data\\stories.db";
$db = new PDO("sqlite:" . $db_file);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec("PRAGMA foreign_keys = ON;");
$story_id = intval($_GET['story_id'] ?? 0);
// Fetch current story and creator metadata configurations
$stmt = $db->prepare("SELECT s.*, c.name AS creator_name FROM stories s JOIN creators c ON s.creator_id = c.id WHERE s.id = ?");
$stmt->execute([$story_id]);
$story = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$story) {
die("Story execution error: Invalid story instance ID requested.");
}
$creator_id = $story['creator_id'];
// AJAX API Processing Controller Route Space
if (isset($_GET['ajax_action'])) {
header('Content-Type: application/json');
$action = $_GET['ajax_action'];
if ($action === 'add_character') {
$stmt = $db->prepare("INSERT INTO characters (story_id, gender_id, sexuality_id, species_id, age_id, hair_color_id, eye_color_id, height_id, weight_id) VALUES (?,1,1,1,1,1,1,1,1)");
$stmt->execute([$story_id]);
echo json_encode(['status' => 'success']);
exit;
}
if ($action === 'update_field') {
$char_id = intval($_GET['char_id']);
$field = preg_replace('/[^a-z0-9_]/', '', $_GET['field']);
$value = $_GET['value'];
$stmt = $db->prepare("UPDATE characters SET {$field} = ? WHERE id = ? AND story_id = ?");
$stmt->execute([$value, $char_id, $story_id]);
echo json_encode(['status' => 'success']);
exit;
}
if ($action === 'add_other') {
$type = preg_replace('/[^a-z_]/', '', $_GET['type']);
$table = ($type === 'species') ? 'species' : $type . 's';
$value = trim($_GET['value']);
$db->prepare("INSERT OR IGNORE INTO {$table} (name) VALUES (?)")->execute([$value]);
$stmt = $db->prepare("SELECT id FROM {$table} WHERE name = ?");
$stmt->execute([$value]); $item_id = $stmt->fetchColumn();
$singular = ($type === 'species') ? 'species' : rtrim($type, 's');
$db->prepare("INSERT OR IGNORE INTO creator_{$table} (creator_id, {$singular}_id) VALUES (?,?)")->execute([$creator_id, $item_id]);
echo json_encode(['status' => 'success']);
exit;
}
if ($action === 'update_relationship') {
$char1 = intval($_GET['char1']); $char2 = intval($_GET['char2']); $type_id = intval($_GET['type_id']);
$db->prepare("DELETE FROM character_relationships WHERE character_id_1 = ? AND character_id_2 = ?")->execute([$char1, $char2]);
if ($type_id > 1) {
$db->prepare("INSERT INTO character_relationships (story_id, character_id_1, character_id_2, relationship_type_id) VALUES (?,?,?,?)")->execute([$story_id, $char1, $char2, $type_id]);
}
$stmt = $db->prepare("SELECT to_relationship_id FROM relationship_reciprocals WHERE from_relationship_id = ?");
$stmt->execute([$type_id]); $recip_id = $stmt->fetchColumn() ?: 1;
$db->prepare("DELETE FROM character_relationships WHERE character_id_1 = ? AND character_id_2 = ?")->execute([$char2, $char1]);
if ($recip_id > 1) {
$db->prepare("INSERT INTO character_relationships (story_id, character_id_1, character_id_2, relationship_type_id) VALUES (?,?,?,?)")->execute([$story_id, $char2, $char1, $recip_id]);
}
echo json_encode(['status' => 'success', 'recip_id' => $recip_id]);
exit;
}
if ($action === 'delete_character') {
$db->prepare("DELETE FROM characters WHERE id = ? AND story_id = ?")->execute([intval($_GET['char_id']), $story_id]);
echo json_encode(['status' => 'success']);
exit;
}
if ($action === 'update_story_title') {
$db->prepare("UPDATE stories SET title = ? WHERE id = ?")->execute([trim($_GET['title']), $story_id]);
echo json_encode(['status' => 'success']);
exit;
}
}
$getOptions = function($db, $table, $creator_id) {
$singular = ($table === 'species') ? 'species' : rtrim($table, 's');
return $db->query("SELECT DISTINCT id, name FROM {$table} s LEFT JOIN creator_{$table} c ON s.id = c.{$singular}_id AND c.creator_id = {$creator_id} WHERE c.creator_id IS NOT NULL OR s.id <= 50 ORDER BY s.id ASC")->fetchAll(PDO::FETCH_ASSOC);
};
$lookups = [
'gender_id' => $db->query("SELECT id, name FROM genders")->fetchAll(PDO::FETCH_ASSOC),
'sexuality_id' => $db->query("SELECT id, name FROM sexualities")->fetchAll(PDO::FETCH_ASSOC),
'species_id' => $getOptions($db, 'species', $creator_id),
'age_id' => $db->query("SELECT id, name FROM ages")->fetchAll(PDO::FETCH_ASSOC),
'hair_color_id' => $getOptions($db, 'hair_colors', $creator_id),
'eye_color_id' => $getOptions($db, 'eye_colors', $creator_id),
'height_id' => $db->query("SELECT id, name FROM heights")->fetchAll(PDO::FETCH_ASSOC),
'weight_id' => $db->query("SELECT id, name FROM weights")->fetchAll(PDO::FETCH_ASSOC),
'rel_types' => $db->query("SELECT id, name FROM relationship_types ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC)
];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Story Matrix Canvas Workspace</title>
<style>
body { font-family: Arial, sans-serif; background: #f0f2f5; margin: 0; padding: 20px; }
.header-panel { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; }
.title-input { font-size: 22px; font-weight: bold; border: none; border-bottom: 2px solid transparent; width: 350px; padding: 5px; }
.title-input:focus { border-bottom-color: #0056b3; outline: none; }
.btn-add { background: #166534; color: white; padding: 10px 20px; border: none; border-radius: 4px; font-weight: bold; cursor: pointer; }
.grid-container { display: flex; flex-direction: column; gap: 20px; }
.char-card { background: white; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); padding: 20px; }
.char-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; }
.form-box { display: flex; flex-direction: column; }
.form-box label { font-size: 12px; font-weight: bold; color: #555; margin-bottom: 4px; }
.form-box input, .form-box select { padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
.other-container { display: none; margin-top: 5px; }
.btn-delete { background: #dc2626; color: white; border: none; padding: 6px 12px; border-radius: 4px; font-size: 12px; cursor: pointer; float: right; }
.rel-matrix { margin-top: 25px; border-top: 2px solid #e2e8f0; padding-top: 15px; }
.rel-row { display: block; background: #f8fafc; padding: 10px 15px; border-radius: 6px; border: 1px solid #e2e8f0; margin-bottom: 8px; max-width: 600px; display: flex; align-items: center; justify-content: space-between; }
.rel-label { font-size: 14px; font-weight: 500; color: #334155; }
.rel-select { padding: 6px; border-radius: 4px; border: 1px solid #cbd5e1; width: 180px; }
</style>
</head>
<body>
<?php include 'nav.php'; ?>
<div class="header-panel">
<div>
<input type="text" class="title-input" id="storyTitle" value="<?php echo htmlspecialchars($story['title']); ?>" onblur="saveStoryTitle()">
<div style="font-size: 13px; color:#666; margin-top:5px;">Creator Mode Profile: <strong><?php echo htmlspecialchars($story['creator_name']); ?></strong></div>
</div>
<div style="display: flex; gap: 15px; align-items: center;">
<input type="text" id="canvasSearch" placeholder="🔍 Search Cast by Name..." style="padding: 8px 12px; border: 1px solid #cbd5e1; border-radius: 4px; width: 220px;" oninput="filterCharacterCards(this.value)">
<button class="btn-add" onclick="addCharacterCard()">+ Add Entry Character</button>
</div>
</div>
<div class="grid-container" id="charactersContainer">
<?php
$chars = $db->query("SELECT * FROM characters WHERE story_id = {$story_id} ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC);
$all_rels = $db->query("SELECT * FROM character_relationships WHERE story_id = {$story_id}")->fetchAll(PDO::FETCH_ASSOC);
$rel_map = [];
foreach ($all_rels as $r) { $rel_map[$r['character_id_1']][$r['character_id_2']] = $r['relationship_type_id']; }
foreach ($chars as $c):
$cid = $c['id'];
$cFirst = trim($c['first_name'] ?? ''); $cLabel = $cFirst ?: "Character";
$fullName = trim(($c['first_name'] ?? '') . ' ' . ($c['last_name'] ?? '')) ?: "Character #".$cid;
?>
<div class="char-card" id="card_<?php echo $cid; ?>">
<button class="btn-delete" onclick="deleteCharacter(<?php echo $cid; ?>)">Delete Character</button>
<h3 style="margin-top:0; color:#1e293b; margin-bottom:15px;" id="heading_<?php echo $cid; ?>"><?php echo htmlspecialchars($fullName); ?></h3>
<div class="char-fields">
<div class="form-box"><label>First Name</label><input type="text" value="<?php echo htmlspecialchars($c['first_name']); ?>" oninput="updateField(<?php echo $cid; ?>, 'first_name', this.value)"></div>
<div class="form-box"><label>Last Name</label><input type="text" value="<?php echo htmlspecialchars($c['last_name']); ?>" oninput="updateField(<?php echo $cid; ?>, 'last_name', this.value)"></div>
<div class="form-box"><label>Nickname</label><input type="text" value="<?php echo htmlspecialchars($c['nickname']); ?>" oninput="updateField(<?php echo $cid; ?>, 'nickname', this.value)"></div>
<?php
$dropdownsConfig = [
'gender_id' => 'Gender', 'sexuality_id' => 'Sexuality',
'species_id' => 'Species', 'age_id' => 'Age',
'hair_color_id' => 'Hair Color', 'eye_color_id' => 'Eye Color',
'height_id' => 'Height', 'weight_id' => 'Weight'
];
foreach ($dropdownsConfig as $fld => $lbl):
?>
<div class="form-box">
<label><?php echo $lbl; ?></label>
<select id="sel_<?php echo $fld; ?>_<?php echo $cid; ?>" onchange="handleDropdownChange(<?php echo $cid; ?>, '<?php echo $fld; ?>', this)">
<?php foreach ($lookups[$fld] as $opt): ?>
<option value="<?php echo $opt['id']; ?>" <?php echo ($c[$fld] == $opt['id']) ? 'selected' : ''; ?>><?php echo htmlspecialchars($opt['name']); ?></option>
<?php endforeach; ?>
</select>
<div class="other-container" id="other_<?php echo $fld; ?>_<?php echo $cid; ?>">
<input type="text" style="width:100%; margin-top:5px;" placeholder="Custom option..." onblur="saveCustomOther(<?php echo $cid; ?>, '<?php echo $fld; ?>', this.value)">
</div>
</div>
<?php endforeach; ?>
</div>
<div class="rel-matrix">
<strong style="color: #475569; font-size:13px; text-transform: uppercase;">Inter-Character Relationships Layout Map:</strong>
<div style="margin-top:10px;" id="rels_for_<?php echo $cid; ?>">
<?php
foreach ($chars as $target):
if ($target['id'] === $cid) continue;
$tid = $target['id'];
$tName = trim(($target['first_name'] ?? '') . ' ' . ($target['last_name'] ?? '')) ?: "Character #".$tid;
$selected_rel = $rel_map[$cid][$tid] ?? 1;
?>
<div class="rel-row">
<span class="rel-label"><strong id="lbl_tname_<?php echo $cid; ?>_<?php echo $tid; ?>"><?php echo htmlspecialchars($tName); ?></strong> is <span id="lbl_cfirst_<?php echo $cid; ?>_<?php echo $tid; ?>"><?php echo htmlspecialchars($cLabel); ?></span>'s:</span>
<select class="rel-select" data-char1="<?php echo $cid; ?>" data-char2="<?php echo $tid; ?>" onchange="saveRelationship(<?php echo $cid; ?>, <?php echo $tid; ?>, this.value)">
<?php foreach ($lookups['rel_types'] as $rt): ?>
<option value="<?php echo $rt['id']; ?>" <?php echo ($selected_rel == $rt['id']) ? 'selected' : ''; ?>><?php echo htmlspecialchars($rt['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<script>
const storyId = <?php echo $story_id; ?>;
function filterCharacterCards(searchQuery) {
const query = searchQuery.toLowerCase().trim();
const cards = document.querySelectorAll('.char-card');
cards.forEach(card => {
const firstName = card.querySelector('input[oninput*="first_name"]').value.toLowerCase();
const lastName = card.querySelector('input[oninput*="last_name"]').value.toLowerCase();
const nickname = card.querySelector('input[oninput*="nickname"]').value.toLowerCase();
if (firstName.includes(query) || lastName.includes(query) || nickname.includes(query)) {
card.style.display = 'block';
} else {
card.style.display = 'none';
}
});
}
function saveStoryTitle() { fetch(`workspace.php?story_id=${storyId}&ajax_action=update_story_title&title=${encodeURIComponent(document.getElementById('storyTitle').value.trim())}`); }
function addCharacterCard() { fetch(`workspace.php?story_id=${storyId}&ajax_action=add_character`).then(res => res.json()).then(data => { if(data.status === 'success') window.location.reload(); }); }
function deleteCharacter(charId) { if (confirm("Delete character profile?")) { fetch(`workspace.php?story_id=${storyId}&ajax_action=delete_character&char_id=${charId}`).then(res => res.json()).then(data => { if(data.status === 'success') window.location.reload(); }); } }
function updateField(charId, field, value) {
fetch(`workspace.php?story_id=${storyId}&ajax_action=update_field&char_id=${charId}&field=${field}&value=${encodeURIComponent(value)}`);
if (field === 'first_name' || field === 'last_name') {
const f = document.querySelector(`#card_${charId} input[oninput*="first_name"]`).value.trim();
const l = document.querySelector(`#card_${charId} input[oninput*="last_name"]`).value.trim();
const full = (f + ' ' + l).trim() || "Character #" + charId;
const firstLabel = f || "Character";
document.getElementById(`heading_${charId}`).innerText = full;
document.querySelectorAll(`[id^="lbl_tname_"][id$="_${charId}"]`).forEach(el => el.innerText = full);
document.querySelectorAll(`[id^="lbl_cfirst_${charId}_"]`).forEach(el => el.innerText = firstLabel);
}
}
function handleDropdownChange(charId, field, selectEl) {
const optText = selectEl.options[selectEl.selectedIndex].text;
const otherBox = document.getElementById(`other_${field}_${charId}`);
if (optText === 'Random') {
otherBox.style.display = 'none';
const valid = [];
for (let i = 0; i < selectEl.options.length; i++) {
const txt = selectEl.options[i].text;
if (txt !== 'Random' && txt !== 'Other' && txt !== 'None') valid.push(selectEl.options[i].value);
}
const randVal = valid[Math.floor(Math.random() * valid.length)];
selectEl.value = randVal; updateField(charId, field, randVal);
} else if (optText === 'Other') { otherBox.style.display = 'block'; }
else { otherBox.style.display = 'none'; updateField(charId, field, selectEl.value); }
}
function saveCustomOther(charId, field, textValue) {
if (!textValue.trim()) return;
fetch(`workspace.php?story_id=${storyId}&ajax_action=add_other&type=${field.replace('_id', '')}&value=${encodeURIComponent(textValue)}`)
.then(res => res.json()).then(data => { if (data.status === 'success') window.location.reload(); });
}
function saveRelationship(char1, char2, typeId) {
fetch(`workspace.php?story_id=${storyId}&ajax_action=update_relationship&char1=${char1}&char2=${char2}&type_id=${typeId}`)
.then(res => res.json()).then(data => {
if(data.status === 'success') {
const reciprocalSelect = document.querySelector(`select[data-char1="${char2}"][data-char2="${char1}"]`);
if (reciprocalSelect) reciprocalSelect.value = data.recip_id;
else window.location.reload();
}
});
}
</script>
</body>
</html>
workspace