🔒 SPEDGenie does not store student names, addresses, phone numbers, SSNs, parent contact information, or original uploaded documents. Uploaded files are processed and deleted after extraction. Use student codes only — never real names.
Privacy practices →
🔄 Swap Symbol
📖 StoryGlyph Studio™

Symbol directly under word.
Every student reads.

Turn any passage into accessible visual supports. Four reading levels. Subject-aware symbol selection. Hover for definitions. Click for vocabulary cards. For any subject, any student, any disability.

Symbols under every key word4 reading levelsVocabulary bridge includedSymbol boards + print-ready
📖 Open StoryGlyph Studio →
⚠️
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.
StoryGlyph™
📖
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+'
' + '
'+t.sym.svg+'
' + '
'; } 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 += '
' + '
' + '
Page '+(i+1)+' of '+state.pages.length+'
' + '
' + '' + '' + '
' + '
' + 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.svg+'' + '
'+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.