Discover children’s stories with read-aloud audio, word highlighting, and illustrations. Safe, age-grouped tales with discussion questions for 0–12 year olds.
Stories that speak, words that glow.
Featured Stories
Benny's Big Helpers Benny learns to help around the house and discovers the joy of being a contributing family member.
Cousins' Countryside Adventure Join Sophia, Isabelle, Juliette, and Rory as four little cousins explore the countryside together, discovering butterflies, a babbling brook, and the joy of family.
Cousins' Countryside Adventure Join Sophia, Isabelle, Juliette, and Rory as four little cousins explore the countryside together, discovering butterflies, a babbling brook, and the joy of family.
Benny's Big Helpers Benny learns to help around the house and discovers the joy of being a contributing family member.
Cousins' Countryside Adventure Join Sophia, Isabelle, Juliette, and Rory as four little cousins explore the countryside together, discovering butterflies, a babbling brook, and the joy of family.
Benny's Big Helpers Benny learns to help around the house and discovers the joy of being a contributing family member.
Cousins' Countryside Adventure Join Sophia, Isabelle, Juliette, and Rory as four little cousins explore the countryside together, discovering butterflies, a babbling brook, and the joy of family.
Cousins' Countryside Adventure Join Sophia, Isabelle, Juliette, and Rory as four little cousins explore the countryside together, discovering butterflies, a babbling brook, and the joy of family.
Benny's Big Helpers Benny learns to help around the house and discovers the joy of being a contributing family member.
Cousins' Countryside Adventure Join Sophia, Isabelle, Juliette, and Rory as four little cousins explore the countryside together, discovering butterflies, a babbling brook, and the joy of family.
if (!window.firebase) { console.error("Firebase SDK not found"); }
let _sbUI;
function ensureUI(){
if (!_sbUI) _sbUI = new firebaseui.auth.AuthUI(firebase.auth());
return _sbUI;
}
window.openAuthModal = function(){
var modal = document.getElementById('auth-modal');
if(!modal){ console.warn('auth-modal not found'); return; }
// Show modal immediately
modal.style.display = 'flex';
// Ensure container exists inside modal
var container = document.getElementById('firebaseui-auth-container');
if(!container){
var content = modal.querySelector('.auth-modal-content, .modal-content, [role="dialog"]') || modal;
container = document.createElement('div');
container.id = 'firebaseui-auth-container';
content.appendChild(container);
}
// Make sure container is visible
container.style.display = 'block';
container.style.minHeight = '140px';
// Helper: start FirebaseUI with singleton and safe defaults
function startUI(){
try{
if(!(window.firebase && window.firebaseui && firebase.auth)) return false;
var ui = window.firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebase.auth());
var cfg = {
signInFlow: 'popup',
credentialHelper: (window.firebaseui && firebaseui.auth && firebaseui.auth.CredentialHelper) ? firebaseui.auth.CredentialHelper.NONE : undefined,
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
firebase.auth.EmailAuthProvider.PROVIDER_ID
],
callbacks: {
signInSuccessWithAuthResult: function(){ if (typeof window.closeAuthModal==='function') window.closeAuthModal(); return false; }
}
};
ui.start('#firebaseui-auth-container', cfg);
return true;
}catch(e){ console.error('startUI error', e); return false; }
}
// If project defines a hook, let it run first (it may build custom menu/UI)
try { if (typeof window.onOpenAuthModalHook === 'function') window.onOpenAuthModalHook(); } catch(e){ console.error(e); }
// Robust first-open: retry until buttons appear (max ~1s)
var tries = 0;
function needButtons(){
var has = container.querySelector('.firebaseui-idp-button, .firebaseui-idp-list, .firebaseui-card-content, .firebaseui-provider-wrapper');
return !has;
}
function tick(){
// Start if not already started
if (needButtons()) { startUI(); }
tries++;
if (needButtons() && tries < 7) {
// Wait a frame + small delay to allow CSS/layout & hook work
setTimeout(tick, 150);
}
}
// Kick off after a frame so modal is in the layout
if (document.startViewTransition) {
// No-op; just schedule
requestAnimationFrame(tick);
} else {
requestAnimationFrame(tick);
}
};
window.closeAuthModal = function(){
const m = document.getElementById('auth-modal');
if (m) m.style.display = 'none';
};
window.signOutFirebase = function(){
if (firebase && firebase.auth) {
firebase.auth().signOut().then(function(){ alert('Signed out'); }).catch(console.error);
}
};
// Child profile storage (for Google sign-in users)
function getChildProfile(uid) {
try { return JSON.parse(localStorage.getItem('storybell_child_' + uid) || 'null'); }
catch(e) { return null; }
}
// Get localStorage user (for email sign-up users)
function getLocalUser() {
try { return JSON.parse(localStorage.getItem('storybell_user') || 'null'); }
catch(e) { return null; }
}
// Capitalize first letter of a string
function capitalizeFirst(str) {
if (!str) return str;
return str.charAt(0).toUpperCase() + str.slice(1);
}
// Update header UI based on auth state (supports both Firebase and localStorage users)
function updateHeaderAuth(firebaseUser) {
// Desktop elements
var loginBtnDesktop = document.querySelector('.login-cta-header');
var profileDesktop = document.querySelector('.sb-user-profile');
// Mobile hamburger elements
var loginMobile = document.querySelector('.login-mobile');
var profileMobile = document.querySelector('.login-mobile-profile');
var nameMobile = document.querySelector('.sb-user-name-mobile');
// Subscribe box (hide when logged in)
var subscribeBox = document.querySelector('.subscribe-box');
// Check for email-based user in localStorage
var localUser = getLocalUser();
// Either Firebase user OR localStorage user counts as "signed in"
var isSignedIn = firebaseUser || localUser;
if (isSignedIn) {
var displayName;
if (firebaseUser) {
// Firebase user (Google sign-in): check child profile first
var profile = getChildProfile(firebaseUser.uid);
displayName = (profile && profile.name) || firebaseUser.displayName || (firebaseUser.email ? firebaseUser.email.split('@')[0] : 'Reader');
} else {
// localStorage user (email sign-up): use stored name or email prefix
displayName = localUser.name || (localUser.email ? localUser.email.split('@')[0] : 'Reader');
}
// Capitalize first letter
displayName = capitalizeFirst(displayName);
// Desktop: hide login, show profile with name
if (loginBtnDesktop) loginBtnDesktop.style.display = 'none';
if (profileDesktop) {
profileDesktop.style.display = 'flex';
var nameEl = profileDesktop.querySelector('.sb-user-name');
if (nameEl) nameEl.textContent = displayName;
}
// Mobile hamburger: hide login, show profile with name
if (loginMobile) loginMobile.classList.add('sb-auth-hidden');
if (profileMobile) profileMobile.style.display = 'block';
if (nameMobile) nameMobile.textContent = displayName;
// Hide subscribe box for logged-in users
if (subscribeBox) subscribeBox.style.display = 'none';
console.log('[SB] Header updated: showing', displayName);
} else {
// Desktop: show login, hide profile
if (loginBtnDesktop) loginBtnDesktop.style.display = '';
if (profileDesktop) profileDesktop.style.display = 'none';
// Mobile hamburger: show login, hide profile
if (loginMobile) loginMobile.classList.remove('sb-auth-hidden');
if (profileMobile) profileMobile.style.display = 'none';
// Show subscribe box for logged-out users
if (subscribeBox) subscribeBox.style.display = 'block';
console.log('[SB] Header updated: showing login button');
}
}
// Sign out function (clears both Firebase and localStorage)
window.signOutStoryBell = function(){
// Clear localStorage user (email sign-up)
localStorage.removeItem('storybell_user');
localStorage.removeItem('storybell_simple_user');
// Also sign out Firebase (Google users)
if (window.firebase && firebase.auth) {
firebase.auth().signOut().then(function(){
window.location.href = '/';
}).catch(function(e){
console.error('[SB] Sign out error:', e);
window.location.href = '/';
});
} else {
window.location.href = '/';
}
};
// Auth state listener to update header (for Firebase/Google users)
if (window.firebase && firebase.auth) {
console.log("[SB] Firebase initialized, checking auth...");
// Handle redirect result first (for Google sign-in redirect flow)
firebase.auth().getRedirectResult().then(function(result) {
console.log("[SB] getRedirectResult:", result);
if (result && result.user) {
console.log("[SB] Redirect sign-in successful:", result.user.displayName || result.user.email);
updateHeaderAuth(result.user);
} else {
console.log("[SB] No redirect result (user may not have just signed in via redirect)");
}
}).catch(function(error) {
console.error("[SB] Redirect result error:", error.code, error.message);
});
// Check current user immediately
var currentUser = firebase.auth().currentUser;
console.log("[SB] Current user on load:", currentUser ? currentUser.email : "none");
// Also listen for auth state changes
firebase.auth().onAuthStateChanged(function(u){
console.log("[SB] Firebase auth state:", u ? "SIGNED IN as " + (u.displayName || u.email) : "SIGNED OUT");
updateHeaderAuth(u);
});
}
// Also check for localStorage user on page load (for email sign-up users)
// Run after DOM is ready to ensure header elements exist
function initLocalUserAuth() {
console.log('[SB] initLocalUserAuth running...');
var localUser = getLocalUser();
console.log('[SB] localStorage user:', localUser);
var loginBtns = document.querySelectorAll('.login-cta-header, .login-mobile a[href*="signin"]');
var profileEls = document.querySelectorAll('.sb-user-profile');
console.log('[SB] Found loginBtns:', loginBtns.length, 'profileEls:', profileEls.length);
if (localUser) {
console.log('[SB] Calling updateHeaderAuth for:', localUser.name || localUser.email);
updateHeaderAuth(null); // Pass null for firebase, will use localStorage
}
}
// Run when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initLocalUserAuth);
} else {
initLocalUserAuth();
}
document.addEventListener('click', function(e){
const m = document.getElementById('auth-modal');
if (!m) return;
if (e.target === m) closeAuthModal();
});
// Start/refresh FirebaseUI with Google + Email when auth modal opens
window.onOpenAuthModalHook = function(){
try {
var containerSel = '#firebaseui-auth-container';
// Ensure container exists
var container = document.querySelector(containerSel);
if (!container) {
var m = document.getElementById('auth-modal');
if (!m) return;
container = document.createElement('div');
container.id = 'firebaseui-auth-container';
m.querySelector('.auth-inner, .auth-modal__inner, .auth-content')?.appendChild(container) || m.appendChild(container);
}
// Initialize UI
var ui;
if (typeof ensureUI === 'function') {
ui = ensureUI();
} else if (window.firebaseui && firebase && firebase.auth) {
ui = new firebaseui.auth.AuthUI(firebase.auth());
} else {
console.warn('FirebaseUI not available yet.');
return;
}
// Start UI
ui.start(containerSel, {
signInFlow: 'popup',
credentialHelper: firebaseui.auth.CredentialHelper.NONE,
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
firebase.auth.EmailAuthProvider.PROVIDER_ID
],
callbacks: {
signInSuccessWithAuthResult: function(){
if (typeof window.closeAuthModal === 'function') window.closeAuthModal();
return false;
}
}
});
} catch(e){
console.error('onOpenAuthModalHook error:', e);
}
};