Loading…
@@ -255,8 +286,10 @@ var IS_ADMIN = = $is_admin ? 'true' : 'false' ?>;
var EDIT_SESSION_ID = = $session ? (int)$session['id'] : 'null' ?>;
var PRAYERS_DATA = = json_encode(array_values($prayers_data)) ?>;
var EXISTING_STEPS = = json_encode(array_map(fn($s) => [
- 'prayer_id' => (int)$s['prayer_id'],
- 'attribution' => $s['attribution'],
+ 'step_type' => $s['step_type'] ?? 'prayer',
+ 'bead_type' => $s['bead_type'] ?? null,
+ 'prayer_id' => $s['prayer_id'] ? (int)$s['prayer_id'] : null,
+ 'attribution' => $s['attribution'] ?? 'leader_all',
], $edit_steps)) ?>;
diff --git a/api/builder_session.php b/api/builder_session.php
index c385955..12cc535 100644
--- a/api/builder_session.php
+++ b/api/builder_session.php
@@ -49,28 +49,40 @@ if ($name === '') json_err('Session name is required');
if (empty($steps)) json_err('Add at least one prayer to your sequence');
$valid_attributions = ['leader_all', 'leader_only', 'all_only', 'none'];
+$valid_bead_types = ['small', 'large', 'crucifix'];
-// Validate steps
+// Validate steps and collect prayer IDs to verify
+$prayer_ids = [];
foreach ($steps as $i => $step) {
- $pid = (int)($step['prayer_id'] ?? 0);
- $att = $step['attribution'] ?? 'leader_all';
- if (!$pid) json_err("Step " . ($i + 1) . " is missing a prayer");
- if (!in_array($att, $valid_attributions)) json_err("Invalid attribution on step " . ($i + 1));
+ $type = $step['step_type'] ?? 'prayer';
+ if ($type === 'bead') {
+ if (!in_array($step['bead_type'] ?? '', $valid_bead_types)) {
+ json_err("Invalid bead type on step " . ($i + 1));
+ }
+ } else {
+ $pid = (int)($step['prayer_id'] ?? 0);
+ $att = $step['attribution'] ?? 'leader_all';
+ if (!$pid) json_err("Step " . ($i + 1) . " is missing a prayer");
+ if (!in_array($att, $valid_attributions)) json_err("Invalid attribution on step " . ($i + 1));
+ $prayer_ids[] = $pid;
+ }
}
// Verify all prayer_ids exist and are accessible
-$prayer_ids = array_unique(array_column($steps, 'prayer_id'));
-$in_placeholders = implode(',', array_fill(0, count($prayer_ids), '?'));
-$valid_stmt = $pdo->prepare("
- SELECT id FROM custom_prayers
- WHERE id IN ($in_placeholders)
- AND (is_global = 1 OR created_by = ?)
-");
-$valid_stmt->execute([...$prayer_ids, $uid]);
-$valid_ids = array_column($valid_stmt->fetchAll(), 'id');
-foreach ($prayer_ids as $pid) {
- if (!in_array((string)$pid, array_map('strval', $valid_ids))) {
- json_err("Prayer ID {$pid} not found or not accessible");
+if (!empty($prayer_ids)) {
+ $prayer_ids = array_unique($prayer_ids);
+ $in_placeholders = implode(',', array_fill(0, count($prayer_ids), '?'));
+ $valid_stmt = $pdo->prepare("
+ SELECT id FROM custom_prayers
+ WHERE id IN ($in_placeholders)
+ AND (is_global = 1 OR created_by = ?)
+ ");
+ $valid_stmt->execute([...$prayer_ids, $uid]);
+ $valid_ids = array_column($valid_stmt->fetchAll(), 'id');
+ foreach ($prayer_ids as $pid) {
+ if (!in_array((string)$pid, array_map('strval', $valid_ids))) {
+ json_err("Prayer ID {$pid} not found or not accessible");
+ }
}
}
@@ -111,11 +123,16 @@ try {
// Insert steps
$step_stmt = $pdo->prepare(
- "INSERT INTO builder_steps (session_id, step_order, prayer_id, attribution)
- VALUES (?, ?, ?, ?)"
+ "INSERT INTO builder_steps (session_id, step_type, bead_type, step_order, prayer_id, attribution)
+ VALUES (?, ?, ?, ?, ?, ?)"
);
foreach ($steps as $order => $step) {
- $step_stmt->execute([$session_id, $order, (int)$step['prayer_id'], $step['attribution']]);
+ $type = $step['step_type'] ?? 'prayer';
+ if ($type === 'bead') {
+ $step_stmt->execute([$session_id, 'bead', $step['bead_type'], $order, null, 'none']);
+ } else {
+ $step_stmt->execute([$session_id, 'prayer', null, $order, (int)$step['prayer_id'], $step['attribution']]);
+ }
}
$pdo->commit();
diff --git a/assets/css/builder.css b/assets/css/builder.css
index 94163bd..0523fb0 100644
--- a/assets/css/builder.css
+++ b/assets/css/builder.css
@@ -439,6 +439,97 @@
margin-top: 2px;
}
+/* ── Bead markers section ────────────────────────────────────── */
+
+.bead-markers-section {
+ padding: 10px 16px 8px;
+ border-bottom: 1px solid var(--border);
+ background: var(--bg-card);
+ flex-shrink: 0;
+}
+
+.bead-markers-label {
+ font-size: 10px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: .06em;
+ color: var(--muted);
+ margin-bottom: 8px;
+}
+
+.bead-marker-cards {
+ display: flex;
+ gap: 8px;
+}
+
+.bead-marker-card {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 10px;
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ background: var(--bg);
+ cursor: pointer;
+ font-family: var(--font);
+ font-size: 12px;
+ text-align: left;
+ transition: border-color .15s, background .15s;
+}
+
+.bead-marker-card:hover {
+ border-color: var(--primary);
+ background: #eff6ff;
+}
+
+.bead-icon-sm { font-size: 18px; color: #6b7280; flex-shrink: 0; line-height: 1; }
+.bead-icon-lg { font-size: 20px; color: #374151; flex-shrink: 0; line-height: 1; }
+.bead-icon-cx { font-size: 18px; color: var(--gold); flex-shrink: 0; line-height: 1; }
+
+.bead-card-text {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ min-width: 0;
+}
+
+.bead-card-text strong { font-size: 12px; font-weight: 600; color: var(--text); }
+.bead-card-text small { font-size: 10px; color: var(--muted); }
+
+.bead-add-label {
+ font-size: 11px;
+ color: var(--primary);
+ font-weight: 600;
+ flex-shrink: 0;
+}
+
+/* ── Bead step card (in sequence list) ───────────────────────── */
+
+.step-card.bead-step {
+ background: #fefce8;
+ border-color: #fde68a;
+}
+
+.step-card.bead-step:hover {
+ border-color: #f59e0b;
+}
+
+.bead-step-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: #92400e;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.bead-step-sub {
+ font-size: 11px;
+ color: #b45309;
+ margin-top: 2px;
+}
+
/* ── Library footer ──────────────────────────────────────────── */
.library-footer {
diff --git a/assets/js/builder.js b/assets/js/builder.js
index 499394c..4274f79 100644
--- a/assets/js/builder.js
+++ b/assets/js/builder.js
@@ -23,8 +23,12 @@
// Populate existing steps when editing a session
if (window.EXISTING_STEPS && window.EXISTING_STEPS.length) {
window.EXISTING_STEPS.forEach(function (s) {
- const prayer = PRAYERS.find(p => String(p.id) === String(s.prayer_id));
- if (prayer) STEPS.push({ prayer_id: s.prayer_id, attribution: s.attribution, _prayer: prayer });
+ if (s.step_type === 'bead') {
+ STEPS.push({ step_type: 'bead', bead_type: s.bead_type });
+ } else {
+ const prayer = PRAYERS.find(p => String(p.id) === String(s.prayer_id));
+ if (prayer) STEPS.push({ step_type: 'prayer', prayer_id: s.prayer_id, attribution: s.attribution, _prayer: prayer });
+ }
});
}
@@ -146,8 +150,30 @@
return;
}
+ const beadMeta = {
+ small: { icon: '○', label: 'Small Bead', sub: 'Hail Mary bead' },
+ large: { icon: '●', label: 'Large Bead', sub: 'Our Father bead' },
+ crucifix: { icon: '✝', label: 'Crucifix', sub: 'Cross bead' },
+ };
+
list.innerHTML = STEPS.map(function (step, i) {
- const p = step._prayer;
+ const moveUp = `
`;
+ const moveDn = `
`;
+ const remove = `
`;
+
+ if (step.step_type === 'bead') {
+ const m = beadMeta[step.bead_type] || beadMeta.small;
+ return `
+
${i + 1}
+
+
${m.icon} ${m.label}
+
${m.sub}
+
+
${moveUp}${moveDn}${remove}
+
`;
+ }
+
+ const p = step._prayer;
const leaderPrev = (p.leader_text || '').replace(/\n/g, ' ').substring(0, 60);
const allPrev = (p.all_text || '').replace(/\n/g, ' ').substring(0, 60);
@@ -164,11 +190,7 @@