| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function mk(tag, cls, styles) { |
| 5 |
var d = document.createElement(tag); |
| 6 |
if (cls) d.className = cls; |
| 7 |
if (styles) Object.keys(styles).forEach(function (k) { d.style[k] = styles[k]; }); |
| 8 |
return d; |
| 9 |
} |
| 10 |
function tx(tag, cls, text, styles) { |
| 11 |
var d = mk(tag, cls, styles); |
| 12 |
d.textContent = text; |
| 13 |
return d; |
| 14 |
} |
| 15 |
function ap(parent) { |
| 16 |
Array.prototype.slice.call(arguments, 1).forEach(function (c) { if (c) parent.appendChild(c); }); |
| 17 |
return parent; |
| 18 |
} |
| 19 |
|
| 20 |
function difficultyInfo(d) { |
| 21 |
if (d === 'beginner') return { label: '🟢 Beginner', bg: '#dcfce7', color: '#14532d' }; |
| 22 |
if (d === 'advanced') return { label: '🔴 Advanced', bg: '#fee2e2', color: '#991b1b' }; |
| 23 |
return { label: '🟡 Intermediate', bg: '#fef9c3', color: '#854d0e' }; |
| 24 |
} |
| 25 |
|
| 26 |
function sectionHead(labelText, accentColor, borderColor, parentEl) { |
| 27 |
var wrap = mk('div', 'bkbg-tc-section-head'); |
| 28 |
var label = tx('span', 'bkbg-tc-section-label', labelText, { color: accentColor }); |
| 29 |
var rule = mk('div', 'bkbg-tc-section-rule', { background: borderColor }); |
| 30 |
ap(wrap, label, rule); |
| 31 |
ap(parentEl, wrap); |
| 32 |
} |
| 33 |
|
| 34 |
function init() { |
| 35 |
document.querySelectorAll('.bkbg-tutorial-card-app').forEach(function (appEl) { |
| 36 |
if (appEl.dataset.rendered) return; |
| 37 |
appEl.dataset.rendered = '1'; |
| 38 |
|
| 39 |
var opts; |
| 40 |
try { opts = JSON.parse(appEl.dataset.opts || '{}'); } catch (e) { opts = {}; } |
| 41 |
|
| 42 |
var o = Object.assign({ |
| 43 |
tutorialTitle: 'Tutorial', subtitle: '', author: '', showAuthor: true, |
| 44 |
difficulty: 'intermediate', duration: '', showDuration: true, |
| 45 |
completionRate: 0, showProgress: false, |
| 46 |
prerequisites: [], showPrerequisites: true, prerequisitesLabel: 'Prerequisites', |
| 47 |
steps: [], showSteps: true, stepsLabel: 'Steps', |
| 48 |
showStepDurations: true, showTips: true, |
| 49 |
materials: [], showMaterials: true, materialsLabel: "What you'll need", |
| 50 |
outcomes: [], showOutcomes: true, outcomesLabel: "What you'll learn", |
| 51 |
fontSize: 14, titleFontSize: 22, lineHeight: 168, borderRadius: 12, |
| 52 |
bgColor: '#ffffff', borderColor: '#e5e7eb', |
| 53 |
headerBg: '#0f172a', headerColor: '#ffffff', |
| 54 |
accentColor: '#3b82f6', |
| 55 |
stepNumberBg: '#3b82f6', stepNumberColor: '#ffffff', |
| 56 |
stepBorderColor: '#e5e7eb', |
| 57 |
stepTipBg: '#fffbeb', stepTipColor: '#92400e', stepTipBorderColor: '#fcd34d', |
| 58 |
prerequisiteBg: '#f1f5f9', prerequisiteColor: '#334155', |
| 59 |
materialBg: '#f0f9ff', materialColor: '#0c4a6e', |
| 60 |
outcomeBg: '#f0fdf4', outcomeColor: '#14532d', |
| 61 |
progressBg: '#e5e7eb', progressFill: '#3b82f6' |
| 62 |
}, opts); |
| 63 |
|
| 64 |
var lh = (o.lineHeight / 100).toFixed(2); |
| 65 |
|
| 66 |
// ── Block wrapper ───────────────────────────────────── |
| 67 |
var block = mk('div', 'bkbg-tc-block', { |
| 68 |
border: '1px solid ' + o.borderColor, |
| 69 |
borderRadius: o.borderRadius + 'px', |
| 70 |
overflow: 'hidden', |
| 71 |
background: o.bgColor |
| 72 |
}); |
| 73 |
|
| 74 |
// ── Header ─────────────────────────────────────────── |
| 75 |
var header = mk('div', 'bkbg-tc-header', { background: o.headerBg }); |
| 76 |
|
| 77 |
// Meta row: difficulty + duration + author |
| 78 |
var diff = difficultyInfo(o.difficulty); |
| 79 |
var metaRow = mk('div', 'bkbg-tc-meta-row'); |
| 80 |
ap(metaRow, tx('span', 'bkbg-tc-difficulty', diff.label, { background: diff.bg, color: diff.color })); |
| 81 |
if (o.showDuration && o.duration) { |
| 82 |
ap(metaRow, tx('span', 'bkbg-tc-meta-item', '⏱ ' + o.duration, { color: o.headerColor })); |
| 83 |
} |
| 84 |
if (o.showAuthor && o.author) { |
| 85 |
ap(metaRow, tx('span', 'bkbg-tc-meta-item', '✍️ ' + o.author, { color: o.headerColor })); |
| 86 |
} |
| 87 |
ap(header, metaRow); |
| 88 |
|
| 89 |
// Title |
| 90 |
ap(header, tx('h2', 'bkbg-tc-title', o.tutorialTitle, { |
| 91 |
color: o.headerColor |
| 92 |
})); |
| 93 |
|
| 94 |
// Subtitle |
| 95 |
if (o.subtitle) { |
| 96 |
ap(header, tx('p', 'bkbg-tc-subtitle', o.subtitle, { |
| 97 |
color: o.headerColor |
| 98 |
})); |
| 99 |
} |
| 100 |
|
| 101 |
// Progress bar |
| 102 |
if (o.showProgress) { |
| 103 |
var progWrap = mk('div', 'bkbg-tc-progress-wrap'); |
| 104 |
var progLbl = mk('div', 'bkbg-tc-progress-label'); |
| 105 |
ap(progLbl, |
| 106 |
tx('span', '', 'Progress', { color: o.headerColor }), |
| 107 |
tx('span', '', o.completionRate + '%', { color: o.headerColor }) |
| 108 |
); |
| 109 |
var track = mk('div', 'bkbg-tc-progress-track', { background: 'rgba(255,255,255,.2)' }); |
| 110 |
var fill = mk('div', 'bkbg-tc-progress-fill', { background: o.progressFill, width: o.completionRate + '%' }); |
| 111 |
ap(track, fill); |
| 112 |
ap(progWrap, progLbl, track); |
| 113 |
ap(header, progWrap); |
| 114 |
} |
| 115 |
ap(block, header); |
| 116 |
|
| 117 |
// ── Body ───────────────────────────────────────────── |
| 118 |
var body = mk('div', 'bkbg-tc-body'); |
| 119 |
|
| 120 |
// Outcomes |
| 121 |
if (o.showOutcomes && o.outcomes && o.outcomes.length > 0) { |
| 122 |
sectionHead(o.outcomesLabel || "What you'll learn", o.accentColor, o.borderColor, body); |
| 123 |
var outcomeList = mk('ul', 'bkbg-tc-outcomes'); |
| 124 |
o.outcomes.forEach(function (item) { |
| 125 |
if (!item || !item.text) return; |
| 126 |
var li = mk('li', 'bkbg-tc-outcome'); |
| 127 |
var icon = tx('span', 'bkbg-tc-outcome-icon', '✓', { background: o.outcomeBg, color: o.outcomeColor }); |
| 128 |
var txt = tx('span', 'bkbg-tc-body-text', item.text, { color: '#374151' }); |
| 129 |
ap(li, icon, txt); |
| 130 |
ap(outcomeList, li); |
| 131 |
}); |
| 132 |
ap(body, outcomeList); |
| 133 |
} |
| 134 |
|
| 135 |
// Prerequisites & Materials — two column row |
| 136 |
var hasPre = o.showPrerequisites && o.prerequisites && o.prerequisites.length > 0; |
| 137 |
var hasMat = o.showMaterials && o.materials && o.materials.length > 0; |
| 138 |
if (hasPre || hasMat) { |
| 139 |
var cols = mk('div', 'bkbg-tc-meta-cols'); |
| 140 |
if (hasPre) { |
| 141 |
var preCol = mk('div', 'bkbg-tc-col'); |
| 142 |
sectionHead(o.prerequisitesLabel || 'Prerequisites', o.accentColor, o.borderColor, preCol); |
| 143 |
var preList = mk('ul', 'bkbg-tc-list'); |
| 144 |
o.prerequisites.forEach(function (item) { |
| 145 |
if (!item || !item.text) return; |
| 146 |
ap(preList, tx('li', 'bkbg-tc-list-item', '→ ' + item.text, { |
| 147 |
background: o.prerequisiteBg, |
| 148 |
color: o.prerequisiteColor |
| 149 |
})); |
| 150 |
}); |
| 151 |
ap(preCol, preList); |
| 152 |
ap(cols, preCol); |
| 153 |
} |
| 154 |
if (hasMat) { |
| 155 |
var matCol = mk('div', 'bkbg-tc-col'); |
| 156 |
sectionHead(o.materialsLabel || "What you'll need", o.accentColor, o.borderColor, matCol); |
| 157 |
var matList = mk('ul', 'bkbg-tc-list'); |
| 158 |
o.materials.forEach(function (item) { |
| 159 |
if (!item || !item.text) return; |
| 160 |
ap(matList, tx('li', 'bkbg-tc-list-item', '◈ ' + item.text, { |
| 161 |
background: o.materialBg, |
| 162 |
color: o.materialColor |
| 163 |
})); |
| 164 |
}); |
| 165 |
ap(matCol, matList); |
| 166 |
ap(cols, matCol); |
| 167 |
} |
| 168 |
ap(body, cols); |
| 169 |
} |
| 170 |
|
| 171 |
// Steps |
| 172 |
if (o.showSteps && o.steps && o.steps.length > 0) { |
| 173 |
sectionHead(o.stepsLabel || 'Steps', o.accentColor, o.borderColor, body); |
| 174 |
var stepList = mk('ol', 'bkbg-tc-steps'); |
| 175 |
|
| 176 |
o.steps.forEach(function (step, i) { |
| 177 |
var li = mk('li', 'bkbg-tc-step'); |
| 178 |
var isLast = i === o.steps.length - 1; |
| 179 |
if (!isLast) { |
| 180 |
li.style.borderBottom = '1px solid ' + o.stepBorderColor; |
| 181 |
} |
| 182 |
|
| 183 |
// Number circle |
| 184 |
var numCircle = tx('div', 'bkbg-tc-step-num', String(i + 1), { |
| 185 |
background: o.stepNumberBg, |
| 186 |
color: o.stepNumberColor |
| 187 |
}); |
| 188 |
|
| 189 |
// Step body |
| 190 |
var stepBody = mk('div', 'bkbg-tc-step-body'); |
| 191 |
|
| 192 |
// Title row |
| 193 |
var titleRow = mk('div', 'bkbg-tc-step-title-row'); |
| 194 |
ap(titleRow, tx('span', 'bkbg-tc-step-title', step.title || ('Step ' + (i + 1)))); |
| 195 |
if (o.showStepDurations && step.duration) { |
| 196 |
ap(titleRow, tx('span', 'bkbg-tc-step-duration', '⏱ ' + step.duration)); |
| 197 |
} |
| 198 |
ap(stepBody, titleRow); |
| 199 |
|
| 200 |
// Content |
| 201 |
ap(stepBody, tx('p', 'bkbg-tc-step-content', step.content || '', { |
| 202 |
color: '#374151' |
| 203 |
})); |
| 204 |
|
| 205 |
// Tip |
| 206 |
if (o.showTips && step.tip) { |
| 207 |
var tip = mk('div', 'bkbg-tc-tip', { |
| 208 |
background: o.stepTipBg, |
| 209 |
color: o.stepTipColor, |
| 210 |
borderLeftColor: o.stepTipBorderColor |
| 211 |
}); |
| 212 |
var tipStrong = document.createElement('strong'); |
| 213 |
tipStrong.textContent = '💡 Tip: '; |
| 214 |
tip.appendChild(tipStrong); |
| 215 |
tip.appendChild(document.createTextNode(step.tip)); |
| 216 |
ap(stepBody, tip); |
| 217 |
} |
| 218 |
|
| 219 |
ap(li, numCircle, stepBody); |
| 220 |
ap(stepList, li); |
| 221 |
}); |
| 222 |
ap(body, stepList); |
| 223 |
} |
| 224 |
|
| 225 |
ap(block, body); |
| 226 |
appEl.parentNode.insertBefore(block, appEl); |
| 227 |
appEl.style.display = 'none'; |
| 228 |
}); |
| 229 |
} |
| 230 |
|
| 231 |
if (document.readyState !== 'loading') { init(); } |
| 232 |
else { document.addEventListener('DOMContentLoaded', init); } |
| 233 |
})(); |
| 234 |
|