rename
· 1 year ago
23015287e4a61f513a9c331482924db38aed53cb
Parent:
e6b1b76bf
2 files changed +212 −93
- emanuel_transcript.json +1 −1
- shabbat-services-cheatsheet.html => shabbat_cheatsheet.html +211 −92
Diff
--- a/emanuel_transcript.json +++ b/emanuel_transcript.json @@ -1,5 +1,5 @@ { - "traditionName": "Temple Emanuel (Apr 4, 2025)", + "traditionName": "(Reform) Temple Emanuel (Apr 4, 2025)", "serviceOrder": [ { "id": "opening_music", --- a/shabbat-services-cheatsheet.html +++ b/shabbat_cheatsheet.html @@ -1,26 +1,34 @@ <!DOCTYPE html> <html lang="en"> - <head> +<head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Shabbat Evening Service Cheatsheet</title> <meta name="description" - content="An interactive, customizable guide to the Shabbat Evening Service (Kabbalat Shabbat & Ma'ariv), outlining key prayers, structure, themes, and including Hebrew/English texts for various traditions." + content="An interactive, customizable guide to the Shabbat Evening Service (Kabbalat Shabbat & Ma'ariv), outlining key prayers, structure, themes, and including Hebrew/English texts for various Jewish traditions." /> - <!-- Add canonical URL if deploying --> - <!-- <link rel="canonical" href="[YOUR-URL-HERE]/shabbat-services-cheatsheet.html" /> --> + <link rel="canonical" href="https://cheatsheets.davidveksler.com/shabbat-cheatsheet.html" /> - <!-- Social Media Metadata (Optional) --> + <!-- Social Media Metadata --> <meta property="og:title" content="Shabbat Evening Service Cheatsheet" /> <meta property="og:description" content="Interactive guide to Friday night prayers: structure, themes, Hebrew/English texts, customizable by tradition (Reform, Conservative, Orthodox, etc.)." /> <meta property="og:type" content="article" /> - <!-- <meta property="og:url" content="[YOUR-URL-HERE]/shabbat-services-cheatsheet.html" /> --> - <!-- <meta property="og:image" content="[YOUR-IMAGE-URL-HERE]/shabbat-preview.png"> --> - <meta name="twitter:card" content="summary" /> + <meta property="og:url" content="https://cheatsheets.davidveksler.com/shabbat-cheatsheet.html" /> + <!-- Replace with your actual image URL --> + <!-- <meta property="og:image" content="[YOUR-IMAGE-URL-HERE]/shabbat-preview.png" /> --> + <meta name="twitter:card" content="summary_large_image" /> <!-- Use summary_large_image if you have an image --> + <meta name="twitter:title" content="Shabbat Evening Service Cheatsheet" /> + <meta + name="twitter:description" + content="Interactive guide to Friday night prayers: structure, themes, Hebrew/English texts, customizable by tradition." + /> + <!-- Replace with your actual image URL --> + <!-- <meta name="twitter:image" content="[YOUR-IMAGE-URL-HERE]/shabbat-preview.png" /> --> + <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css" /> @@ -70,6 +78,9 @@ font-size: 1.1rem; color: rgba(255, 255, 255, 0.9); margin-top: 0.5rem; + max-width: 700px; + margin-left: auto; + margin-right: auto; } .controls-container { display: flex; @@ -94,6 +105,11 @@ padding: 0.4rem 0.8rem; border-radius: 20px; font-size: 0.95em; + cursor: pointer; + } + .tradition-selector-container select:focus { + outline: none; + box-shadow: 0 0 0 2px var(--shabbat-accent); } .tradition-selector-container select option { background-color: var(--shabbat-primary); @@ -113,11 +129,34 @@ border-color: var(--shabbat-accent); margin-top: 0; /* Reset margin if needed */ margin-right: 0.5rem; + cursor: pointer; } .hebrew-toggle-container .form-check-input:checked { background-color: var(--shabbat-accent); border-color: var(--shabbat-secondary); } + .intro-section { + background-color: #fff; + padding: 2rem 1.5rem; + margin-bottom: 2.5rem; + border-radius: 0.5rem; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.05); + border: 1px solid #eee; + border-left: 5px solid var(--shabbat-primary); + } + .intro-section h3 { + font-family: var(--heading-font); + color: var(--shabbat-primary); + font-weight: 700; + margin-bottom: 1rem; + } + .intro-section p { + font-size: 1rem; + line-height: 1.7; + color: #444; + } + .intro-section strong { color: var(--shabbat-primary); } + .section-title { font-family: var(--heading-font); color: var(--shabbat-primary); @@ -128,10 +167,10 @@ letter-spacing: 0.05em; border-bottom: 2px solid var(--shabbat-accent); padding-bottom: 0.5rem; - display: inline-block; /* Center alignment helper */ + display: inline-block; } .section-title-container { - text-align: center; /* Center the inline-block title */ + text-align: center; min-height: 50px; /* Prevent layout jump when title changes */ } .info-card { @@ -144,7 +183,7 @@ display: flex; flex-direction: column; transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; - margin-bottom: 2rem; /* Add margin between cards */ + margin-bottom: 2rem; } .info-card:hover { transform: translateY(-3px); @@ -171,8 +210,9 @@ } .info-card h5 .bi { color: var(--shabbat-accent); - font-size: 1.5em; /* Make icon larger */ - margin-right: 0.3rem; + font-size: 1.6em; /* Made icon slightly larger */ + margin-right: 0.4rem; + vertical-align: -0.15em; } .info-card .description { font-size: 0.95rem; @@ -188,7 +228,7 @@ } .toggle-term .en, .toggle-term .he { - display: inline; /* Default display */ + display: inline; } footer { padding-top: 3rem; @@ -204,7 +244,7 @@ border-top: 1px solid var(--shabbat-secondary); padding-top: 1rem; margin-top: 1rem; - background-color: #f8f5fa; /* Slightly different background for details */ + background-color: #f8f5fa; border-radius: 0 0 0.5rem 0.5rem; padding: 1rem; } @@ -227,28 +267,28 @@ margin-top: 1rem; font-size: 1rem; line-height: 1.8; - white-space: pre-wrap; /* Allows wrapping and preserves line breaks */ + white-space: pre-wrap; } .prayer-text .hebrew { font-family: var(--hebrew-font); font-size: 1.2em; color: var(--shabbat-primary); - display: block; /* Ensure Hebrew is on its own lines */ + display: block; margin-bottom: 0.5rem; - direction: rtl; /* Set text direction for Hebrew */ - text-align: right; /* Align Hebrew text to the right */ + direction: rtl; + text-align: right; } .prayer-text .english-translit { font-family: var(--english-font); font-style: italic; color: #444; - display: block; /* Ensure English is on its own lines */ - margin-bottom: 0.5rem; /* Smaller margin before translation */ + display: block; + margin-bottom: 0.5rem; margin-top: 0.3rem; } .prayer-text .english-translate { font-family: var(--english-font); - color: #555; /* Slightly different color for translation */ + color: #555; display: block; margin-bottom: 1rem; } @@ -275,7 +315,6 @@ vertical-align: -0.1em; transition: transform 0.2s ease-in-out; } - /* Updated selector for chevron toggle based on Bootstrap 5 events */ .details-toggle .bi-chevron-down { transition: transform 0.35s ease; } .details-toggle[aria-expanded="true"] .bi-chevron-down { transform: rotate(180deg); } @@ -304,7 +343,7 @@ margin-bottom: 0.5rem; } .notes { - background-color: #fffadd; /* Light yellow background for notes */ + background-color: #fffadd; border-left: 3px solid var(--shabbat-accent); padding: 0.8rem; margin-top: 1rem; @@ -312,20 +351,22 @@ border-radius: 0 4px 4px 0; } .notes h6 { margin-top: 0; } + .notes p:last-child { margin-bottom: 0; } /* Remove extra space in notes */ + </style> - </head> - <body> +</head> +<body> <header class="page-header"> - <h1 class="display-5"><i class="bi bi-candle"></i> Shabbat Evening Service</h1> + <h1 class="display-5"><i class="bi bi-calendar-check"></i> Shabbat Evening Service</h1> <p class="lead">An Interactive Guide to Welcoming Shabbat Through Prayer</p> <div class="controls-container"> <div class="tradition-selector-container"> - <label for="tradition-selector">Select Tradition:</label> + <label for="tradition-selector">View Service Flow:</label> <select id="tradition-selector" class="form-select-sm"> <option value="reform" selected>Reform</option> <option value="conservative">Conservative</option> <option value="orthodox">Orthodox</option> - <option value="emanuel_transcript">Temple Emanuel (Apr 4, 2025)</option> + <option value="emanuel_transcript">Specific Example (Temple Emanuel, Apr 4 '25)</option> <!-- Add more options here as you create more JSON files --> </select> </div> @@ -337,28 +378,61 @@ </header> <div class="container"> - <div class="section-title-container"> - <h2 class="section-title" id="current-tradition-title">Service Flow</h2> - </div> - <div id="service-content" class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4"> - <!-- Service steps will be loaded here by JavaScript --> - <div class="col"> - <div class="info-card"> - <div class="card-body"> - <h5>Loading...</h5> - <p class="description">Please select a tradition to view the service details.</p> - </div> - </div> - </div> - </div> + + <!-- Introduction Section --> + <section class="intro-section"> + <h3>Welcoming Shabbat: An Overview</h3> + <p> + The Shabbat evening service, typically held on Friday night, marks the transition from the ordinary weekday to the sacred time of Shabbat. It's a time for community gathering, spiritual reflection, and joyfully welcoming the 'Sabbath Queen' or 'Sabbath Bride'. + </p> + <p> + The service generally consists of two main parts: + <ul> + <li><strong>Kabbalat Shabbat ("Receiving Shabbat"):</strong> A series of Psalms and the poem <em>Lecha Dodi</em>, introduced relatively recently (16th century) by mystics to create a spiritual gateway into Shabbat.</li> + <li><strong>Ma'ariv (Evening Service):</strong> The standard evening prayer service, including the <em>Shema</em> and its blessings, and the <em>Amidah</em> (standing prayer), adapted specifically for Shabbat.</li> + </ul> + </p> + <p> + <strong>Exploring Traditions:</strong> Different Jewish movements approach the liturgy with varying emphases. + <strong>Orthodox</strong> services adhere closely to traditional texts and structures, primarily in Hebrew. + <strong>Conservative (Masorti)</strong> services also follow traditional structures but allow for some textual adaptations (like including Matriarchs) and greater use of instrumental music or egalitarian practices than Orthodoxy. + <strong>Reform (Liberal/Progressive)</strong> services prioritize thematic relevance and accessibility, often using significant amounts of English, contemporary readings, abbreviated texts, and embracing full egalitarianism and diverse musical styles. + <strong>Reconstructionist</strong> services view tradition as evolving and emphasize democratic community values, often resulting in creative and adaptive liturgy. + </p> + <p> + Use the dropdown menu above to explore the typical flow and key prayers within different traditions, or view a specific example service. + </p> + </section> + + <!-- Dynamic Service Flow Section --> + <div class="section-title-container"> + <h2 class="section-title" id="current-tradition-title">Service Flow</h2> + </div> + <div id="service-content" class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4"> + <!-- Service steps will be loaded here by JavaScript --> + <div class="col"> + <div class="info-card"> + <div class="card-body text-center"> + <h5 class="text-muted"><i class="bi bi-hourglass-split"></i> Loading...</h5> + <p class="description text-muted">Please select a tradition above to view the service details.</p> + </div> + </div> + </div> + </div> + </div><!-- /.container --> <footer class="container text-center pb-3"> <p class="mb-2"> - © 2025 [Your Name/Organization] · This cheatsheet provides a general overview. Consult a Siddur (prayer book) and local custom for definitive practice. Texts may be adapted or shortened for brevity. Initial text data often sourced from public domain resources like Sefaria.org. + © 2025 David Veksler · This cheatsheet provides a general overview. Liturgy, order, and customs can vary significantly between movements and individual congregations. Consult a Siddur (prayer book) and local custom for definitive practice. Texts may be adapted or shortened for brevity. Initial text data often sourced from public domain resources like Sefaria.org. </p> <div> - <!-- Add relevant links here if desired --> + <a href="https://github.com/DavidVeksler/Cheatsheets" title="View Source on GitHub" target="_blank" rel="noopener noreferrer" class="mx-2 link-secondary"> + <i class="bi bi-github"></i> GitHub + </a> + <a href="https://cheatsheets.davidveksler.com/" title="Browse All Cheatsheets" class="mx-2 link-secondary"> + <i class="bi bi-collection"></i> All Cheatsheets + </a> </div> </footer> @@ -371,68 +445,91 @@ const applyLang = () => { const showHe = hebrewToggle.checked; - const terms = serviceContent.querySelectorAll('.toggle-term'); + const terms = serviceContent.querySelectorAll('.toggle-term'); // Query within serviceContent terms.forEach(t => { const en = t.querySelector('.en'); const he = t.querySelector('.he'); if (en && he) { + // Ensure elements exist before accessing style en.style.display = showHe ? 'none' : 'inline'; he.style.display = showHe ? 'inline' : 'none'; } }); + + // Also apply to any static toggle terms if needed (none currently outside dynamic area) + const staticTerms = document.querySelectorAll('.intro-section .toggle-term'); // Example if needed + staticTerms.forEach(t => { + const en = t.querySelector('.en'); + const he = t.querySelector('.he'); + if (en && he) { + en.style.display = showHe ? 'none' : 'inline'; + he.style.display = showHe ? 'inline' : 'none'; + } + }); }; const renderService = (serviceData) => { serviceContent.innerHTML = ''; // Clear previous content currentTraditionTitle.textContent = `${serviceData.traditionName} Service Flow`; + if (!serviceData.serviceOrder || serviceData.serviceOrder.length === 0) { + serviceContent.innerHTML = `<div class="col"><div class="alert alert-warning" role="alert">No service steps defined for "${serviceData.traditionName}".</div></div>`; + return; + } + serviceData.serviceOrder.forEach(step => { const cardCol = document.createElement('div'); - // Determine column class - maybe make it dynamic later or stick to 3? - cardCol.className = 'col'; // Let Bootstrap handle wrapping + cardCol.className = 'col'; // Use Bootstrap's column system - const collapseId = `collapse-${step.id}`; + const collapseId = `collapse-${step.id}-${Math.random().toString(36).substring(2, 7)}`; // Add random suffix for absolute uniqueness if needed - let detailsHtml = ` - <h6>Background</h6> - <p>${step.details.background || 'No background available.'}</p> - `; - - if (step.details.full_text_he || step.details.full_text_en_translit || step.details.full_text_en_translate) { - detailsHtml += `<h6>Text</h6><div class="prayer-text">`; - if (step.details.full_text_he) { - detailsHtml += `<div class="hebrew">${step.details.full_text_he}</div>`; + let detailsHtml = ''; + if(step.details) { // Check if details object exists + if(step.details.background) { + detailsHtml += `<h6>Background</h6><p>${step.details.background}</p>`; } - if (step.details.full_text_en_translit) { - detailsHtml += `<div class="english-translit">${step.details.full_text_en_translit}</div>`; + + if (step.details.full_text_he || step.details.full_text_en_translit || step.details.full_text_en_translate) { + detailsHtml += `<h6>Text</h6><div class="prayer-text">`; + if (step.details.full_text_he) { + detailsHtml += `<div class="hebrew">${step.details.full_text_he}</div>`; + } + if (step.details.full_text_en_translit) { + detailsHtml += `<div class="english-translit">${step.details.full_text_en_translit}</div>`; + } + if (step.details.full_text_en_translate) { + detailsHtml += `<div class="english-translate">${step.details.full_text_en_translate}</div>`; + } + detailsHtml += `</div>`; // Close prayer-text } - if (step.details.full_text_en_translate) { - detailsHtml += `<div class="english-translate">${step.details.full_text_en_translate}</div>`; + + let notesContent = ''; + if(step.details.notes) notesContent += `<p><strong>Notes:</strong> ${step.details.notes}</p>`; + if(step.details.transcript_notes) notesContent += `<p><strong>Transcript Specifics:</strong> ${step.details.transcript_notes}</p>`; + + if(notesContent) { + detailsHtml += `<div class="notes">${notesContent}</div>`; } - detailsHtml += `</div>`; + } else { + detailsHtml = '<p><em>No further details available for this step.</em></p>'; } - if (step.details.notes || step.details.transcript_notes) { - detailsHtml += `<div class="notes">`; - if(step.details.notes) detailsHtml += `<p><strong>Notes:</strong> ${step.details.notes}</p>`; - if(step.details.transcript_notes) detailsHtml += `<p><strong>Transcript Specifics:</strong> ${step.details.transcript_notes}</p>`; - detailsHtml += `</div>`; - } - const hebrewName = step.name_he || step.name_en; // Fallback to English if no Hebrew + const hebrewName = step.name_he || step.name_en; // Fallback cardCol.innerHTML = ` <div class="info-card"> <div class="card-body"> ${step.timestamp ? `<span class="timestamp">(${step.timestamp})</span>` : ''} <h5> - <i class="bi bi-${getIconForStep(step.id)}"></i> <!-- Dynamic Icon --> + <i class="bi bi-${getIconForStep(step.id, step.name_en)}"></i> <span class="toggle-term"> <span class="en">${step.name_en}</span> <span class="he">${hebrewName}</span> </span> </h5> - <p class="description">${step.summary}</p> + <p class="description">${step.summary || '...'}</p> + ${step.details ? ` <button class="btn btn-sm details-toggle mt-auto" type="button" @@ -446,47 +543,58 @@ <div class="collapse collapse-content" id="${collapseId}"> ${detailsHtml} </div> + ` : ''} </div> </div> `; serviceContent.appendChild(cardCol); }); - applyLang(); // Apply language toggle to newly rendered content - // Re-initialize or ensure collapse functionality works (Bootstrap usually handles this automatically via attributes) + applyLang(); // Apply language toggle initially }; - // Simple function to assign icons - expand as needed - const getIconForStep = (stepId) => { - if (stepId.includes('candle')) return 'brightness-high'; - if (stepId.includes('kabbalat') || stepId.includes('welcome') || stepId.includes('dodi')) return 'door-open'; - if (stepId.includes('barchu')) return 'megaphone'; - if (stepId.includes('shema')) return 'ear'; + // Expanded function to assign icons + const getIconForStep = (stepId, stepNameEn) => { + stepId = stepId.toLowerCase(); + stepNameEn = stepNameEn.toLowerCase(); + + if (stepId.includes('candle')) return 'brightness-high-fill'; // Filled candle + if (stepId.includes('kabbalat') || stepId.includes('welcome') || stepId.includes('intro') || stepId.includes('dodi')) return 'door-open-fill'; + if (stepId.includes('barchu')) return 'megaphone-fill'; + if (stepId.includes('shema')) return 'ear-fill'; if (stepId.includes('amidah') || stepId.includes('tefillah')) return 'person-arms-up'; if (stepId.includes('aleinu')) return 'globe-americas'; - if (stepId.includes('kaddish')) return 'person-heart'; + if (stepId.includes('kaddish') || stepNameEn.includes('mourner')) return 'calendar-heart-fill'; // Yahrzeit/Mourning if (stepId.includes('kiddush')) return 'cup-straw'; - if (stepId.includes('psalm') || stepId.includes('hymn') || stepId.includes('song') || stepId.includes('music')) return 'music-note-beamed'; - if (stepId.includes('sermon') || stepId.includes('reading') || stepId.includes('blessing') || stepId.includes('tribute')) return 'book'; - if (stepId.includes('intro') || stepId.includes('announce')) return 'info-circle'; - if (stepId.includes('board') || stepId.includes('member') || stepId.includes('install')) return 'person-badge'; - if (stepId.includes('healing') || stepId.includes('prayer_for')) return 'heart-pulse'; // For Mi Sheberach etc. - return 'star'; // Default icon + if (stepId.includes('psalm') || stepId.includes('hymn') || stepId.includes('song') || stepId.includes('music') || stepId.includes('dayenu')) return 'music-note-beamed'; + if (stepId.includes('sermon') || stepId.includes('reading') || stepId.includes('response') || stepId.includes('tribute')) return 'book-half'; // Reading/Speech + if (stepId.includes('announce')) return 'info-circle-fill'; + if (stepId.includes('board') || stepId.includes('member') || stepId.includes('install')) return 'person-badge-fill'; + if (stepId.includes('healing') || stepId.includes('prayer_for') || stepId.includes('hostage')) return 'heart-pulse-fill'; // Healing/Concern + if (stepId.includes('blessing')) return 'stars'; // General blessing + if (stepId.includes('silent')) return 'pause-circle-fill'; + if (stepId.includes('shalom')) return 'peace-fill'; // For Oseh Shalom + return 'check-circle'; // Default checkmark } const loadService = async (traditionKey) => { + serviceContent.innerHTML = `<div class="col"><div class="info-card"><div class="card-body text-center"><h5 class="text-muted"><i class="bi bi-hourglass-split"></i> Loading ${traditionSelector.options[traditionSelector.selectedIndex].text}...</h5></div></div></div>`; // Loading message + currentTraditionTitle.textContent = `Loading...`; try { - // Assuming JSON files are in the same directory as the HTML file + // Ensure JSON files are in the same directory or provide correct path const response = await fetch(`./${traditionKey}.json`); if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + // Try to provide a more specific error message if possible + const errorText = await response.text(); + console.error(`HTTP error! status: ${response.status}, message: ${errorText}`); + throw new Error(`Could not fetch ${traditionKey}.json. Status: ${response.status}`); } const serviceData = await response.json(); renderService(serviceData); } catch (error) { - console.error('Error loading service data:', error); - serviceContent.innerHTML = `<div class="col"><div class="alert alert-danger" role="alert">Could not load service data for "${traditionKey}". Please ensure the file exists and is valid JSON.</div></div>`; + console.error('Error loading or rendering service data:', error); + serviceContent.innerHTML = `<div class="col"><div class="alert alert-danger" role="alert"><strong>Error:</strong> Could not load service data for "${traditionSelector.options[traditionSelector.selectedIndex].text}".<br><small>Please ensure the file "${traditionKey}.json" exists in the same directory as this HTML file and is valid JSON.</small></div></div>`; currentTraditionTitle.textContent = `Error Loading Data`; } }; @@ -501,8 +609,19 @@ // Initial Load document.addEventListener('DOMContentLoaded', () => { loadService(traditionSelector.value); // Load default selected tradition + // Also apply language toggle to static intro section on load + const staticTerms = document.querySelectorAll('.intro-section .toggle-term'); + const showHe = hebrewToggle.checked; + staticTerms.forEach(t => { + const en = t.querySelector('.en'); + const he = t.querySelector('.he'); + if (en && he) { + en.style.display = showHe ? 'none' : 'inline'; + he.style.display = showHe ? 'inline' : 'none'; + } + }); }); </script> - </body> +</body> </html> \ No newline at end of file