Discover children’s stories with read-aloud audio, word highlighting, and illustrations. Safe, age-grouped tales with discussion questions for 0–12 year olds.
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.
Rusty's Big Day A heartwarming rhyming poem about Rusty, an old rusty lorry who proves that true worth comes from within, not from how shiny you look on the outside.
Stars in My Window A bedtime poem about finding comfort in the night sky.
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.
Oliver's Ocean Quest Oliver explores tide pools at the beach with his dad and discovers the wonders of ocean life.
Rusty's Big Day A heartwarming rhyming poem about Rusty, an old rusty lorry who proves that true worth comes from within, not from how shiny you look on the outside.
The Lonely Leaf A poem about sadness and finding new friends in autumn.
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.
Rusty's Big Day A heartwarming rhyming poem about Rusty, an old rusty lorry who proves that true worth comes from within, not from how shiny you look on the outside.
Stars in My Window A bedtime poem about finding comfort in the night sky.
Rusty's Big Day A heartwarming rhyming poem about Rusty, an old rusty lorry who proves that true worth comes from within, not from how shiny you look on the outside.
Stars in My Window A bedtime poem about finding comfort in the night sky.
if (!window.firebase) { console.error("Firebase SDK not found"); }
else if (!firebase.apps.length) { if (!firebase.apps.length) { if (!firebase.apps.length) { firebase.initializeApp(firebaseConfig); } } }
if (!window.firebase) { console.error("Firebase SDK not found"); }
else if (!firebase.apps.length) { if (!firebase.apps.length) { if (!firebase.apps.length) { firebase.initializeApp(firebaseConfig); } } }
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);
}
};