What it does
Subscribers pick a time for a daily reminder to take their product, saved to their calendar.
Why it exists
For any merchant whose product works best as a daily habit. One tap adds a recurring reminder to Google or Apple Calendar, with no app to install.
The code
Paste it as-is, or download daily-reminder.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>[BRAND] Daily Reminder Block</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Oswald:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
/* Scoped under #tl-reminder with tlrem- prefixed classes so this never
collides with theme CSS or with the separate 30-Day Challenge block —
these two are meant to run side by side on the same portal page.
(tl-reminder / tlrem- are just internal scoped selector names — not
tied to any brand — feel free to rename if you'd prefer different
prefixes for your own project.) */
#tl-reminder, #tl-reminder *{
box-sizing:border-box;
margin:0;
padding:0;
font-family:'Inter',-apple-system,BlinkMacSystemFont,sans-serif;
line-height:normal;
-webkit-font-smoothing:antialiased;
}
#tl-reminder{
--bg:#0a0a0b;
--panel:#141416;
--panel-2:#1c1c1f;
--line:#2a2a2e;
--ink:#f5f5f4;
--ink-dim:#a8a8ac;
--red:#e6293f;
--red-2:#ff4d5e;
--gold:#f4b93c;
--gold-2:#ffd873;
--green:#33d17a;
display:flex;
justify-content:center;
padding:24px 16px;
color:var(--ink);
}
#tl-reminder .tlrem-card{
width:100%;
max-width:520px;
background:linear-gradient(180deg, var(--panel) 0%, #0f0f11 100%);
border:1px solid var(--line);
border-radius:20px;
padding:28px 26px 30px;
position:relative;
overflow:hidden;
box-shadow:0 30px 60px -20px rgba(0,0,0,.6);
isolation:isolate;
}
#tl-reminder .tlrem-card::before{
content:"";
position:absolute;
inset:-40% -40% auto -40%;
height:260px;
background:radial-gradient(closest-side, rgba(244,185,60,.22), transparent 70%);
filter:blur(10px);
animation:tlremWash 6s ease-in-out infinite;
pointer-events:none;
z-index:0;
}
@keyframes tlremWash{
0%,100%{opacity:.5; transform:translateY(0);}
50%{opacity:.85; transform:translateY(8px);}
}
#tl-reminder .tlrem-eyebrow{
font-family:'Oswald','Arial Narrow',Impact,sans-serif;
font-size:11px;
letter-spacing:.16em;
text-transform:uppercase;
color:var(--gold-2);
font-weight:600;
display:flex;
align-items:center;
gap:8px;
position:relative;
z-index:1;
}
#tl-reminder .tlrem-dot{width:6px;height:6px;border-radius:50%;background:var(--gold-2);box-shadow:0 0 8px var(--gold-2); flex:none;}
#tl-reminder .tlrem-bell{
position:relative;
z-index:1;
font-size:26px;
display:inline-block;
margin:14px 0 2px;
animation:tlremRing 3.4s ease-in-out infinite;
transform-origin:50% 0%;
}
@keyframes tlremRing{
0%, 6%, 100%{ transform:rotate(0deg); }
1%{ transform:rotate(-14deg); }
2%{ transform:rotate(11deg); }
3%{ transform:rotate(-8deg); }
4%{ transform:rotate(5deg); }
5%{ transform:rotate(-2deg); }
}
#tl-reminder .tlrem-title{
font-family:'Oswald','Arial Narrow',Impact,sans-serif;
font-size:26px;
font-weight:700;
line-height:1.2;
letter-spacing:.01em;
margin:8px 0 6px;
position:relative;
z-index:1;
}
#tl-reminder .tlrem-title em{color:var(--gold-2); font-style:normal;}
#tl-reminder .tlrem-sub{
color:var(--ink-dim);
font-size:14px;
line-height:1.5;
margin:0 0 24px;
max-width:400px;
position:relative;
z-index:1;
}
/* ---- time-of-day quick presets ---- */
#tl-reminder .tlrem-section-label{
font-family:'Oswald','Arial Narrow',Impact,sans-serif;
font-size:11px;
font-weight:600;
letter-spacing:.1em;
text-transform:uppercase;
color:var(--ink-dim);
margin-bottom:10px;
position:relative;
z-index:1;
}
#tl-reminder .tlrem-presets{
position:relative;
z-index:1;
display:flex;
gap:10px;
margin-bottom:22px;
}
#tl-reminder button.tlrem-preset{
-webkit-appearance:none;
appearance:none;
flex:1;
display:flex;
align-items:center;
justify-content:center;
gap:8px;
font-family:'Oswald','Arial Narrow',Impact,sans-serif;
font-size:13px;
font-weight:600;
letter-spacing:.03em;
text-transform:uppercase;
color:var(--ink-dim);
background:rgba(245,245,244,.04) !important;
border:1px solid var(--line) !important;
padding:12px 10px !important;
border-radius:12px !important;
cursor:pointer;
transition:border-color .15s ease, color .15s ease, background .15s ease;
}
#tl-reminder button.tlrem-preset span.tlrem-emoji{font-size:15px;}
#tl-reminder button.tlrem-preset.tlrem-active{
color:var(--ink) !important;
background:linear-gradient(160deg, rgba(244,185,60,.16), rgba(230,41,63,.08)) !important;
border-color:rgba(244,185,60,.5) !important;
}
/* ---- time picker ---- */
#tl-reminder .tlrem-time-row{
position:relative;
z-index:1;
display:flex;
align-items:center;
justify-content:space-between;
gap:14px;
background:var(--panel-2);
border:1px solid var(--line);
border-radius:14px;
padding:16px 18px;
margin-bottom:8px;
}
#tl-reminder .tlrem-time-label{
font-size:13.5px;
color:var(--ink-dim);
}
#tl-reminder .tlrem-time-label b{
display:block;
color:var(--ink);
font-family:'Oswald','Arial Narrow',Impact,sans-serif;
font-size:15px;
font-weight:600;
letter-spacing:.01em;
margin-top:2px;
}
#tl-reminder input.tlrem-time-input{
-webkit-appearance:none;
appearance:none;
font-family:'Oswald','Arial Narrow',Impact,sans-serif;
font-size:20px;
font-weight:600;
letter-spacing:.02em;
color:var(--ink) !important;
background:var(--panel) !important;
border:1px solid var(--line) !important;
border-radius:10px !important;
padding:10px 14px !important;
outline:none;
color-scheme:dark;
}
#tl-reminder input.tlrem-time-input:focus{
border-color:rgba(244,185,60,.6) !important;
}
#tl-reminder .tlrem-repeats{
position:relative;
z-index:1;
display:flex;
align-items:center;
gap:6px;
font-size:12px;
color:var(--ink-dim);
margin:0 0 24px 2px;
}
#tl-reminder .tlrem-repeats .tlrem-repeats-dot{
width:5px; height:5px; border-radius:50%; background:var(--green); flex:none;
box-shadow:0 0 6px var(--green);
}
/* ---- CTA buttons ---- */
#tl-reminder .tlrem-actions{
position:relative;
z-index:1;
display:flex;
gap:10px;
flex-wrap:wrap;
}
#tl-reminder button.tlrem-btn-primary,
#tl-reminder button.tlrem-btn-secondary{
-webkit-appearance:none;
appearance:none;
flex:1;
min-width:180px;
display:flex;
align-items:center;
justify-content:center;
gap:8px;
font-family:'Oswald','Arial Narrow',Impact,sans-serif;
font-size:13.5px;
font-weight:600;
letter-spacing:.03em;
text-transform:uppercase;
padding:13px 16px !important;
border-radius:12px !important;
cursor:pointer;
line-height:normal;
}
#tl-reminder button.tlrem-btn-primary{
color:#1a1206;
background:linear-gradient(180deg, var(--gold-2), var(--gold)) !important;
border:none !important;
box-shadow:0 8px 20px -6px rgba(244,185,60,.55);
}
#tl-reminder button.tlrem-btn-secondary{
color:var(--ink);
background:rgba(245,245,244,.05) !important;
border:1px solid var(--line) !important;
}
/* ---- confirmation strip ---- */
#tl-reminder .tlrem-confirm{
position:relative;
z-index:1;
display:flex;
align-items:center;
gap:10px;
margin-top:16px;
padding:13px 16px;
border-radius:12px;
background:rgba(51,209,122,.1);
border:1px solid rgba(51,209,122,.35);
font-size:13.5px;
color:var(--ink);
animation:tlremConfirmIn .35s ease both;
}
#tl-reminder .tlrem-confirm[hidden]{ display:none; }
@keyframes tlremConfirmIn{
0%{opacity:0; transform:translateY(-4px);}
100%{opacity:1; transform:translateY(0);}
}
#tl-reminder .tlrem-confirm-check{
width:20px; height:20px; border-radius:50%;
background:var(--green);
color:#08210f;
display:flex; align-items:center; justify-content:center;
font-size:12px; font-weight:700;
flex:none;
}
#tl-reminder .tlrem-confirm b{ color:var(--gold-2); }
#tl-reminder .tlrem-privacy{
position:relative;
z-index:1;
font-size:11.5px;
color:var(--ink-dim);
margin-top:16px;
line-height:1.5;
}
</style>
</head>
<body style="margin:0; background:#0a0a0b;">
<div id="tl-reminder">
<div class="tlrem-card">
<div class="tlrem-eyebrow"><span class="tlrem-dot"></span> [BRAND] · DAILY REMINDER</div>
<div class="tlrem-bell" aria-hidden="true">🔔</div>
<div class="tlrem-title">Never miss a dose. <em>Set it once.</em></div>
<p class="tlrem-sub">Pick a time and we'll add a daily reminder to your calendar — no app to open, no code to remember.</p>
<div class="tlrem-section-label">Time of day</div>
<div class="tlrem-presets">
<button type="button" class="tlrem-preset tlrem-active" id="tlremPresetMorning">
<span class="tlrem-emoji">🌅</span> Morning
</button>
<button type="button" class="tlrem-preset" id="tlremPresetEvening">
<span class="tlrem-emoji">🌙</span> Evening
</button>
</div>
<div class="tlrem-time-row">
<div class="tlrem-time-label">
Reminder time
<b id="tlremTimeDisplay">8:00 AM</b>
</div>
<input type="time" class="tlrem-time-input" id="tlremTimeInput" value="08:00" step="60">
</div>
<div class="tlrem-repeats"><span class="tlrem-repeats-dot"></span> Repeats every day</div>
<div class="tlrem-actions">
<button type="button" class="tlrem-btn-primary" id="tlremGoogleBtn">📅 Add to Google Calendar</button>
<button type="button" class="tlrem-btn-secondary" id="tlremIcsBtn">⬇ Download .ics (Apple / Outlook)</button>
</div>
<div class="tlrem-confirm" id="tlremConfirm" hidden>
<span class="tlrem-confirm-check">✓</span>
<span id="tlremConfirmText">Daily reminder set for <b>8:00 AM</b>.</span>
</div>
<p class="tlrem-privacy">This creates a recurring event on your own device's calendar. [BRAND] doesn't store, see, or get notified about your reminder.</p>
</div>
</div>
<script>
(function () {
var timeInput = document.getElementById('tlremTimeInput');
var timeDisplay = document.getElementById('tlremTimeDisplay');
var morningBtn = document.getElementById('tlremPresetMorning');
var eveningBtn = document.getElementById('tlremPresetEvening');
var googleBtn = document.getElementById('tlremGoogleBtn');
var icsBtn = document.getElementById('tlremIcsBtn');
var confirmEl = document.getElementById('tlremConfirm');
var confirmText = document.getElementById('tlremConfirmText');
var EVENT_TITLE = 'Take your [BRAND] supplement';
var EVENT_DETAILS = 'Your daily reminder to take your [BRAND] product. Consistency is the whole game.';
var EVENT_DURATION_MINUTES = 10;
function pad(n) { return String(n).padStart(2, '0'); }
function parseTimeInput() {
var val = timeInput.value || '08:00';
var parts = val.split(':');
return { h: parseInt(parts[0], 10) || 0, m: parseInt(parts[1], 10) || 0 };
}
function formatTime12h(h, m) {
var period = h >= 12 ? 'PM' : 'AM';
var h12 = h % 12;
if (h12 === 0) h12 = 12;
return h12 + ':' + pad(m) + ' ' + period;
}
function updateTimeDisplay() {
var t = parseTimeInput();
timeDisplay.textContent = formatTime12h(t.h, t.m);
}
function setActivePreset(which) {
morningBtn.classList.toggle('tlrem-active', which === 'morning');
eveningBtn.classList.toggle('tlrem-active', which === 'evening');
}
morningBtn.addEventListener('click', function () {
timeInput.value = '08:00';
setActivePreset('morning');
updateTimeDisplay();
});
eveningBtn.addEventListener('click', function () {
timeInput.value = '20:00';
setActivePreset('evening');
updateTimeDisplay();
});
timeInput.addEventListener('input', function () {
setActivePreset(null); // manual edit — neither preset is "selected" anymore
updateTimeDisplay();
});
/* ------------------------------------------------------------------
Build start/end Date objects for the chosen time, today, in the
customer's own local time. The .ics event is written as a
"floating" local time (no trailing Z, no VTIMEZONE) — the
standard, simplest choice for a personal daily reminder: whatever
device it's imported on shows it at that device's local time,
which is exactly what someone picking "8:00 AM" expects.
------------------------------------------------------------------ */
function buildStartEnd(h, m) {
var now = new Date();
var start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), h, m, 0);
var end = new Date(start.getTime() + EVENT_DURATION_MINUTES * 60000);
return { start: start, end: end };
}
function tsLocal(date) {
return '' + date.getFullYear() + pad(date.getMonth() + 1) + pad(date.getDate()) +
'T' + pad(date.getHours()) + pad(date.getMinutes()) + '00';
}
function tsUTC(date) {
return '' + date.getUTCFullYear() + pad(date.getUTCMonth() + 1) + pad(date.getUTCDate()) +
'T' + pad(date.getUTCHours()) + pad(date.getUTCMinutes()) + pad(date.getUTCSeconds()) + 'Z';
}
function buildICS(h, m) {
var range = buildStartEnd(h, m);
var uid = 'brand-daily-reminder-' + Date.now() + '@example.com'; // TODO: swap to [BRAND]'s real domain
var lines = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//[BRAND]//Daily Reminder//EN',
'CALSCALE:GREGORIAN',
'METHOD:PUBLISH',
'BEGIN:VEVENT',
'UID:' + uid,
'DTSTAMP:' + tsUTC(new Date()),
'DTSTART:' + tsLocal(range.start),
'DTEND:' + tsLocal(range.end),
'RRULE:FREQ=DAILY',
'SUMMARY:' + EVENT_TITLE,
'DESCRIPTION:' + EVENT_DETAILS,
'BEGIN:VALARM',
'ACTION:DISPLAY',
'DESCRIPTION:' + EVENT_TITLE,
'TRIGGER:PT0M',
'END:VALARM',
'END:VEVENT',
'END:VCALENDAR'
];
return lines.join('\r\n');
}
function downloadICS() {
var t = parseTimeInput();
var ics = buildICS(t.h, t.m);
var blob = new Blob([ics], { type: 'text/calendar;charset=utf-8' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'brand-daily-reminder.ics'; // TODO: rename to [BRAND]'s actual filename convention
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(function () { URL.revokeObjectURL(url); }, 4000);
showConfirmation(t.h, t.m);
}
function googleCalendarUrl(h, m) {
var range = buildStartEnd(h, m);
var params = new URLSearchParams({
action: 'TEMPLATE',
text: EVENT_TITLE,
dates: tsLocal(range.start) + '/' + tsLocal(range.end),
details: EVENT_DETAILS,
recur: 'RRULE:FREQ=DAILY'
});
return 'https://calendar.google.com/calendar/render?' + params.toString();
}
function addToGoogle() {
var t = parseTimeInput();
window.open(googleCalendarUrl(t.h, t.m), '_blank', 'noopener');
showConfirmation(t.h, t.m);
}
function showConfirmation(h, m) {
confirmText.innerHTML = 'Daily reminder set for <b>' + formatTime12h(h, m) + '</b>.';
confirmEl.hidden = false;
}
googleBtn.addEventListener('click', addToGoogle);
icsBtn.addEventListener('click', downloadICS);
updateTimeDisplay();
})();
</script>
</body>
</html>
How to install
- Copy the code above, or download the .html file.
- Go to
Storefront→Customer Portal→Branding→Layoutand clickAdd custom extension. - Pick
HTML with embedded CSS and JavaScriptand paste the whole file into the code editor. - Swap anything the file asks you to, like
[BRAND], product names or Quick Action IDs. The AI prompt below can walk you through it. - Save, click
+to add it to the layout, thenPublish. Check it on a test subscription’s magic link with?forcePreview=true, since some scripts don’t run in Preview.
Replace [BRAND] in the copy and in the calendar event. It is designed to sit next to the Challenge Tracker on the same page.
Customize it with AI
Paste this prompt into Claude Code or Codex. It asks for the code and your brand and product details, then writes a version for your store.
I want to add a custom extension from customizeyourportal.xyz to my subscription customer portal. Walk me through customizing it for my brand, then write the finished code. EXTENSION Name: Daily Reminder (No. 11) Page: /blocks/daily-reminder What it does: Subscribers pick a time for a daily reminder to take their product, saved to their calendar. My portal app: Skio WHAT THE BUILDER SAYS TO CUSTOMIZE Replace [BRAND] in the copy and in the calendar event. It is designed to sit next to the Challenge Tracker on the same page. HOW TO WORK 1. Ask me to paste the full code (the "Copy code" button on the page) or to give you the path to the downloaded daily-reminder.html. Wait until you have it. 2. Read the whole file. List every value that is specific to the original brand or needs my input: brand name and [PLACEHOLDERS], product and variant names or IDs, prices, discount codes and reward amounts, Quick Action IDs or tokens, store domains and API endpoints, colors, fonts, logos and images, and any copy that names the original brand, products or offers. Include everything in the builder's notes above. 3. Ask me for those values in one numbered list, with the current value next to each, so I can answer or say "keep". If you can open web pages, offer to pull my brand colors, fonts, logo and product names from my store's URL. 4. Don't invent stats, reviews, IDs, prices or claims. If I don't have a value, leave a clearly marked TODO in the code and tell me. 5. Write the customized version as daily-reminder-<my-brand>.html. Change only what my brand needs. Keep the logic, behavior and structure the same, and keep it one self-contained file (HTML, <style> and <script>). 6. Finish with a short list of what you changed, any TODOs left, and these install steps for Skio: 1. Go to `Storefront` → `Customer Portal` → `Branding` → `Layout` and click `Add custom extension`. 2. Pick `HTML with embedded CSS and JavaScript` and paste the whole file into the code editor. 3. Save, click `+` to add it to the layout, then `Publish`. Check it on a test subscription’s magic link with `?forcePreview=true`, since some scripts don’t run in Preview.
Blocks are shared as-is by the people who built them. Read the code before you add it to your portal, and test it with a test subscriber first.
