Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,15 @@ Combines your avatar, role, bio, skills, and handle into a clean profile header.

<img src="https://readmeme.eu.cc/api/quote.svg?theme=ocean&quoteCategory=programming&label=Daily+Wisdom" alt="Quote Preview" />

### 6. Location & Timezone
### 6. Word of the Day

```md
![Word of the Day](https://readmeme.eu.cc/api/word.svg?theme=paper&label=Vocabulary&showOrigin=1)
```

<img src="https://readmeme.eu.cc/api/word.svg?theme=paper&label=Vocabulary&showOrigin=1" alt="Word of the Day Preview" />

### 7. Location & Timezone

```md
![Country Flag](https://readmeme.eu.cc/api/flag.svg?theme=forest&country=JP&label=Based+In)
Expand Down Expand Up @@ -285,6 +293,10 @@ Procedural sky & ocean widgets that shift color palettes dynamically based on yo
* `unit`
* `platform`

### Word of the Day

* `showOrigin`

## Local Development

Clone the repository:
Expand Down
10 changes: 9 additions & 1 deletion api/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const FALLBACK_AVATAR = `data:image/svg+xml;base64,${Buffer.from(`<svg xmlns="ht
* 60 s max-age keeps clocks fresh without hammering the origin.
* - date : Changes once per day; 1-hour cache is safe.
* - music : Static mock data; 5-minute cache balances freshness and load.
* - streak / quote / profile: Daily-changing or mostly-static data; 1-hour cache is appropriate.
* - streak / quote / word / profile: Daily-changing or mostly-static data; 1-hour cache is appropriate.
* - flag : Country data never changes; 24-hour cache maximises CDN hits.
*/
const CACHE_POLICIES = {
Expand All @@ -36,6 +36,13 @@ const CACHE_POLICIES = {
weather: 'public, max-age=1800, s-maxage=1800, stale-while-revalidate=600',

// Daily-change widgets — refresh every hour

date: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600',
quote: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600',
word: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600',
streak: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600',
profile: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600',

date: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600',
quote: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600',
streak: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600',
Expand All @@ -44,6 +51,7 @@ const CACHE_POLICIES = {
glass: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600',
countdown: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600',
marketplace: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600',

youtube: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600',

// Static mock content — refresh every 5 minutes
Expand Down
94 changes: 93 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ <h2 class="font-serif font-black text-5xl md:text-7xl leading-[0.95] tracking-ti
<div class="grid grid-cols-2 gap-3 text-center">
<div class="border-2 border-black p-3">
<div class="font-serif font-black text-3xl" id="statCount">
8
10
</div>
<div class="font-mono text-[10px] uppercase tracking-wider mt-1">
Widget Types
Expand Down Expand Up @@ -525,6 +525,7 @@ <h4 class="font-serif font-black text-3xl leading-none">Stylish Readme</h4>
{ id:'clock', label:'Digital Clock', icon:'watch' },
{ id:'date', label:'Date Stamp', icon:'calendar' },
{ id:'quote', label:'Daily Quote', icon:'quote' },
{ id:'word', label:'Word of the Day', icon:'book-open' },
{ id:'flag', label:'Country Flag', icon:'flag' },
{ id:'timezone',label:'Timezone Banner', icon:'globe' },
{ id:'streak', label:'Coding Streak', icon:'flame' },
Expand Down Expand Up @@ -729,6 +730,19 @@ <h4 class="font-serif font-black text-3xl leading-none">Stylish Readme</h4>
{ q:'Simplicity is the soul of efficiency.', a:'Austin Freeman' }
];

const WORDS = [
{ word:'Serendipity', pronunciation:'/ser-en-DIP-i-tee/', partOfSpeech:'Noun', definition:'The occurrence of finding something valuable or pleasant by chance.', example:'Meeting my mentor at the conference was pure serendipity.', synonyms:'chance, fortune, luck', origin:'Coined from The Three Princes of Serendip, a tale about accidental discoveries.' },
{ word:'Eloquent', pronunciation:'/EL-uh-kwent/', partOfSpeech:'Adjective', definition:'Fluent, expressive, and persuasive in speech or writing.', example:'Her eloquent README made the project feel instantly approachable.', synonyms:'expressive, articulate, persuasive', origin:'From Latin eloqui, meaning to speak out.' },
{ word:'Resilient', pronunciation:'/ri-ZIL-yent/', partOfSpeech:'Adjective', definition:'Able to recover quickly from difficulty or change.', example:'The resilient team shipped a cleaner fix after the outage.', synonyms:'adaptable, tough, flexible', origin:'From Latin resilire, meaning to spring back.' },
{ word:'Lucid', pronunciation:'/LOO-sid/', partOfSpeech:'Adjective', definition:'Clear, easy to understand, or rational.', example:'A lucid explanation can make complex code feel friendly.', synonyms:'clear, coherent, intelligible', origin:'From Latin lucidus, meaning bright or clear.' },
{ word:'Meticulous', pronunciation:'/meh-TIK-yuh-lus/', partOfSpeech:'Adjective', definition:'Showing great attention to detail.', example:'The meticulous review caught a subtle accessibility issue.', synonyms:'careful, precise, thorough', origin:'From Latin meticulosus, originally meaning fearful or timid.' },
{ word:'Ephemeral', pronunciation:'/ih-FEM-er-uhl/', partOfSpeech:'Adjective', definition:'Lasting for a very short time.', example:'The ephemeral preview refreshed as soon as the settings changed.', synonyms:'brief, fleeting, temporary', origin:'From Greek ephemeros, meaning lasting only a day.' },
{ word:'Pragmatic', pronunciation:'/prag-MAT-ik/', partOfSpeech:'Adjective', definition:'Focused on practical results and real-world usefulness.', example:'A pragmatic design kept the widget simple to customize.', synonyms:'practical, realistic, sensible', origin:'From Greek pragmatikos, meaning fit for action.' },
{ word:'Tenacious', pronunciation:'/tuh-NAY-shus/', partOfSpeech:'Adjective', definition:'Persistent and determined, especially when facing obstacles.', example:'Her tenacious debugging turned a vague error into a clear fix.', synonyms:'persistent, determined, steadfast', origin:'From Latin tenere, meaning to hold.' },
{ word:'Nuance', pronunciation:'/NOO-ahns/', partOfSpeech:'Noun', definition:'A subtle difference in meaning, feeling, or expression.', example:'Good documentation captures the nuance behind each option.', synonyms:'subtlety, shade, distinction', origin:'From French nuance, meaning shade or subtle variation.' },
{ word:'Candid', pronunciation:'/KAN-did/', partOfSpeech:'Adjective', definition:'Truthful, direct, and sincere.', example:'The candid changelog explained both the fix and the tradeoff.', synonyms:'honest, frank, open', origin:'From Latin candidus, meaning white or shining.' }
];

const SONGS = [
// Bollywood
{ title:'Tum Hi Ho', artist:'Arijit Singh', year:'2013', genre:'Bollywood' },
Expand Down Expand Up @@ -763,6 +777,8 @@ <h4 class="font-serif font-black text-3xl leading-none">Stylish Readme</h4>
country: 'IN',
// Quote
quoteCategory: 'programming',
// Word of the day
showOrigin: true,
// Clock style
clockStyle: 'digital',
// Streak
Expand Down Expand Up @@ -949,6 +965,27 @@ <h4 class="font-serif font-black text-3xl leading-none">Stylish Readme</h4>
</div>
`;
}
else if (state.widget==='word'){
html += `
<div class="mb-5">
<label class="label-tag">Theme</label>
${themeSwatches()}
</div>
<div class="mb-5">
<label class="label-tag">Header Label</label>
<input class="input-field" value="${state.label}" oninput="setField('label',this.value,false)" placeholder="e.g. Word of the Day"/>
</div>
<div class="mb-3 space-y-2">
<label class="flex items-center gap-3 cursor-pointer font-mono text-xs">
<input type="checkbox" ${state.showOrigin?'checked':''} onchange="setField('showOrigin',this.checked)" class="w-4 h-4 accent-black"/>
<span class="uppercase tracking-wider">Show word origin</span>
</label>
</div>
<div class="note-box">
<strong>Note:</strong> A new vocabulary word is selected each day with pronunciation, definition, example, and synonyms.
</div>
`;
}
else if (state.widget==='flag'){
html += `
<div class="mb-5">
Expand Down Expand Up @@ -1520,6 +1557,58 @@ <h4 class="font-serif font-black text-3xl leading-none">Stylish Readme</h4>
</svg>`;
}

function wordLines(text, maxLen, maxLines){
const words = String(text||'').split(/\s+/).filter(Boolean);
const lines=[]; let line='';
words.forEach(w=>{
if(lines.length>=maxLines) return;
if((line+' '+w).trim().length>maxLen){
if(line) lines.push(line.trim());
line=w;
} else {
line=(line+' '+w).trim();
}
});
if(line && lines.length<maxLines) lines.push(line.trim());
return lines;
}

function wordOfDay(){
const dayKey = Math.floor(Date.now()/86400000);
return WORDS[dayKey % WORDS.length];
}

function svgWord(){
const theme = themeObj(state.theme);
const borderStyle = theme.border?`stroke:${theme.fg};stroke-width:2;`:'';
const item = wordOfDay();
const width=560, height=270;
const definition = wordLines(item.definition, 48, 2);
const example = wordLines(item.example, 50, 2);
const origin = wordLines(item.origin, 58, 2);
return `
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" style="font-family:'JetBrains Mono',monospace;">
<rect x="1" y="1" width="${width-2}" height="${height-2}" fill="${theme.bg}" style="${borderStyle}"/>
<rect x="18" y="18" width="160" height="${height-36}" fill="${theme.fg}" opacity="0.08"/>
<text x="28" y="42" fill="${theme.fg}" font-size="9" font-weight="700" letter-spacing="2.4" opacity="0.65">${(state.label||'WORD OF THE DAY').toUpperCase()}</text>
<text x="28" y="86" fill="${theme.fg}" font-size="30" font-weight="800" font-family="Fraunces,serif">${item.word}</text>
<text x="30" y="112" fill="${theme.fg}" font-size="12" font-weight="600" opacity="0.78">${item.pronunciation}</text>
<rect x="28" y="132" width="112" height="24" fill="none" stroke="${theme.fg}" stroke-width="1.4" opacity="0.5"/>
<text x="84" y="148" text-anchor="middle" fill="${theme.fg}" font-size="9" font-weight="800" letter-spacing="1.5">${item.partOfSpeech.toUpperCase()}</text>
<line x1="198" y1="24" x2="198" y2="${height-24}" stroke="${theme.fg}" stroke-width="1" opacity="0.22"/>
<text x="220" y="38" fill="${theme.fg}" font-size="8" font-weight="800" letter-spacing="2" opacity="0.5">DEFINITION</text>
${definition.map((line,i)=>`<text x="220" y="${60+i*18}" fill="${theme.fg}" font-size="13" font-weight="600" opacity="0.88">${line}</text>`).join('')}
<text x="220" y="104" fill="${theme.fg}" font-size="8" font-weight="800" letter-spacing="2" opacity="0.5">EXAMPLE</text>
${example.map((line,i)=>`<text x="220" y="${126+i*18}" fill="${theme.fg}" font-size="12" font-weight="500" font-style="italic" opacity="0.82">${line}</text>`).join('')}
<text x="220" y="166" fill="${theme.fg}" font-size="8" font-weight="800" letter-spacing="2" opacity="0.5">SYNONYMS</text>
<text x="220" y="188" fill="${theme.fg}" font-size="11" font-weight="700" opacity="0.8">${item.synonyms}</text>
${state.showOrigin?`
<text x="220" y="216" fill="${theme.fg}" font-size="8" font-weight="800" letter-spacing="2" opacity="0.5">ORIGIN</text>
${origin.map((line,i)=>`<text x="220" y="${238+i*15}" fill="${theme.fg}" font-size="10" font-weight="500" opacity="0.72">${line}</text>`).join('')}
`:''}
</svg>`;
}

function svgFlag(){
const theme = themeObj(state.theme);
const country = COUNTRIES.find(c=>c.code===state.country) || COUNTRIES[0];
Expand Down Expand Up @@ -2739,6 +2828,7 @@ <h4 class="font-serif font-black text-3xl leading-none">Stylish Readme</h4>
case 'clock': return svgClock();
case 'date': return svgDate();
case 'quote': return svgQuote();
case 'word': return svgWord();
case 'flag': return svgFlag();
case 'timezone': return svgTimezone();
case 'streak': return svgStreak();
Expand Down Expand Up @@ -2767,6 +2857,7 @@ <h4 class="font-serif font-black text-3xl leading-none">Stylish Readme</h4>
clock: ['timezone','theme','timeFormat','showSeconds','label'],
date: ['timezone','theme','label'],
quote: ['theme','quoteCategory','label'],
word: ['theme','label','showOrigin'],
flag: ['country','theme','label'],
timezone: ['timezone','theme','timeFormat'],
streak: ['startDate','unit','theme','customLabel','platform'],
Expand Down Expand Up @@ -2845,6 +2936,7 @@ <h4 class="font-serif font-black text-3xl leading-none">Stylish Readme</h4>
{ title:'Tokyo Time', w:'time', overrides:{timezone:'Asia/Tokyo',theme:'terminal',label:'Tokyo'} },
{ title:'New York Clock', w:'clock', overrides:{timezone:'America/New_York',theme:'paper',label:'Eastern'} },
{ title:'Daily Quote', w:'quote', overrides:{theme:'crimson',label:'Today'} },
{ title:'Word of the Day', w:'word', overrides:{theme:'paper',label:'Vocabulary',showOrigin:true} },
{ title:'London Date', w:'date', overrides:{theme:'retro',timezone:'Europe/London',label:'Today'} },
{ title:'NYC Timezone', w:'timezone', overrides:{theme:'ocean',timezone:'America/New_York'} },
{ title:'India Flag', w:'flag', overrides:{country:'IN',theme:'forest',label:'From'} },
Expand Down
Loading