⚠️
FERPA Notice: Do not enter or paste personally identifiable student information (full name, date of birth, address, student ID, or parent contact details) into any text field. Use student codes, initials, or anonymized descriptors only.
📖
Ready to build your first StoryGlyph™
Click New Story above, paste or upload any story or folktale, and Claude AI will build a symbol-supported picture book in seconds.
1
Click New Story and paste or upload your text
2
Choose layout, symbol style, and page count
3
Claude reads and maps symbols to every scene
4
Edit any symbol or sentence — then print or display
StoryGlyph™ is reading your story…
Reading and understanding the story…
Identifying key scenes and characters…
Simplifying language for accessibility…
Selecting symbols for each scene…
Building your symbol story…
// WORD-BY-WORD SYMBOL MAPPING
// Maps individual words → symbol ids
// ═══════════════════════════════════════════════════════════════
// Merge base word map into existing WORD_MAP (built by symbols_library.js)
Object.assign(WORD_MAP, {
// ── ANIMALS ──────────────────────────────────────────────
// ── NATURE / STORY ──────────────────────────────────────────
squirrel:'squirrel', squirrels:'squirrel',
nest:'nest', nests:'nest',
acorn:'acorn', acorns:'acorn',
leaf:'leaf', leaves:'leaf',
growing:'grow', grows:'grow', grown:'grow',
shade:'shade', shady:'shade',
climbing:'climb',
// ── ANIMALS ──────────────────────────────────────────────
rabbit:'rabbit', rabbits:'rabbit', bunny:'rabbit', brer:'rabbit',
fox:'fox', foxes:'fox',
bear:'bear', bears:'bear',
wolf:'wolf', wolves:'wolf',
pig:'pig', pigs:'pig', hog:'pig', hogs:'pig', swine:'pig',
spider:'spider', spiders:'spider',
bird:'bird', birds:'bird', robin:'bird', eagle:'bird',
cat:'cat', cats:'cat', kitten:'cat',
dog:'dog', dogs:'dog', puppy:'dog',
fish:'fish', fishes:'fish',
horse:'horse', horses:'horse', pony:'horse',
// ── PEOPLE ────────────────────────────────────────────────
person:'person', people:'person', someone:'person', somebody:'person',
boy:'boy', boys:'boy',
girl:'girl', girls:'girl',
man:'man', men:'man', male:'man', gentleman:'man',
woman:'woman', women:'woman', lady:'woman', female:'woman',
child:'child', children:'child', kid:'child', kids:'child',
baby:'child', babies:'child', infant:'child',
king:'king', prince:'king', emperor:'king',
queen:'queen', princess:'queen', empress:'queen',
farmer:'man', hunter:'man', soldier:'man',
mother:'woman', mom:'woman', mama:'woman',
father:'man', dad:'man', papa:'man',
teacher:'person', student:'person', friend:'person', friends:'person',
family:'person', families:'person',
// ── HOME ─────────────────────────────────────────────────
bed:'bed', beds:'bed', bedroom:'bed',
bathroom:'bathroom', restroom:'bathroom', toilet:'toilet',
bathtub:'shower', bath:'shower',
sink:'sink', faucet:'sink', washbasin:'sink',
kitchen:'kitchen', kitchens:'kitchen',
stove:'stove', oven:'stove', range:'stove', burner:'stove',
microwave:'microwave',
refrigerator:'refrigerator', fridge:'refrigerator', freezer:'refrigerator',
chair:'chair', chairs:'chair', seat:'chair',
table:'table', tables:'table', desk:'table',
couch:'couch', sofa:'couch', loveseat:'couch',
trash:'trash', garbage:'trash', bin:'trash', rubbish:'trash',
broom:'broom', sweep:'broom', sweeping:'broom',
laundry:'laundry', clothes:'laundry',
iron:'iron', ironing:'iron',
clock:'clock', timer:'clock', alarm:'clock',
phone:'telephone', telephone:'telephone', call:'telephone',
tv:'television', television:'television', screen:'television',
computer:'computer', laptop:'computer', tablet:'computer',
key:'key', keys:'key', lock:'lock', unlock:'lock',
light:'light', lamp:'light', lights:'light',
door:'door', doors:'door', gate:'door',
house:'house', home:'house', cabin:'house',
// ── HYGIENE ───────────────────────────────────────────────
tooth brush:'toothbrush',
toothpaste:'toothpaste', paste:'toothpaste',
soap:'soap', wash:'soap', washing:'soap', scrub:'soap',
shampoo:'shampoo', conditioner:'shampoo',
deodorant:'deodorant', antiperspirant:'deodorant',
comb:'comb', hair towel:'towel', dry:'towel', drying:'towel',
mirror:'mirror',
tissue:'tissue', tissues:'tissue', kleenex:'tissue',
medicine:'medicine', medication:'medicine', pill:'medicine', pills:'medicine',
bandage:'bandage', bandaid:'bandage', plaster:'bandage',
// ── FOOD & DRINK ──────────────────────────────────────────
apple:'apple', apples:'apple',
banana:'banana', bananas:'banana',
bread:'bread', loaf:'bread', toast:'bread', sandwich:'sandwich',
milk:'milk', dairy:'milk',
juice:'juice', lemonade:'juice',
water:'water_cup', drink:'water_cup', drinking:'water_cup',
pizza:'pizza',
egg:'eggs', eggs:'eggs',
rice:'rice', grain:'rice',
chicken:'chicken', meat:'chicken', turkey:'chicken',
vegetable:'vegetables', vegetables:'vegetables', veggies:'vegetables', salad:'vegetables',
fork:'fork', forks:'fork',
spoon:'spoon', spoons:'spoon',
plate:'plate', plates:'plate', dish:'plate',
bowl:'bowl', bowls:'bowl',
cup:'cup', cups:'cup', glass:'cup', mug:'cup',
cookie:'cookie', cookies:'cookie', biscuit:'cookie',
cake:'cake', cakes:'cake', cupcake:'cake',
candy:'candy', sweets:'candy', chocolate:'candy', treat:'candy',
snack:'food', lunch:'food', dinner:'food', breakfast:'food', meal:'food', food:'food', foods:'food',
// ── TRANSPORT ─────────────────────────────────────────────
bus:'bus', buses:'bus',
car:'car', cars:'car', vehicle:'car', auto:'car',
train:'train', trains:'train', subway:'train', metro:'train',
bicycle:'bicycle', bike:'bicycle', bikes:'bicycle',
airplane:'airplane', plane:'airplane', aircraft:'airplane', flight:'airplane',
bus_
crosswalk:'crosswalk', crossing:'crosswalk',
stop_sign:'stop_sign',
traffic_light:'traffic_light', traffic:'traffic_light',
seatbelt:'seatbelt', belt:'seatbelt',
// ── COMMUNITY ─────────────────────────────────────────────
store:'store', market:'store', mall:'store',
grocery:'grocery', supermarket:'grocery', groceries:'grocery',
cart:'cart', basket:'cart', trolley:'cart',
library:'library', libraries:'library',
hospital:'hospital', clinic:'hospital',
school:'school_building',
bank:'bank', banks:'bank',
restaurant:'restaurant', cafe:'restaurant', cafeteria:'restaurant', diner:'restaurant',
park:'park', playground:'park', garden:'park',
post:'post_office', office:'post_office', mail:'post_office',
laundromat:'laundromat',
// ── JOBS ─────────────────────────────────────────────────
work:'work', job:'work', working:'work', employment:'work',
apron:'apron',
nametag:'name_tag', badge:'name_tag', id:'name_tag',
boss:'boss', manager:'boss', supervisor:'boss',
break:'break_time', lunch_break:'break_time',
// ── MONEY ─────────────────────────────────────────────────
wallet:'wallet', purse:'wallet',
receipt:'receipt', bill:'receipt', invoice:'receipt',
card:'credit_card', credit:'credit_card', debit:'credit_card',
atm:'atm', cash_machine:'atm',
price:'price_tag', cost:'price_tag', sale:'price_tag',
coupon:'coupon', discount:'coupon', deal:'coupon',
money:'money', cash:'money', dollar:'money', dollars:'money',
coin:'money', coins:'money', change:'money',
pay:'money', paying:'money', paid:'money', purchase:'money',
buy:'money', buying:'money', bought:'money', shop:'store',
// ── SAFETY ───────────────────────────────────────────────
danger:'danger', dangerous:'danger', warning:'danger', caution:'danger',
emergency:'emergency',
police:'police', officer:'police', cop:'police',
fire_truck:'fire_truck', firetruck:'fire_truck',
ambulance:'ambulance',
hurt:'hurt', pain:'hurt', injured:'hurt', injury:'hurt',
safe:'safe', safety:'safe', okay:'safe',
help:'help_me', helping:'help_me',
// ── BODY ─────────────────────────────────────────────────
hand:'hand', hands:'hand', finger:'hand', fingers:'hand',
eye:'eye', eyes:'eye', see:'eye', watching:'eye',
ear:'ear', ears:'ear', hear:'ear', listen:'ear', hearing:'ear',
mouth:'mouth', lips:'mouth', teeth:'mouth', speak:'mouth',
nose:'nose', smell:'nose', sniff:'nose',
head:'head', face:'head', chin:'head',
stomach:'stomach', belly:'stomach', tummy:'stomach', hungry:'stomach',
arm:'arm', arms:'arm', elbow:'arm', wrist:'arm',
leg:'leg', legs:'leg', knee:'leg', foot:'leg', feet:'leg',
// ── CLOTHING ─────────────────────────────────────────────
shirt:'shirt', shirts:'shirt', blouse:'shirt', top:'shirt', tshirt:'shirt',
pants:'pants', jeans:'pants', trousers:'pants', shorts:'pants',
shoes:'shoes', boot:'shoes', boots:'shoes', sneakers:'shoes',
socks:'socks', sock:'socks',
jacket:'jacket', coat:'jacket', sweater:'jacket', hoodie:'jacket',
dress:'dress', skirt:'dress', gown:'dress',
hat:'hat_cap', cap:'hat_cap', beanie:'hat_cap',
glasses:'glasses', sunglasses:'glasses', spectacles:'glasses',
// ── WEATHER ───────────────────────────────────────────────
cloud:'cloud', clouds:'cloud', cloudy:'cloud',
rain:'rain', rainy:'rain', raining:'rain', drizzle:'rain', shower:'shower',
snow:'snow', snowy:'snow', snowing:'snow', blizzard:'snow',
wind:'wind', windy:'wind', breeze:'wind', breezy:'wind',
hot:'hot', warm:'hot', heat:'hot',
cold:'cold', cool:'cold', chilly:'cold', freezing:'cold',
umbrella:'umbrella',
sun:'sun', sunny:'sun', sunshine:'sun',
sunrise:'morning',
night:'night', darkness:'night',
// ── TIME ─────────────────────────────────────────────────
today:'today', tonight:'today',
yesterday:'yesterday',
tomorrow:'tomorrow',
morning:'morning_time', dawn:'morning_time',
afternoon:'afternoon', noon:'afternoon', midday:'afternoon',
evening:'evening', dusk:'evening', sunset:'evening',
calendar:'schedule', timetable:'schedule',
finished:'finished', done:'finished', complete:'finished', completed:'finished',
wait:'wait', waiting:'wait', pause:'wait',
now:'now', immediately:'now', right_now:'now',
later:'later', soon:'later', eventually:'later',
// ── SOCIAL / AAC ─────────────────────────────────────────
want:'want', wanting:'want', wanted:'want', desire:'want',
more:'more', again:'more', another:'more', extra:'more',
please:'please',
thank:'thank_you', thanks:'thank_you',
sorry:'sorry', apologize:'sorry', apology:'sorry',
quiet:'quiet_please', silence:'quiet_please', shh:'quiet_please',
stop:'stop_please', halt:'stop_please',
all_done:'all_done',
turn:'my_turn',
// ── COLORS ───────────────────────────────────────────────
red:'red', crimson:'red', scarlet:'red',
blue:'blue', navy:'blue', azure:'blue',
green:'green', emerald:'green', lime:'green',
yellow:'yellow', gold:'yellow',
orange:'orange', amber:'orange',
purple:'purple', violet:'purple', lavender:'purple',
pink:'pink', magenta:'pink',
black:'black', dark:'black',
white:'white', pale:'white',
brown:'brown', tan:'brown', beige:'brown',
// ── NUMBERS ──────────────────────────────────────────────
one:'one', '1':'one', first:'one',
two:'two', '2':'two', second:'two', pair:'two',
three:'three', '3':'three', third:'three',
four:'four', '4':'four', fourth:'four',
five:'five', '5':'five', fifth:'five',
// ── NATURE ───────────────────────────────────────────────
tree:'tree', trees:'tree', forest:'forest', woods:'forest', jungle:'forest',
flower:'flower', flowers:'flower', rose:'flower', daisy:'flower',
river:'river', stream:'river', lake:'river', pond:'river',
mountain:'mountain', hill:'mountain', cliff:'mountain',
road:'road', path:'road', trail:'road', sidewalk:'road', street:'road',
field:'field', meadow:'field', grass:'field', lawn:'field',
fire:'fire', flame:'fire', flames:'fire',
// ── ACTIONS ──────────────────────────────────────────────
walk:'walk', walked:'walk', walking:'walk', stroll:'walk', wander:'walk',
run:'run', ran:'run', running:'run', jog:'run', sprint:'run',
jump:'jump', jumped:'jump', leap:'jump', hop:'jump',
climb:'climb', climbed:'climb', eat:'eat', ate:'eat', eating:'eat', taste:'eat', nibble:'eat',
sleep:'sleep', slept:'sleep', sleeping:'sleep', nap:'sleep', rest:'sleep',
talk:'talk', said:'talk', spoke:'talk', asked:'talk', told:'talk',
cry:'cry', cried:'cry', crying:'cry', weep:'cry', sob:'cry',
hide:'hide', hid:'hide', hiding:'hide',
plant:'plant', planted:'plant', grow:'plant', grew:'plant',
pull:'pull', pulled:'pull', drag:'pull', tug:'pull',
find:'find', found:'find', search:'find', look:'find', seek:'find',
trick:'trick', tricked:'trick', fool:'trick', fooled:'trick', clever:'trick', sneak:'trick',
laugh:'laugh', laughed:'laugh', laughing:'laugh', giggle:'laugh', chuckle:'laugh',
// ── FEELINGS ─────────────────────────────────────────────
happy:'happy', glad:'happy', joy:'happy', joyful:'happy',
sad:'sad', unhappy:'sad', sorrow:'sad', grief:'sad', miss:'sad',
angry:'angry', mad:'angry', furious:'angry', rage:'angry', upset:'angry',
scared:'scared', afraid:'scared', fear:'scared', frightened:'scared',
proud:'proud', pride:'proud',
tired:'tired', sleepy:'tired', exhausted:'tired', weary:'tired',
// ── PHASE 3 — DAILY LIVING ───────────────────────────────
brush_teeth:'brush_teeth', brushing:'brush_teeth',
dressed:'get_dressed', dressing:'get_dressed',
woke:'wake_up', waking:'wake_up', awake:'wake_up',
bedtime:'go_to_bed', goodnight:'go_to_bed',
cook:'cook', cooking:'cook', bake:'cook', baking:'cook',
clean:'clean', cleaning:'clean', swept:'clean', mopping:'clean',
vacuum:'vacuum', vacuuming:'vacuum',
dishes:'dishes',
fold:'fold_clothes', folding:'fold_clothes',
recycle:'recycle', recycling:'recycle',
exercise:'exercise', workout:'exercise',
routine:'morning_routine',
focus:'pay_attention', focused:'pay_attention',
pack:'pack_bag', packing:'pack_bag',
// ── PHASE 3 — COMMUNITY & JOBS ───────────────────────────
cashier:'cashier', register:'cashier',
checkout:'checkout',
doctor:'doctor', physician:'doctor',
nurse:'nurse',
dentist:'dentist',
firefighter:'firefighter',
interview:'interview', interviewing:'interview',
volunteer:'volunteer', volunteering:'volunteer',
meeting:'meeting', conference:'meeting',
paycheck:'paycheck', salary:'paycheck', wages:'paycheck',
uniform:'uniform',
punctual:'be_on_time',
// ── PHASE 3 — SCHOOL ─────────────────────────────────────
pencil:'pencil_sym', pen:'pencil_sym',
notebook:'notebook', journal:'notebook',
test:'test', quiz:'test', exam:'test',
homework:'homework', assignment:'homework',
math:'math_sym', mathematics:'math_sym', addition:'math_sym',
science:'science_sym', biology:'science_sym',
technology:'computer_class', coding:'computer_class',
art:'art_class', drawing:'art_class', painting:'art_class',
singing:'music_class', instrument:'music_class',
teamwork:'group_work', collaborate:'group_work',
submit:'turn_in',
rules:'classroom_rules',
// ── PHASE 3 — EMOTIONS ───────────────────────────────────
frustrated:'frustrated', frustration:'frustrated', annoyed:'frustrated',
calm:'calm_feeling', calming:'calm_feeling', relaxed:'calm_feeling', peaceful:'calm_feeling',
excited:'excited_feeling', excitement:'excited_feeling', thrilled:'excited_feeling',
worried:'worried', anxious:'worried', anxiety:'worried', nervous:'nervous',
lonely:'lonely', alone:'lonely', isolated:'lonely',
overwhelmed:'overwhelmed', stressed:'overwhelmed',
embarrassed:'embarrassed', ashamed:'embarrassed',
disappointed:'disappointed',
surprised:'surprised', shocked:'surprised',
jealous:'jealous', envious:'jealous',
loved:'loved', caring:'loved',
grateful:'grateful', thankful:'grateful',
bored:'bored', boring:'bored',
// ── PHASE 3 — BEHAVIOR ───────────────────────────────────
breathe:'deep_breath', breathing:'deep_breath',
solution:'problem_solving', solve:'problem_solving',
persevere:'try_again',
advocate:'advocate',
symbol:'picture_symbol',
talker:'aac_device',
signing:'sign_language',
choices:'choice_board',
schedule:'visual_schedule',
// ── PHASE 3 — TRANSITION ─────────────────────────────────
independence:'independence', independent:'independence',
goal:'goal', goals:'goal', objective:'goal',
budget:'budget', budgeting:'budget',
apartment:'apartment', housing:'apartment',
application:'job_application', apply:'job_application',
responsibility:'responsibility', responsible:'responsibility',
career:'career', profession:'career',
college:'college', university:'college',
community:'community_living', neighborhood:'community_living',
});
// Stop words - do not symbolize
var STOP_WORDS = new Set([
'the','a','an','and','or','but','in','on','at','to','of','for','is','was',
'are','were','be','been','being','have','had','has','do','did','does','will',
'would','could','should','may','might','shall','it','its','this','that','these',
'those','i','me','my','we','our','you','your','he','his','she','her','they',
'them','their','who','which','what','when','where','how','why','if','then',
'so','with','by','from','up','out','into','about','as','like','just','one',
'two','three','all','some','any','each','every','both','few','more','most',
'over','under','before','after','between','through','during','again','off',
'down','very','too','also','than','only','even','well','back','still','way',
'there','here','now','then','soon','finally','first','next','last','long',
'day','days','time','once','twice','going','went','come','came','get','got',
'make','made','take','took','put','see','saw','knew','know','want','wanted',
'need','needed','thought','think','began','begin','started','start',
]);
// Tokenize sentence into word tokens with symbol lookups
function tokenizeSentence(sentence) {
var words = sentence.replace(/([.!?,;:])/g, ' $1 ').split(/\s+/).filter(Boolean);
var tokens = [];
var i = 0;
while (i < words.length) {
var raw = words[i];
var isPunct = /^[.!?,;:]+$/.test(raw);
if (isPunct) { tokens.push({ raw: raw, sym: null, isPunct: true }); i++; continue; }
// Try 3-word phrase first, then 2-word, then single
var found = false;
for (var len = Math.min(3, words.length - i); len >= 2; len--) {
var phrase = words.slice(i, i+len).join(' ').toLowerCase().replace(/[^a-z ]/g, '').trim();
var symId = WORD_MAP[phrase];
if (symId) {
var sym = SYMBOLS.find(function(s) { return s && s.id === symId; });
var combined = words.slice(i, i+len).join(' ');
tokens.push({ raw: combined, sym: sym || null });
i += len;
found = true;
break;
}
}
if (!found) {
// Strip possessives ('s, s') before single-word lookup
var clean = raw.toLowerCase().replace(/'s$/i, '').replace(/s'$/i, '').replace(/[^a-z]/g, '');
if (!clean || STOP_WORDS.has(clean)) {
tokens.push({ raw: raw, sym: null });
} else {
var symId2 = WORD_MAP[clean];
var sym2 = symId2 ? SYMBOLS.find(function(s) { return s && s.id === symId2; }) : null;
tokens.push({ raw: raw, sym: sym2 || null });
}
i++;
}
}
return tokens;
}
// ════════════════════════════════════════════════════════════════
// STATE
// ════════════════════════════════════════════════════════════════
function openModal() {
document.getElementById('modal-backdrop').classList.add('open');
document.body.style.overflow = 'hidden';
// Reset loading steps
for (var i=0;i<5;i++) {
var el = document.getElementById('lstep-'+i);
if (el) el.className = 'loading-step-item' + (i===0?' active':'');
}
}
function closeModal() {
document.getElementById('modal-backdrop').classList.remove('open');
document.body.style.overflow = '';
}
// Close modal on backdrop click
document.getElementById('modal-backdrop').addEventListener('click', function(e) {
if (e.target === this) closeModal();
});
// ESC key closes modal
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeModal();
});
var state = {
layout: 'book',
style: 'pcs',
pages: [], // [{sentence, simplified, symbols:[{id,label,cat,svg}]}]
title: '',
editIdx: null,
editSelSym: null
};
// ════════════════════════════════════════════════════════════════
// CONTROLS
// ════════════════════════════════════════════════════════════════
function setLayout(l) {
state.layout = l;
document.getElementById('layout-book').className = 'layout-btn'+(l==='book'?' active':'');
document.getElementById('layout-strip').className = 'layout-btn'+(l==='strip'?' active':'');
if (state.pages.length) renderOutput();
}
function setStyle(s) {
state.style = s;
document.getElementById('style-pcs').className = 'style-btn'+(s==='pcs'?' active':'');
document.getElementById('style-hc').className = 'style-btn'+(s==='hc'?' active':'');
if (state.pages.length) renderOutput();
}
// ════════════════════════════════════════════════════════════════
// FILE UPLOAD
// ════════════════════════════════════════════════════════════════
function handleFileUpload(evt) {
var file = evt.target.files[0];
if (!file) return;
document.getElementById('upload-filename').textContent = '📄 ' + file.name;
var reader = new FileReader();
reader.onload = function(e) {
var text = e.target.result;
// For PDFs, content may be binary — use only if text-readable
if (file.name.endsWith('.txt')) {
document.getElementById('story-text').value = text.substring(0,8000);
} else {
// PDF — try to extract readable text portions
var readable = text.replace(/[^\x20-\x7E\n]/g,' ').replace(/\s+/g,' ').trim();
if (readable.length > 100) {
document.getElementById('story-text').value = readable.substring(0,8000);
} else {
document.getElementById('story-text').value = '';
alert('PDF text could not be extracted automatically. Please copy and paste the story text instead.');
}
}
if (!document.getElementById('story-title').value && file.name) {
document.getElementById('story-title').value = file.name.replace(/\.(txt|pdf)$/i,'').replace(/[-_]/g,' ');
}
};
reader.readAsText(file);
}
function handleDrop(evt) {
evt.preventDefault();
document.getElementById('upload-zone').classList.remove('drag');
var file = evt.dataTransfer.files[0];
if (file) {
document.getElementById('file-inp').files = evt.dataTransfer.files;
handleFileUpload({target:{files:[file]}});
}
}
// ════════════════════════════════════════════════════════════════
// GENERATE — calls Anthropic API
// ════════════════════════════════════════════════════════════════
function generateStoryGlyph() {
var storyText = document.getElementById('story-text').value.trim();
if (!storyText || storyText.length < 20) {
document.getElementById('story-text').style.borderColor = '#dc2626';
document.getElementById('story-text').focus();
return;
}
document.getElementById('story-text').style.borderColor = '';
state.title = document.getElementById('story-title').value.trim() || 'My Story';
var level = document.getElementById('reading-level').value;
var pageCountRaw = document.getElementById('page-count').value;
var student = document.getElementById('student-name').value.trim();
// Auto mode: count paragraphs in the pasted text, one page per paragraph
var pageCount;
if (pageCountRaw === 'auto') {
var paragraphs = storyText.split(/\n\s*\n/).filter(function(p){ return p.trim().length > 10; });
// If no blank-line paragraphs, split on sentences (approx 3 sentences per page)
if (paragraphs.length <= 1) {
var sentences = storyText.match(/[^.!?]+[.!?]+/g) || [];
paragraphs = [];
for (var si = 0; si < sentences.length; si += 3) {
paragraphs.push(sentences.slice(si, si+3).join(' '));
}
}
pageCount = Math.max(2, Math.min(40, paragraphs.length));
} else {
pageCount = parseInt(pageCountRaw);
}
// Close modal and show loading
closeModal();
document.getElementById('empty-state').style.display = 'none';
document.getElementById('output-area').style.display = 'none';
var ls = document.getElementById('loading-state');
ls.classList.add('show');
// Animate loading steps
var stepIdx = 0;
var stepInt = setInterval(function() {
if (stepIdx > 0) {
var prev = document.getElementById('lstep-'+(stepIdx-1));
if (prev) { prev.className = 'loading-step-item done'; }
}
var curr = document.getElementById('lstep-'+stepIdx);
if (curr) { curr.className = 'loading-step-item active'; }
stepIdx++;
if (stepIdx >= 5) clearInterval(stepInt);
}, 900);
var levelMap = {
emerging: '3-5 words per sentence, very concrete, no metaphors',
beginner: '6-8 words per sentence, simple vocabulary, concrete language',
developing: 'full sentences, key vocabulary preserved, light scaffolding',
grade: 'preserve most of the original language with minor simplifications'
};
// For long texts, send up to 8000 chars
var textForAI = storyText.substring(0, 8000);
// Grade 2 vocabulary context — pull up to 12 matched words from SG2
var gradeLevel = document.getElementById('sg-grade-level') ? document.getElementById('sg-grade-level').value : '';
var gradeVocabContext = '';
if (gradeLevel === '2' && window.SG2) {
var storyWords = textForAI.toLowerCase().replace(/[^a-z\s]/g,' ').split(/\s+/).filter(function(w){return w.length>3;});
var seenW = {}; var matchedEntries = [];
storyWords.forEach(function(w) {
if (seenW[w]) return; seenW[w] = true;
var e = window.SG2.byWord(w);
if (e) matchedEntries.push(e);
});
if (matchedEntries.length) {
gradeVocabContext = '\nGRADE 2 VOCABULARY (use these student-friendly definitions in teacher prompts):\n'
+ matchedEntries.slice(0,12).map(function(e){ return '- ' + e.word + ': ' + e.studentDefinition; }).join('\n')
+ '\n';
}
} else if (gradeLevel && gradeLevel !== '') {
gradeVocabContext = '\nSTUDENT GRADE LEVEL: Grade ' + gradeLevel + '. Simplify language accordingly.\n';
}
var prompt = 'You are StoryGlyph\u2122, a special education tool. Convert the passage below into a symbol-supported picture book with exactly ' + pageCount + ' pages.\n\n'
+ 'READING LEVEL: ' + levelMap[level] + '\n'
+ gradeVocabContext + '\n'
+ 'PASSAGE:\n---\n' + textForAI + '\n---\n\n'
+ 'RULES:\n'
+ '- Divide the passage evenly across EXACTLY ' + pageCount + ' pages. Do not skip any content.\n'
+ '- Each page must cover a DIFFERENT sequential part of the passage.\n'
+ '- Write one simplified sentence per page matching the reading level.\n'
+ '- Write a short teacher prompt under 10 words for each page.\n'
+ '- Never repeat the same sentence on two pages.\n'
+ (student ? '- Refer to the student as: ' + student + '\n' : '- Use the student — never real names.\n')
+ '\nRespond with a JSON array ONLY — no markdown, no explanation:\n'
+ '[\n {"page": 1, "sentence": "...", "note": "teacher tip"},\n ...\n]'
fetch('/.netlify/functions/generate', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ type: 'storyglyph', prompt: prompt, pageCount: pageCount })
})
.then(function(r) {
if (!r.ok) throw new Error('Server error: ' + r.status);
return r.json();
})
.then(function(data) {
clearInterval(stepInt);
// Mark all steps done
for (var i=0;i<5;i++) {
var el = document.getElementById('lstep-'+i);
if (el) el.className = 'loading-step-item done';
}
setTimeout(function() {
ls.classList.remove('show');
var text = '';
if (data.content) text = typeof data.content === 'string' ? data.content : (data.content[0] && data.content[0].text) || '';
// Parse JSON from response
try {
var clean = text.replace(/```json|```/g,'').trim();
var pages = JSON.parse(clean);
state.pages = pages.map(function(p) {
return { sentence: p.sentence || '', simplified: p.note || p.prompt || '' };
});
} catch(e) {
// Fallback: split story into sentences and map symbols locally
state.pages = fallbackParse(storyText, pageCount, level);
}
document.getElementById('toolbar-title').textContent = state.title + ' — StoryGlyph™';
renderOutput();
}, 400);
})
.catch(function(err) {
clearInterval(stepInt);
ls.classList.remove('show');
// Fallback to local parsing
state.pages = fallbackParse(storyText, pageCount, level);
document.getElementById('toolbar-title').textContent = state.title + ' — StoryGlyph™';
renderOutput();
});
}
function fallbackParse(text, count, level) {
// Clean input — normalize separators
var t = text.replace(/[-\/]+(?=\s)/g, ',').replace(/\s+/g, ' ').trim();
var pool = [];
// Pass 1: real sentence boundaries
var s1 = t.match(/[^.!?]+[.!?]+/g) || [];
s1.forEach(function(s){ var c=s.trim(); if(c.length>5) pool.push(c); });
// Pass 2: comma + connective clause split
if(pool.length < count) {
pool = [];
var parts = t.split(/,\s*(?:and|but|when|while|as|so|then|looking|who|which|that|where|because|after|until)\s+/i);
parts.forEach(function(p){
var c = p.trim().replace(/^(and|but|when|while|as|so|then|looking)\s+/i,'');
if(c.length>5){ if(!c.match(/[.!?]$/)) c+='.'; pool.push(c); }
});
}
// Pass 3: split on every comma
if(pool.length < count) {
pool = [];
t.split(/,\s+/).forEach(function(p){
var c=p.trim(); if(c.length>5){ if(!c.match(/[.!?]$/)) c+='.'; pool.push(c); }
});
}
// Pass 4: split on dash/hyphen
if(pool.length < count) {
pool = [];
t.split(/\s+-\s+/).forEach(function(p){
var c=p.trim(); if(c.length>5){ if(!c.match(/[.!?]$/)) c+='.'; pool.push(c); }
});
}
// Pass 5: word-group chunks — ALWAYS guarantees unique pages
if(pool.length < count) {
pool = [];
var words = t.replace(/[.,!?;:]/g,'').split(/\s+/).filter(Boolean);
var size = Math.max(1, Math.ceil(words.length / count));
for(var g=0; g
3; });
if(clean.length === 0) clean = [t];
// Ensure enough chunks by repeating last if needed
while(clean.length < count) clean.push(clean[clean.length-1]);
// Distribute evenly — each page gets a different chunk
var pages = [];
for(var i=0; i 5) {
return words.slice(0, 5).join(' ') + '.';
} else if (level === 'beginner' && words.length > 9) {
return words.slice(0, 8).join(' ') + '.';
}
return sentence;
}
// ════════════════════════════════════════════════════════════════
// RENDER
// ════════════════════════════════════════════════════════════════
function renderSentenceLine(sentence, pageIdx) {
var tokens = tokenizeSentence(sentence);
var hasSym = tokens.some(function(t){ return t.sym; });
var html = '';
tokens.forEach(function(t, ti) {
if (t.isPunct) {
html += '
'+t.raw+'';
} else if (t.sym) {
html += '
'
+ '
'+t.raw+'
'
+ '
'
+ '
';
} else {
var spacer = hasSym ? '
' : '';
html += '
'+t.raw+''+spacer+'
';
}
});
html += '
';
return html;
}
function renderOutput() {
var out = document.getElementById('output-area');
var contrastClass = state.style === 'hc' ? ' style-contrast' : '';
var html = '';
// Cover
html += '
'
+ '
'+state.title+'
'
+ '
A StoryGlyph™ Symbol Story · SPEDGenie
'
+ '
📖 Symbol Supported Story · '+state.pages.length+' pages
'
+ '
';
state.pages.forEach(function(p, i) {
html += '
'
+ ''
+ '
'
+ renderSentenceLine(p.sentence, i)
+ (p.simplified ? '
💬 Teacher prompt: '+p.simplified+'
' : '')
+ '
';
});
html += '
';
out.innerHTML = html;
out.style.display = 'block';
out.scrollIntoView({behavior:'smooth', block:'start'});
}
// ════════════════════════════════════════════════════════════════
// EDIT
// ════════════════════════════════════════════════════════════════
function openEdit(idx) {
state.editIdx = idx;
state.editSelSym = state.pages[idx] ? state.pages[idx].symbols[0] : null;
document.getElementById('edit-sentence-inp').value = state.pages[idx] ? state.pages[idx].sentence : '';
var grid = document.getElementById('edit-sym-grid');
grid.innerHTML = SYMBOLS.map(function(s) {
var sel = state.editSelSym && state.editSelSym.id===s.id ? ' sel' : '';
return ''
+ '
'
+ '
'+s.label+'
';
}).join('');
document.getElementById('overlay').classList.add('open');
document.getElementById('edit-popup').classList.add('open');
}
function selEditSym(id) {
state.editSelSym = SYMBOLS.find(function(s){ return s && s.id===id; });
document.querySelectorAll('.esym').forEach(function(el){ el.classList.remove('sel'); });
event.currentTarget.classList.add('sel');
}
function applyEdit() {
if (state.editIdx === null) return;
var sentence = document.getElementById('edit-sentence-inp').value.trim();
if (sentence) state.pages[state.editIdx].sentence = sentence;
if (state.editSelSym) state.pages[state.editIdx].symbols[0] = state.editSelSym;
closeEdit();
renderOutput();
}
function closeEdit() {
document.getElementById('overlay').classList.remove('open');
document.getElementById('edit-popup').classList.remove('open');
state.editIdx = null;
}
function movePage(idx, dir) {
var ni = idx + dir;
if (ni < 0 || ni >= state.pages.length) return;
var t = state.pages[idx]; state.pages[idx]=state.pages[ni]; state.pages[ni]=t;
renderOutput();
}
// ════════════════════════════════════════════════════════════════
// UTIL
// ════════════════════════════════════════════════════════════════
function clearAll() {
state.pages = [];
document.getElementById('output-area').style.display = 'none';
document.getElementById('loading-state').classList.remove('show');
document.getElementById('empty-state').style.display = 'flex';
document.getElementById('story-text').value = '';
document.getElementById('upload-filename').textContent = '';
document.getElementById('toolbar-title').textContent = 'StoryGlyph™';
}
function displayFullscreen() {
var out = document.getElementById('output-area');
if (!out || !state.pages.length) return;
var w = window.open('','_blank','width=1200,height=850');
w.document.write('StoryGlyph™ by SPEDGenie'
+''
+''
+''
+out.innerHTML);
w.document.close();
}
// Add storyglyph_goal type handler note — generate.js handles via 'storyglyph' type
// ── Auto-load text from Chunking Engine ──────────────────────────
(function() {
var chunkText = localStorage.getItem('sg_chunk_text');
if (chunkText && chunkText.length > 20) {
localStorage.removeItem('sg_chunk_text');
setTimeout(function() {
var storyEl = document.getElementById('story-text');
if (storyEl) {
storyEl.value = chunkText;
if (typeof onStoryTextChange === 'function') onStoryTextChange(chunkText);
if (typeof showToast === 'function') showToast('📖 Text loaded from Chunking Engine — click ✚ New Story to generate symbol supports.');
}
}, 600);
}
})();
✦ Connected Workflow
StoryGlyph Studio™ feeds into:
Visual supports generated here are the classroom implementation layer of Curriculum Accessibility™.
← Back to full workflow overview
· SPEDGenie™ is a Special Education Operating System that helps educators understand students, generate goals, make curriculum accessible, support paraprofessionals, monitor progress, and produce district-ready documentation.