Skip to content

Instantly share code, notes, and snippets.

@mjradwin
Last active April 14, 2026 13:47
Show Gist options
  • Select an option

  • Save mjradwin/b33fbd222ab9bbe84d7eff81295fdc4a to your computer and use it in GitHub Desktop.

Select an option

Save mjradwin/b33fbd222ab9bbe84d7eff81295fdc4a to your computer and use it in GitHub Desktop.
Omer Counter for Tel Aviv
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sefirat HaOmer</title>
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;0,700;1,400&family=DM+Sans:wght@400;500;600&family=Frank+Ruhl+Libre:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--gold: #c9a84c;
--gold-light: #e8d5a0;
--gold-dim: #8a7033;
--bg-deep: #0c0a14;
--bg-card: #13111d;
--bg-card-border: #1e1a2e;
--text-primary: #e8e4d9;
--text-secondary: #9a9488;
--text-dim: #5c5750;
--accent-blue: #4a6fa5;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg-deep);
color: var(--text-primary);
font-family: 'DM Sans', sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
body::before {
content: '';
position: fixed;
inset: 0;
background:
radial-gradient(ellipse 600px 400px at 50% 30%, rgba(201,168,76,0.06) 0%, transparent 70%),
radial-gradient(ellipse 400px 600px at 80% 70%, rgba(74,111,165,0.04) 0%, transparent 70%);
pointer-events: none;
}
.container {
width: 100%;
max-width: 520px;
padding: 24px;
position: relative;
z-index: 1;
}
.loading-state, .error-state, .content {
text-align: center;
}
.loading-state { padding: 80px 0; }
.spinner {
width: 32px; height: 32px;
border: 2px solid var(--bg-card-border);
border-top-color: var(--gold);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loading-state p {
color: var(--text-secondary);
font-size: 14px;
letter-spacing: 0.05em;
}
.error-state {
padding: 60px 20px;
color: var(--text-secondary);
}
.error-state .err-icon { font-size: 32px; margin-bottom: 16px; }
.error-state p { line-height: 1.6; font-size: 15px; }
/* Header */
.header {
margin-bottom: 32px;
opacity: 0;
animation: fadeUp 0.6s ease forwards;
}
.header .subtitle {
font-family: 'DM Sans', sans-serif;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--gold-dim);
margin-bottom: 8px;
}
.header h1 {
font-family: 'Cormorant Garamond', serif;
font-size: 42px;
font-weight: 700;
color: var(--text-primary);
line-height: 1.1;
}
.header .hdate {
font-family: 'DM Sans', sans-serif;
font-size: 13px;
color: var(--text-secondary);
margin-top: 8px;
}
/* Omer Count Card */
.omer-count-card {
background: var(--bg-card);
border: 1px solid var(--bg-card-border);
border-radius: 16px;
padding: 36px 28px;
margin-bottom: 20px;
position: relative;
overflow: hidden;
opacity: 0;
animation: fadeUp 0.6s ease 0.15s forwards;
}
.omer-count-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, var(--gold-dim), transparent);
}
.day-number {
font-family: 'Cormorant Garamond', serif;
font-size: 96px;
font-weight: 700;
color: var(--gold);
line-height: 1;
margin-bottom: 4px;
}
.day-label {
font-size: 14px;
font-weight: 500;
color: var(--text-secondary);
letter-spacing: 0.05em;
margin-bottom: 20px;
}
.weeks-breakdown {
display: inline-block;
background: rgba(201,168,76,0.08);
border: 1px solid rgba(201,168,76,0.15);
border-radius: 8px;
padding: 8px 16px;
font-size: 13px;
color: var(--gold-light);
}
/* Hebrew Count */
.hebrew-section {
background: var(--bg-card);
border: 1px solid var(--bg-card-border);
border-radius: 16px;
padding: 28px;
margin-bottom: 20px;
opacity: 0;
animation: fadeUp 0.6s ease 0.3s forwards;
}
.hebrew-section .section-label {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--text-dim);
margin-bottom: 14px;
}
.hebrew-text {
font-family: 'Frank Ruhl Libre', serif;
font-size: 20px;
line-height: 1.7;
color: var(--text-primary);
direction: rtl;
text-align: center;
}
.english-count {
font-size: 14px;
color: var(--text-secondary);
margin-top: 14px;
line-height: 1.5;
text-align: center;
}
/* Sefira Card */
.sefira-card {
background: var(--bg-card);
border: 1px solid var(--bg-card-border);
border-radius: 16px;
padding: 28px;
margin-bottom: 20px;
opacity: 0;
animation: fadeUp 0.6s ease 0.45s forwards;
}
.sefira-card .section-label {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--text-dim);
margin-bottom: 16px;
}
.sefira-hebrew {
font-family: 'Frank Ruhl Libre', serif;
font-size: 28px;
color: var(--gold);
text-align: center;
margin-bottom: 6px;
}
.sefira-translit {
font-family: 'Cormorant Garamond', serif;
font-style: italic;
font-size: 18px;
color: var(--text-secondary);
text-align: center;
margin-bottom: 4px;
}
.sefira-english {
font-size: 14px;
color: var(--text-dim);
text-align: center;
}
/* Detail pills */
.detail-row {
display: flex;
gap: 12px;
opacity: 0;
animation: fadeUp 0.6s ease 0.6s forwards;
}
.detail-pill {
flex: 1;
background: var(--bg-card);
border: 1px solid var(--bg-card-border);
border-radius: 12px;
padding: 18px 14px;
text-align: center;
}
.detail-pill .pill-label {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--text-dim);
margin-bottom: 8px;
}
.detail-pill .pill-hebrew {
font-family: 'Frank Ruhl Libre', serif;
font-size: 22px;
color: var(--text-primary);
}
.detail-pill .pill-sub {
font-size: 11px;
color: var(--text-secondary);
margin-top: 4px;
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@media (max-width: 480px) {
.day-number { font-size: 72px; }
.header h1 { font-size: 34px; }
.hebrew-text { font-size: 17px; }
}
</style>
</head>
<body>
<div class="container">
<div id="loading" class="loading-state">
<div class="spinner"></div>
<p>Checking zmanim for Tel Aviv…</p>
</div>
<div id="error" class="error-state" style="display:none;"></div>
<div id="content" class="content" style="display:none;"></div>
</div>
<script>
const GEONAME_ID = 293397; // Tel Aviv
function pad(n) { return String(n).padStart(2, '0'); }
function formatDate(d) {
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}
function showError(msg) {
document.getElementById('loading').style.display = 'none';
const el = document.getElementById('error');
el.innerHTML = `<div class="err-icon">✦</div><p>${msg}</p>`;
el.style.display = 'block';
}
async function run() {
try {
// Step 1: Get today's date and fetch zmanim
const now = new Date();
const todayStr = formatDate(now);
const zmanimUrl = `https://www.hebcal.com/zmanim?cfg=json&geonameid=${GEONAME_ID}&date=${todayStr}`;
const zmanimRes = await fetch(zmanimUrl);
if (!zmanimRes.ok) throw new Error('Failed to fetch zmanim');
const zmanimData = await zmanimRes.json();
// Step 2: Compare tzeit7083deg to current time
const tzeitStr = zmanimData?.times?.tzeit7083deg;
if (!tzeitStr) throw new Error('tzeit7083deg not found in zmanim response');
const tzeitTime = new Date(tzeitStr);
let targetDate;
if (now >= tzeitTime) {
// After tzeit — it's already the next Jewish day, use tomorrow's civil date
const tomorrow = new Date(now);
tomorrow.setDate(tomorrow.getDate() + 1);
targetDate = formatDate(tomorrow);
} else {
// Before tzeit — still today's Jewish day
targetDate = todayStr;
}
// Step 3: Fetch hebcal events for that date
document.querySelector('#loading p').textContent = 'Fetching Omer count…';
const hebcalUrl = `https://www.hebcal.com/hebcal?v=1&cfg=json&i=on&o=on&start=${targetDate}&end=${targetDate}`;
const hebcalRes = await fetch(hebcalUrl);
if (!hebcalRes.ok) throw new Error('Failed to fetch hebcal data');
const hebcalData = await hebcalRes.json();
// Step 4: Find the omer item
const omerItem = hebcalData.items?.find(i => i.category === 'omer');
if (!omerItem) {
showError('No Omer count found for this date. The Omer is counted between Pesach and Shavuot.');
return;
}
// Step 5: Render
render(omerItem, targetDate);
} catch (err) {
console.error(err);
showError(`Something went wrong: ${err.message}`);
}
}
function render(item, dateStr) {
const omer = item.omer;
const count = omer.count;
const sefira = omer.sefira;
const dayNum = item.title_orig.replace('Omer ', '');
document.getElementById('loading').style.display = 'none';
const el = document.getElementById('content');
el.style.display = 'block';
el.innerHTML = `
<div class="header">
<div class="subtitle">Sefirat HaOmer</div>
<h1>${item.title}</h1>
<div class="hdate">${item.hdate} · ${dateStr}</div>
</div>
<div class="omer-count-card">
<div class="day-number">${dayNum}</div>
<div class="day-label">days of the Omer</div>
<div class="weeks-breakdown">${count.en.replace(/^Today is /, '')}</div>
</div>
<div class="hebrew-section">
<div class="section-label">Hebrew Count</div>
<div class="hebrew-text">${count.he}</div>
<div class="english-count">${count.en}</div>
</div>
<div class="sefira-card">
<div class="section-label">Sefira of the Day</div>
<div class="sefira-hebrew">${sefira.he}</div>
<div class="sefira-translit">${sefira.translit}</div>
<div class="sefira-english">${sefira.en}</div>
</div>
<div class="detail-row">
<div class="detail-pill">
<div class="pill-label">Ana BeKoach</div>
<div class="pill-hebrew">${omer.anaBekoachWord}</div>
</div>
<div class="detail-pill">
<div class="pill-label">LaMnatzeach</div>
<div class="pill-hebrew">${omer.lamnatzeachLetter}</div>
<div class="pill-sub">${omer.lamnatzeachWord}</div>
</div>
</div>
`;
}
run();
</script>
</body>
</html>
Write me a short HTML page that uses JavaScript client-side fetch() to first look up the time of tzeit7083deg for Tel Aviv using an API call like this:
https://www.hebcal.com/zmanim?cfg=json&geonameid=293397&date=YYYY-MM-DD
Then, compare the time returned to now. If tzeit7083deg >= now, add +1 to the date for tomorrow's date. if now is < tzeit7083deg, we can keep today's YYYY-MM-DD as it is.
We will use this time to create a second API call like the following with the YYYY-MM-DD
https://www.hebcal.com/hebcal?v=1&cfg=json&i=on&o=on&start=2026-04-14&end=2026-04-14
Where the start and end parameters are the same YYYY-MM-DD
The second API will return a result like
```
{
"title": "Hebcal Israel April 2026",
"date": "2026-04-14T13:38:26.230Z",
"version": "6.3.0-3.6.4",
"location": {
"geo": "none"
},
"range": {
"start": "2026-04-14",
"end": "2026-04-14"
},
"items": [
{
"title": "12th day of the Omer",
"date": "2026-04-14",
"hdate": "27 Nisan 5786",
"category": "omer",
"title_orig": "Omer 12",
"hebrew": "עומר יום 12",
"link": "https://hebcal.com/o/5786/12?i=on&us=js&um=api",
"omer": {
"count": {
"he": "הַיּוֹם שְׁנֵים עָשָׂר יוֹם, שֶׁהֵם שָׁבֽוּעַ אֶחָד וַחֲמִשָּׁה יָמִים לָעֽוֹמֶר",
"en": "Today is 12 days, which are 1 week and 5 days of the Omer"
},
"sefira": {
"he": "הוֹד שֶׁבִּגְבוּרָה",
"translit": "Hod shebiGevurah",
"en": "Splendor within Might"
},
"anaBekoachWord": "טַהֲרֵנוּ",
"lamnatzeachWord": "גּוֹיִם",
"lamnatzeachLetter": "ל"
}
}
]
}
```
Then display the returned Omer count and related details in HTML using some simple formatting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment