From 2545a1ecb33e8c71efc7e3a464f2b5e48b01c796 Mon Sep 17 00:00:00 2001 From: djg Date: Mon, 21 Sep 2026 22:39:09 +0000 Subject: [PATCH] chore: remove site code (now in BuzzParty/{bestdadjokes,helpme-tips,myadhd}) --- adhd/deploy.sh | 18 - bdj/build.py | 570 ------------------------------ bdj/content/jokes.json | 362 ------------------- bdj/deploy.sh | 41 --- bdj/k8s/letsencrypt-issuer.yaml | 25 -- bdj/k8s/static-sites.yaml | 262 -------------- bdj/k8s/traefik.yaml | 113 ------ bdj/static/favicon.svg | 6 - bdj/static/main.js | 235 ------------ bdj/static/style.css | 169 --------- helpme-tips/build.py | 298 ---------------- helpme-tips/content/articles.json | 320 ----------------- helpme-tips/deploy.sh | 24 -- helpme-tips/k8s/helpme-tips.yaml | 116 ------ helpme-tips/static/favicon.svg | 5 - helpme-tips/static/main.js | 5 - helpme-tips/static/style.css | 77 ---- 17 files changed, 2646 deletions(-) delete mode 100755 adhd/deploy.sh delete mode 100644 bdj/build.py delete mode 100644 bdj/content/jokes.json delete mode 100755 bdj/deploy.sh delete mode 100644 bdj/k8s/letsencrypt-issuer.yaml delete mode 100644 bdj/k8s/static-sites.yaml delete mode 100644 bdj/k8s/traefik.yaml delete mode 100644 bdj/static/favicon.svg delete mode 100644 bdj/static/main.js delete mode 100644 bdj/static/style.css delete mode 100644 helpme-tips/build.py delete mode 100644 helpme-tips/content/articles.json delete mode 100755 helpme-tips/deploy.sh delete mode 100644 helpme-tips/k8s/helpme-tips.yaml delete mode 100644 helpme-tips/static/favicon.svg delete mode 100644 helpme-tips/static/main.js delete mode 100644 helpme-tips/static/style.css diff --git a/adhd/deploy.sh b/adhd/deploy.sh deleted file mode 100755 index caab93b..0000000 --- a/adhd/deploy.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -# Build (nothing to build yet โ€” site is static) + sync docroot in place. -set -euo pipefail -cd "$(dirname "$0")" -BASE=/home/opc/zai-home-base - -mkdir -p "$BASE/sites/myadhd.dev" -rsync -a --delete site/ "$BASE/sites/myadhd.dev/" - -echo "==> verifying through Traefik (origin)" -sleep 1 -code=$(curl -sk -o /dev/null -w '%{http_code}' --resolve myadhd.dev:443:127.0.0.1 https://myadhd.dev/ --max-time 10) -echo " https://myadhd.dev -> $code (origin)" -for asset in /static/style.css /static/app.js /static/favicon.svg /robots.txt /sitemap.xml; do - out=$(curl -sk -o /dev/null -w '%{http_code} %{content_type}' --resolve myadhd.dev:443:127.0.0.1 "https://myadhd.dev$asset" --max-time 10) - echo " $asset -> $out" -done -echo "==> deploy complete" diff --git a/bdj/build.py b/bdj/build.py deleted file mode 100644 index ff5c365..0000000 --- a/bdj/build.py +++ /dev/null @@ -1,570 +0,0 @@ -#!/usr/bin/env python3 -"""Static site generator for bestdadjokes.lol. Python 3.6 stdlib only.""" -import json, os, re, shutil, html, datetime - -ROOT = os.path.dirname(os.path.abspath(__file__)) -CONTENT = os.path.join(ROOT, 'content', 'jokes.json') -SITE_DIR = os.path.join(ROOT, 'site') -STATIC = os.path.join(ROOT, 'static') - -SITE_URL = 'https://bestdadjokes.lol' -SITE_NAME = 'Best Dad Jokes' -TAGLINE = 'The best dad jokes on the internet โ€” short, clean, and groan-worthy.' -YEAR = datetime.date.today().year - -with open(CONTENT) as f: - DATA = json.load(f) -CATS = DATA['categories'] -JOKES = DATA['jokes'] -CAT_BY_SLUG = {c['slug']: c for c in CATS} -for i, j in enumerate(JOKES): - j['id'] = i - -# Long-tail topic pages: jokes matched by keyword across all categories. -TOPICS = [ - {"slug": "coffee", "name": "Dad Jokes About Coffee", "emoji": "โ˜•", - "intro": "Fuel for dads and their worst puns alike. These coffee dad jokes are best served strong, black, and with a healthy amount of eye-rolling.", - "keywords": r"\b(coffee|espresso|latte|caffeine|beans?|mugs?)\b"}, - {"slug": "cats", "name": "Dad Jokes About Cats", "emoji": "๐Ÿฑ", - "intro": "For the cat dads out there. These cat dad jokes will make your feline walk out of the room in disgust โ€” which, for a cat, is a standing ovation.", - "keywords": r"\b(cats?|kittens?|purr|feline|meow)\b"}, - {"slug": "dogs", "name": "Dad Jokes About Dogs", "emoji": "๐Ÿ•", - "intro": "A dog is the only audience that wags at dad jokes. Fetch! These dog dad jokes are good boys, every one of them.", - "keywords": r"\b(dogs?|puppy|puppies|pup|hound)\b"}, - {"slug": "cows", "name": "Dad Jokes About Cows", "emoji": "๐Ÿ„", - "intro": "Outstanding in their field and in our pun archive. These cow dad jokes are udderly relentless.", - "keywords": r"\b(cows?|cattle|moo|udder|beef|herd)\b"}, - {"slug": "pizza", "name": "Dad Jokes About Pizza", "emoji": "๐Ÿ•", - "intro": "A slice of comedy gold. These pizza dad jokes are cheesy by design โ€” that's not a bug, it's the whole point.", - "keywords": r"\bpizzas?\b"}, - {"slug": "money", "name": "Dad Jokes About Money", "emoji": "๐Ÿ’ฐ", - "intro": "Money can't buy happiness, but it can buy these money dad jokes, which are basically the same thing but cheaper.", - "keywords": r"\b(money|cash|coins?|dollars?|penn(y|ies)|bank|rich|spen[dt]|salary)\b"}, - {"slug": "sleep", "name": "Dad Jokes About Sleep", "emoji": "๐Ÿ˜ด", - "intro": "Read these sleep dad jokes in bed. Groaning yourself to sleep counts as a lullaby.", - "keywords": r"\b(sleep|nap|bed|pillow|blanket|tired|asleep|dream)\b"}, - {"slug": "love", "name": "Dad Jokes About Love", "emoji": "โค๏ธ", - "intro": "Romance, dad-style. These love dad jokes have walked someone down the aisle against their will and lived to pun about it.", - "keywords": r"\b(love|romantic|valentine|marri(ed|age)|attract|wife|husband|buoy)\b"}, - {"slug": "birthday", "name": "Birthday Dad Jokes", "emoji": "๐ŸŽ‚", - "intro": "Because every birthday needs at least one groan with the cake. These birthday dad jokes are tradition now, sorry.", - "keywords": r"\b(birthday|cake|candles?|party|presents?)\b"}, - {"slug": "winter", "name": "Winter Dad Jokes", "emoji": "โ›„", - "intro": "These winter dad jokes are snow joke. Perfect for breaking the ice at any cold-weather gathering.", - "keywords": r"\b(winter|snow|snowman|ic(y|icle)|frost|chill)\b"}, -] -MIN_TOPIC_JOKES = 5 - - -def esc(s): - return html.escape(s, quote=True) - - -def joke_count(slug): - return sum(1 for j in JOKES if j['cat'] == slug) - - -def joke_card(j, reveal=False): - """A single joke card. Punchline is in the DOM (SEO-safe); JS blurs until clicked.""" - cls = 'joke-card' - if j.get('type') == 'knock': - body = '

%s

' % esc(j['setup']).replace('\n', '
') - else: - body = '

%s

' % esc(j['setup']) - punch = '
%s
' % esc(j['punch']) - share = ('' - '' % (j['id'], j['id'])) - return ('
%s%s' - '
%s
' - % (cls, j['id'], body, punch, share)) - - -def layout(title, desc, body, path='/', extra_head=''): - nav_cats = ''.join( - '%s %s' % (c['slug'], c['emoji'], esc(c['name'])) - for c in CATS[:7]) - rest = ''.join('%s %s' % (c['slug'], c['emoji'], esc(c['name'])) - for c in CATS[7:]) - foot_cats = ''.join('%s' % (c['slug'], esc(c['name'])) for c in CATS) - return f""" - - - - -{esc(title)} - - - - - - - - - - - - -{extra_head} - - - -
-{body} -
- - - - -""" - - -def page_index(): - rnd = JOKES[(datetime.date.today().toordinal() * 7) % len(JOKES)] - d_idx = datetime.date.today().toordinal() - jod = JOKES[d_idx % len(JOKES)] - latest = JOKES[-12:][::-1] - live_topics = [t for t in TOPICS if len(topic_jokes(t)) >= MIN_TOPIC_JOKES] - topic_chips = ''.join('%s %s' % ( - t['slug'], t['emoji'], esc(t['name'].replace('Dad Jokes About ', '').replace('Dad Jokes', 'Jokes'))) - for t in live_topics) - cat_cards = ''.join(f""" - - {c['emoji']} - {esc(c['name'])} - {joke_count(c['slug'])} jokes - """ for c in CATS) - latest_cards = ''.join(joke_card(j) for j in latest) - body = f""" -
-

The Best Dad Jokes on the Internet

-

Short. Clean. Painfully punny. {len(JOKES)}+ jokes your kids don't want to hear but absolutely need to.

-
-

{esc(rnd['setup'])}

-
{esc(rnd['punch'])}
-
- - - -
-
-
- - - -
-
-

๐Ÿ˜„ Dad Joke of the Day

-

{esc(jod['setup'])}

-
{esc(jod['punch'])}
-

See the archive โ†’

-
-
- -
-

Browse Dad Jokes by Category

-
{cat_cards}
-
- -
-

Popular Dad Joke Topics

-
- {topic_chips} -
-
- - - -
-

Fresh Out of the Dad Oven

-
{latest_cards}
- -
- -
-

What is a dad joke?

-

A dad joke is a short, clean, pun-based joke delivered with maximum confidence and zero shame. It's the kind of joke that makes your kids groan, your wife roll her eyes, and your dog leave the room. Science has yet to explain why hearing one makes you feel briefly smarter and visibly worse at the same time.

-

This site is a growing collection of the best dad jokes on the internet: one-liners, knock-knock jokes, and puns about animals, food, work, school, sports, tech, space and more. Every joke is family-friendly โ€” the only thing at risk of injury is everyone's patience. Start with the dad joke generator, browse the categories, or come back every day for a new dad joke of the day.

-
""" - title = 'Best Dad Jokes โ€” %d+ Funny, Clean Dad Jokes (One-Liners, Puns & Knock-Knocks)' % len(JOKES) - desc = ('The best dad jokes on the internet: %d clean, funny, groan-worthy dad jokes. ' - 'One-liners, knock-knock jokes and puns by category, plus a daily dad joke and a free generator.' - % len(JOKES)) - ld = { - "@context": "https://schema.org", - "@type": "WebSite", - "name": SITE_NAME, - "url": SITE_URL + '/', - "description": TAGLINE, - "potentialAction": { - "@type": "SearchAction", - "target": SITE_URL + "/search/?q={search_term_string}", - "query-input": "required name=search_term_string" - } - } - return layout(title, desc, body, '/', - '' % esc(json.dumps(ld))) - - -def page_category(slug): - c = CAT_BY_SLUG[slug] - js = [j for j in JOKES if j['cat'] == slug] - cards = ''.join(joke_card(j) for j in js) - others = [o for o in CATS if o['slug'] != slug][:6] - others_html = ''.join('%s %s' % (o['slug'], o['emoji'], esc(o['name'])) for o in others) - body = f""" -
- -

{c['emoji']} {esc(c['name'])}

-

{esc(c['blurb'])} That's {len(js)} jokes of pure dad energy.

-
{cards}
- -

Keep the groans going

-
{others_html}
- -
""" - title = '%s โ€” %d Funny, Clean %s' % (c['name'], len(js), c['name']) - desc = '%s %s %d jokes, all clean and all terrible. Perfect for dads, teachers and any occasion that needs a groan.' % (c['blurb'], 'Browse', len(js)) - ld = {"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [ - {"@type": "ListItem", "position": 1, "name": "Home", "item": SITE_URL + '/'}, - {"@type": "ListItem", "position": 2, "name": c['name'], "item": SITE_URL + '/category/%s/' % slug}]} - return layout(title, desc, body, '/category/%s/' % slug, - '' % esc(json.dumps(ld))) - - -def topic_jokes(topic): - pat = re.compile(topic['keywords']) - return [j for j in JOKES if pat.search((j['setup'] + ' ' + j['punch']).lower())] - - -def page_topic(slug): - t = next(t for t in TOPICS if t['slug'] == slug) - js = topic_jokes(t) - if len(js) < MIN_TOPIC_JOKES: - return None - cards = ''.join(joke_card(j) for j in js) - other_topics = [o for o in TOPICS if o['slug'] != slug and len(topic_jokes(o)) >= MIN_TOPIC_JOKES][:6] - chips = ''.join('%s %s' % (o['slug'], o['emoji'], esc(o['name'].replace('Dad Jokes About ', '').replace('Dad Jokes', 'Jokes'))) for o in other_topics) - body = f""" -
- -

{t['emoji']} {esc(t['name'])}

-

{esc(t['intro'])} That's {len(js)} of them, carefully curated for maximum groan.

-
{cards}
-

More where that came from

-
{chips}
- -
""" - title = '%s โ€” %d Clean, Groan-Worthy Jokes' % (t['name'], len(js)) - desc = '%s %s' % (t['intro'], 'A curated collection of %d clean, family-friendly jokes.' % len(js)) - return layout(title, desc, body, '/topic/%s/' % slug) - - -def page_generator(): - body = """ -
-

๐ŸŽฒ Dad Joke Generator

-

One click. One random dad joke. Zero apologies. Works at parties, in the car line, and mid-argument.

-
-

Click the button to summon a dad joke.

-
-
- - - -
-
-

How to weaponize a dad joke

-
    -
  1. Deliver with confidence. Eye contact is everything. The dad joke fears hesitation.
  2. -
  3. Pause before the punchline. Let the suspense marinate. Then pounce.
  4. -
  5. Ignore the groans. The groan is applause in dad dialect.
  6. -
  7. Repeat the best ones. Repetition builds tradition. Tradition builds groans.
  8. -
-

Looking for something specific? Search all dad jokes or browse by category.

-
""" - title = 'Dad Joke Generator โ€” Random Dad Jokes, One Click' - desc = 'Press one button and get a random dad joke instantly. Hundreds of clean, funny dad jokes ready to embarrass your kids.' - return layout(title, desc, body, '/dad-joke-generator/') - - -def page_jotd(): - d = datetime.date.today() - j = JOKES[d.toordinal() % len(JOKES)] - body = f""" -
-

๐Ÿ˜„ Dad Joke of the Day

-

A brand-new dad joke every single day. Doctors recommend exactly one per day. We won't tell if you don't.

-
-

{d.strftime('%B %d, %Y')}

-

{esc(j['setup'])}

-
{esc(j['punch'])}
-
- - -
-
-

The dad joke archive

-

Every recent dad joke of the day, one per day, no repeats (for a few months, anyway):

-
- -
""" - title = 'Dad Joke of the Day โ€” A New Funny Dad Joke Daily' - desc = "Today's dad joke of the day, plus the full archive. One clean, funny dad joke for every day of the year." - ld = {"@context": "https://schema.org", "@type": "SpecialAnnouncement", "name": "Dad Joke of the Day", - "url": SITE_URL + '/joke-of-the-day/', "category": "https://schema.org/EntertainmentEvent"} - return layout(title, desc, body, '/joke-of-the-day/', - '' % SITE_URL + - '' % esc(json.dumps(ld))) - - -def page_search(): - body = """ -
-

๐Ÿ”Ž Search Dad Jokes

-

Looking for a dad joke about coffee, cats, or cardio? Type it in. We probably have a pun for that.

-
- -
-

-
-

No jokes matched. Which, honestly, is a great setup for a new dad joke โ€” send it to us via the contact page.

-
""" - title = 'Search Dad Jokes โ€” Find a Dad Joke About Anything' - desc = 'Search hundreds of clean dad jokes by keyword. Find puns and one-liners about any topic instantly.' - return layout(title, desc, body, '/search/') - - -def page_about(): - body = """ -
-

๐Ÿ‘‹ About Best Dad Jokes

-

Welcome to BestDadJokes.lol โ€” a growing collection of the internet's best dad jokes: short, clean, pun-heavy, and delivered with the quiet confidence only a dad can muster.

-

The mission is simple: build the most useful dad joke resource on the internet. That means:

- -

Why dad jokes?

-

Because a dad joke is never just a joke. It's a love language. It's saying "I would rather embarrass myself than miss a chance to make you laugh." Also, they're free, and this is a budget operation.

-

Get in touch

-

Got a joke that deserves to be here? Found a bug? Want to license 300 dad jokes for your family group chat? Head to the contact page.

-
""" - title = 'About โ€” Best Dad Jokes .lol' - desc = 'BestDadJokes.lol is a curated collection of clean, funny dad jokes. Hand-picked jokes, family-friendly humor, zero nonsense.' - return layout(title, desc, body, '/about/') - - -def page_contact(): - body = """ -
-

๐Ÿ“ฎ Contact

-

Want to send us the greatest dad joke of all time? Report a joke that's more "dad" than "joke"? Tell us the site made you laugh so hard you spilled your coffee? We read everything.

-

Email us at hello@bestdadjokes.lol โ€” responses may be delayed by naps.

-

Joke submissions

-

Include the setup and the punchline in your email. If we use it, you get full bragging rights and absolutely no money, which is exactly how dad economics works.

-
""" - title = 'Contact โ€” Best Dad Jokes .lol' - desc = 'Get in touch with BestDadJokes.lol โ€” submit a dad joke, say hi, or report a bug.' - return layout(title, desc, body, '/contact/') - - -def page_privacy(): - body = """ -
-

Privacy Policy

-

Last updated: %s

-

BestDadJokes.lol respects your privacy, which is ironic because dads famously respect nothing more than a good interruption. Here's the deal in plain English:

-

What we collect

-

We keep standard web server logs (IP address, browser type, pages visited) for security and aggregate traffic statistics. We do not ask for your name, email, or your kids' report cards. There are no accounts on this site.

-

Cookies and advertising

-

We may display advertising on this site to keep it free. Advertising partners, which may include Google, use cookies and similar technologies to serve ads based on your prior visits to this and other websites.

-

Google's use of advertising cookies enables it and its partners to serve ads based on your visits to this site and/or other sites on the Internet. You may opt out of personalized advertising by visiting Google Ads Settings, or opt out of third-party vendor cookies at aboutads.info.

-

If we run a consent banner for visitors from the EEA/UK, your choices will be honored and stored locally.

-

Analytics

-

We use aggregate, privacy-respecting analytics to count page views and understand which categories people enjoy. This data cannot reasonably be used to identify you.

-

Data sharing

-

We do not sell personal data. We have no personal data to sell. We barely have data.

-

Children

-

Content on this site is family-friendly by design. The site is not directed at children under 13, and we do not knowingly collect personal information from children.

-

Contact

-

Questions about this policy? Email hello@bestdadjokes.lol.

-
""" % datetime.date.today().strftime('%B %d, %Y') - title = 'Privacy Policy โ€” Best Dad Jokes .lol' - desc = 'Privacy policy for BestDadJokes.lol โ€” what we collect (very little), cookies, advertising, and your choices.' - return layout(title, desc, body, '/privacy-policy/') - - -def page_terms(): - body = """ -
-

Terms of Use

-

Last updated: %s

-

By using BestDadJokes.lol you agree to the following terms. Reading them aloud in a dad voice is optional but encouraged.

-

Use of content

-

The jokes on this site are provided for personal, non-commercial enjoyment. Share them freely at dinner tables, offices, and group chats. If you want to republish jokes in bulk or use this site commercially, contact us first โ€” we're reasonable people.

-

No warranties

-

This site is provided "as is." We make no guarantees that jokes will be funny, that groans will be produced, or that your teenagers will acknowledge your existence. All comedic risk is yours.

-

Limitation of liability

-

To the fullest extent permitted by law, BestDadJokes.lol is not liable for any damages arising from use of the site, including but not limited to eye-rolling, sighing, involuntary chuckling, and children hiding your phone.

-

External links

-

We are not responsible for the content of external websites, including their jokes, which are almost certainly worse than ours.

-

Changes

-

We may update these terms occasionally. Continued use of the site means you accept the updated terms, and possibly a fresh dad joke.

-
""" % datetime.date.today().strftime('%B %d, %Y') - title = 'Terms of Use โ€” Best Dad Jokes .lol' - desc = 'Terms of use for BestDadJokes.lol.' - return layout(title, desc, body, '/terms/') - - -def page_404(): - body = """ -
-

404

-

This page is like a dad joke that got cut from the site โ€” it just didn't land.

-
-

Why did the web page go to therapy?

-
It had too many broken links.
-
-

Take me home โ†’

-
""" - return layout('404 โ€” Page Not Found', 'Page not found. The joke is on us.', body, '/404.html') - - -def write(path, content): - full = os.path.join(SITE_DIR, path) - os.makedirs(os.path.dirname(full), exist_ok=True) - with open(full, 'w') as f: - f.write(content) - - -def build(): - if os.path.exists(SITE_DIR): - shutil.rmtree(SITE_DIR) - os.makedirs(SITE_DIR) - # static assets - st = os.path.join(SITE_DIR, 'static') - os.makedirs(st, exist_ok=True) - for fn in os.listdir(STATIC): - shutil.copy(os.path.join(STATIC, fn), st) - # jokes data for client-side JS - payload = { - 'categories': [{'slug': c['slug'], 'name': c['name'], 'emoji': c['emoji']} for c in CATS], - 'jokes': [{'id': j['id'], 'cat': j['cat'], 'type': j.get('type', 'qa'), - 's': j['setup'], 'p': j['punch']} for j in JOKES]} - write('static/jokes-data.js', - 'window.BDJ=%s;' % json.dumps(payload, ensure_ascii=False, separators=(',', ':'))) - - write('index.html', page_index()) - for c in CATS: - write('category/%s/index.html' % c['slug'], page_category(c['slug'])) - for t in TOPICS: - page = page_topic(t['slug']) - if page: - write('topic/%s/index.html' % t['slug'], page) - write('dad-joke-generator/index.html', page_generator()) - write('joke-of-the-day/index.html', page_jotd()) - write('search/index.html', page_search()) - write('about/index.html', page_about()) - write('contact/index.html', page_contact()) - write('privacy-policy/index.html', page_privacy()) - write('terms/index.html', page_terms()) - write('404.html', page_404()) - - # robots + sitemap - pages = ['/', '/dad-joke-generator/', '/joke-of-the-day/', '/search/', '/about/', '/contact/', - '/privacy-policy/', '/terms/'] + ['/category/%s/' % c['slug'] for c in CATS] \ - + ['/topic/%s/' % t['slug'] for t in TOPICS if len(topic_jokes(t)) >= MIN_TOPIC_JOKES] - write('robots.txt', 'User-agent: *\nAllow: /\n\nSitemap: %s/sitemap.xml\n' % SITE_URL) - today = datetime.date.today().isoformat() - sm = ['', - ''] - for p in pages: - sm.append('%s%s%s%s%s' - % (SITE_URL, p, today, 'weekly' if p == '/' else 'monthly', '1.0' if p == '/' else '0.8')) - sm.append('') - write('sitemap.xml', '\n'.join(sm)) - - # RSS: dad joke of the day feed, last 21 days - from email.utils import formatdate - now_ts = datetime.datetime.utcnow().timestamp() - items = [] - for back in range(21): - d = datetime.date.today() - datetime.timedelta(days=back) - j = JOKES[d.toordinal() % len(JOKES)] - ts = now_ts - back * 86400 - items.append( - '%s%s/joke-of-the-day/?d=%s' - 'bjd-daily-%s' - '%s%s โ€” %s' - % (esc('Dad Joke of the Day โ€” %s' % d.strftime('%b %d')), SITE_URL, d.isoformat(), - d.isoformat(), formatdate(ts), esc(j['setup']), esc(j['punch']))) - rss = ('' - '%s โ€” Dad Joke of the Day' - '%s/joke-of-the-day/%s' - 'en-us%s' - % (esc(SITE_NAME), SITE_URL, esc(TAGLINE), ''.join(items))) - write('rss.xml', rss) - - # free JSON API - rnd = JOKES[(datetime.datetime.utcnow().microsecond // 1000) * len(JOKES) // 1000] - tod = JOKES[datetime.date.today().toordinal() % len(JOKES)] - def api_j(j): - return json.dumps({'setup': j['setup'], 'punchline': j['punch'], 'type': j.get('type', 'qa'), - 'category': j['cat'], - 'source': SITE_URL + '/category/%s/#joke-%d' % (j['cat'], j['id'])}, - ensure_ascii=False) - write('api/v1/random.json', api_j(rnd)) - write('api/v1/today.json', api_j(tod)) - - total = sum(os.path.getsize(os.path.join(dp, f)) for dp, dn, fn in os.walk(SITE_DIR) for f in fn) - nfiles = sum(len(fn) for dp, dn, fn in os.walk(SITE_DIR)) - print('Built %d files into %s (%d bytes)' % (nfiles, SITE_DIR, total)) - - -if __name__ == '__main__': - build() diff --git a/bdj/content/jokes.json b/bdj/content/jokes.json deleted file mode 100644 index 6956bff..0000000 --- a/bdj/content/jokes.json +++ /dev/null @@ -1,362 +0,0 @@ -{ - "categories": [ - {"slug": "animals", "name": "Animal Dad Jokes", "emoji": "๐Ÿฆ", "blurb": "Paws-itively terrible animal puns that will make the whole zoo groan."}, - {"slug": "food", "name": "Food Dad Jokes", "emoji": "๐Ÿ•", "blurb": "Freshly baked food puns served with a side of eye-rolls."}, - {"slug": "work", "name": "Work & Office Dad Jokes", "emoji": "๐Ÿ’ผ", "blurb": "Watercooler-worthy workplace jokes for the 9-to-5 groan."}, - {"slug": "school", "name": "School & Teacher Dad Jokes", "emoji": "๐ŸŽ’", "blurb": "Class clown certified jokes for teachers, students and homework survivors."}, - {"slug": "sports", "name": "Sports Dad Jokes", "emoji": "โšฝ", "blurb": "Jokes that swing, dribble and score โ€” straight from the sidelines."}, - {"slug": "tech", "name": "Tech & Computer Dad Jokes", "emoji": "๐Ÿ’ป", "blurb": "404: good joke not found. Just kidding โ€” these are giggle-bytes."}, - {"slug": "family", "name": "Family & Parenting Dad Jokes", "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง", "blurb": "Certified dad behavior: jokes the whole family has to live with."}, - {"slug": "weather", "name": "Weather Dad Jokes", "emoji": "โ›…", "blurb": "Forecast: 100% chance of groans with scattered puns."}, - {"slug": "space", "name": "Space Dad Jokes", "emoji": "๐Ÿš€", "blurb": "Out-of-this-world jokes โ€” no astronaut training required."}, - {"slug": "travel", "name": "Travel & Vacation Dad Jokes", "emoji": "โœˆ๏ธ", "blurb": "Puns to pack on every trip, from Fin-land to Pencil-vania."}, - {"slug": "halloween", "name": "Halloween Dad Jokes", "emoji": "๐ŸŽƒ", "blurb": "Spook-tacular jokes that are scary bad (and that's the point)."}, - {"slug": "christmas", "name": "Christmas Dad Jokes", "emoji": "๐ŸŽ„", "blurb": "Ho-ho-horrible holiday jokes for the whole workshop."}, - {"slug": "knock-knock", "name": "Knock-Knock Jokes", "emoji": "๐Ÿšช", "blurb": "Who's there? The internet's best collection of knock-knock jokes."}, - {"slug": "one-liners", "name": "Dad Joke One-Liners", "emoji": "๐ŸŽค", "blurb": "Rapid-fire one-liners. Mic drop optional, groan guaranteed."} - ], - "jokes": [ - {"cat": "animals", "setup": "Why don't oysters share their pearls?", "punch": "Because they're shellfish."}, - {"cat": "animals", "setup": "What do you call a bear with no teeth?", "punch": "A gummy bear."}, - {"cat": "animals", "setup": "What do you call a sleeping bull?", "punch": "A bulldozer."}, - {"cat": "animals", "setup": "Why are fish so smart?", "punch": "Because they live in schools."}, - {"cat": "animals", "setup": "What do you call an alligator in a vest?", "punch": "An investigator."}, - {"cat": "animals", "setup": "What do you call a fish without eyes?", "punch": "A fsh."}, - {"cat": "animals", "setup": "What do you call a deer with no eyes?", "punch": "No eye deer."}, - {"cat": "animals", "setup": "What do you call a deer with no eyes and no legs?", "punch": "Still no eye deer."}, - {"cat": "animals", "setup": "Why do cows have hooves instead of feet?", "punch": "Because they lactose."}, - {"cat": "animals", "setup": "What did the buffalo say when his son left for college?", "punch": "Bison."}, - {"cat": "animals", "setup": "Why did the turtle cross the road?", "punch": "To get to the shell station."}, - {"cat": "animals", "setup": "Why do seagulls fly over the sea?", "punch": "Because if they flew over the bay, they'd be bagels."}, - {"cat": "animals", "setup": "What's a cat's favorite color?", "punch": "Purr-ple."}, - {"cat": "animals", "setup": "Why did the dog sit in the shade?", "punch": "Because he didn't want to be a hot dog."}, - {"cat": "animals", "setup": "What do you call a pig that does karate?", "punch": "A pork chop."}, - {"cat": "animals", "setup": "How do bees get to school?", "punch": "On the school buzz."}, - {"cat": "animals", "setup": "What do you call an owl that does magic tricks?", "punch": "Hoo-dini."}, - {"cat": "animals", "setup": "Why don't ducks tell jokes when they fly?", "punch": "Because they'd quack up."}, - {"cat": "animals", "setup": "What did the horse say when it tripped?", "punch": "Help! I've fallen and I can't giddyup!"}, - {"cat": "animals", "setup": "What do you call a snake wearing a hard hat?", "punch": "A boa constructor."}, - {"cat": "animals", "setup": "What's a frog's favorite type of shoes?", "punch": "Open-toad."}, - {"cat": "animals", "setup": "What do you call a crab that plays baseball?", "punch": "A pinch hitter."}, - {"cat": "animals", "setup": "What do you call a lazy kangaroo?", "punch": "A pouch potato."}, - {"cat": "animals", "setup": "How does a penguin build its house?", "punch": "Igloos it together."}, - {"cat": "animals", "setup": "How do you know if there's an elephant under your bed?", "punch": "Your nose touches the ceiling."}, - {"cat": "animals", "setup": "Why do cows wear bells?", "punch": "Because their horns don't work."}, - {"cat": "animals", "setup": "Why do seals swim in salt water?", "punch": "Because pepper water makes them sneeze."}, - {"cat": "animals", "setup": "How many tickles does it take to make an octopus laugh?", "punch": "Ten-tickles."}, - {"cat": "animals", "setup": "What do you call a magic dog?", "punch": "A labracadabrador."}, - - {"cat": "food", "setup": "Why did the tomato blush?", "punch": "Because it saw the salad dressing."}, - {"cat": "food", "setup": "What do you call a fake noodle?", "punch": "An impasta."}, - {"cat": "food", "setup": "Why did the cookie go to the doctor?", "punch": "Because it was feeling crummy."}, - {"cat": "food", "setup": "What did the grape do when it got stepped on?", "punch": "It let out a little wine."}, - {"cat": "food", "setup": "Why don't eggs tell jokes?", "punch": "They'd crack each other up."}, - {"cat": "food", "setup": "What do you call cheese that isn't yours?", "punch": "Nacho cheese."}, - {"cat": "food", "setup": "Why did the banana go to the doctor?", "punch": "It wasn't peeling well."}, - {"cat": "food", "setup": "What's orange and sounds like a parrot?", "punch": "A carrot."}, - {"cat": "food", "setup": "What does a lemon say when it answers the phone?", "punch": "Yellow!"}, - {"cat": "food", "setup": "What did one plate say to the other plate?", "punch": "Dinner's on me."}, - {"cat": "food", "setup": "Why did the yogurt go to the art exhibit?", "punch": "Because it was cultured."}, - {"cat": "food", "setup": "What do you call a sad strawberry?", "punch": "A blueberry."}, - {"cat": "food", "setup": "What did the baby corn say to the mama corn?", "punch": "Where's popcorn?"}, - {"cat": "food", "setup": "Why did the chef get arrested?", "punch": "He was caught beating an egg."}, - {"cat": "food", "setup": "What kind of room doesn't have doors?", "punch": "A mushroom."}, - {"cat": "food", "setup": "What did the lettuce say to the celery?", "punch": "Romaine calm."}, - {"cat": "food", "setup": "Why did the man put his money in the freezer?", "punch": "He wanted cold hard cash."}, - {"cat": "food", "setup": "What did the hamburger name its daughter?", "punch": "Patty."}, - {"cat": "food", "setup": "Why do bananas have to put on sunscreen at the beach?", "punch": "Otherwise they'd peel."}, - {"cat": "food", "setup": "Why did the pizzeria owner go out of business?", "punch": "He couldn't make enough dough."}, - {"cat": "food", "setup": "What do you call an avocado that's been blessed?", "punch": "Holy guacamole."}, - {"cat": "food", "setup": "Why did the coffee file a police report?", "punch": "It got mugged."}, - {"cat": "food", "setup": "What do you call a cow on a trampoline?", "punch": "A milkshake."}, - {"cat": "food", "setup": "Why did the melons plan a big wedding?", "punch": "Because they cantaloupe."}, - {"cat": "food", "setup": "What did the baby cookie's parents do when it cried?", "punch": "They felt crumby."}, - - {"cat": "work", "setup": "Why did the scarecrow win an award?", "punch": "He was outstanding in his field."}, - {"cat": "work", "setup": "Why did the employee get fired from the calendar factory?", "punch": "He took a couple of days off."}, - {"cat": "work", "setup": "My boss told me to have a good day.", "punch": "So I went home."}, - {"cat": "work", "setup": "Why did the banker quit his job?", "punch": "He lost interest."}, - {"cat": "work", "setup": "I used to work in a shoe recycling shop.", "punch": "It was sole destroying."}, - {"cat": "work", "setup": "I got fired from the orange juice factory.", "punch": "I couldn't concentrate."}, - {"cat": "work", "setup": "What did the janitor say when he jumped out of the supply closet?", "punch": "Supplies!"}, - {"cat": "work", "setup": "Why do employees drink so much coffee at work?", "punch": "It's the weekend immune system that keeps them going."}, - {"cat": "work", "setup": "I just got a job making clocks.", "punch": "It's about time."}, - {"cat": "work", "setup": "I used to be a tailor.", "punch": "Turns out I wasn't suited for it."}, - {"cat": "work", "setup": "I started a business making origami.", "punch": "Unfortunately, it folded."}, - {"cat": "work", "setup": "How's my job security?", "punch": "Rock solid โ€” nobody else wants it."}, - {"cat": "work", "setup": "Why did the pencil get a promotion?", "punch": "It was on point."}, - {"cat": "work", "setup": "I asked my boss for a raise.", "punch": "He said the elevator does that, and handed me a ladder."}, - {"cat": "work", "setup": "Why do bakers work so hard?", "punch": "Because they knead the dough."}, - {"cat": "work", "setup": "I quit my job at the helium factory.", "punch": "I refused to be spoken to in that tone."}, - {"cat": "work", "setup": "I got a job feeding the giraffes at the zoo.", "punch": "The pay is lousy, and it's a tall order."}, - {"cat": "work", "setup": "Why did the calendar apply for the job?", "punch": "It had a lot of dates."}, - {"cat": "work", "setup": "Retirement is the best job I've ever had.", "punch": "The pay is terrible, but the hours are great."}, - {"cat": "work", "setup": "My boss said: dress for the job you want, not the job you have.", "punch": "Now HR is asking why I came to work dressed as the boss."}, - - {"cat": "school", "setup": "Why did the student eat his homework?", "punch": "Because the teacher said it was a piece of cake."}, - {"cat": "school", "setup": "Why was the math book sad?", "punch": "It had too many problems."}, - {"cat": "school", "setup": "Why did the teacher wear sunglasses to school?", "punch": "Because her students were so bright."}, - {"cat": "school", "setup": "Who's the king of all school supplies?", "punch": "The ruler."}, - {"cat": "school", "setup": "Why did the student bring a ladder to school?", "punch": "He wanted to go to high school."}, - {"cat": "school", "setup": "Why was the equal sign so humble?", "punch": "It knew it wasn't less than or greater than anyone else."}, - {"cat": "school", "setup": "What do you call a teacher who never farts in public?", "punch": "A private tutor."}, - {"cat": "school", "setup": "Why did the music teacher need a ladder?", "punch": "To reach the high notes."}, - {"cat": "school", "setup": "What did the paper say to the pencil?", "punch": "Write on!"}, - {"cat": "school", "setup": "Why did the kid study in the airplane?", "punch": "He wanted a higher education."}, - {"cat": "school", "setup": "Why was the geometry book so adorable?", "punch": "It had acute angles."}, - {"cat": "school", "setup": "What's a math teacher's favorite tree?", "punch": "Geome-tree."}, - {"cat": "school", "setup": "The past, the present, and the future walked into class.", "punch": "It was tense."}, - {"cat": "school", "setup": "Why did the clock get in trouble at school?", "punch": "It kept tocking back."}, - {"cat": "school", "setup": "Why don't you see giraffes in elementary school?", "punch": "They're all in high school."}, - {"cat": "school", "setup": "Why did the geography teacher love the ocean?", "punch": "It was full of currents."}, - {"cat": "school", "setup": "Teacher: 'Name two pronouns.'", "punch": "Student: 'Who, me?'"}, - {"cat": "school", "setup": "Why do calculators make great friends?", "punch": "You can always count on them."}, - {"cat": "school", "setup": "What's a pirate's favorite subject?", "punch": "ARRRR-t."}, - {"cat": "school", "setup": "Why did the kid throw butter out the window?", "punch": "He wanted to see a butter fly."}, - {"cat": "school", "setup": "Why did the child cross the playground?", "punch": "To get to the other slide."}, - - {"cat": "sports", "setup": "Why did the golfer bring two pairs of pants?", "punch": "In case he got a hole in one."}, - {"cat": "sports", "setup": "Why did the bicycle fall over?", "punch": "It was two-tired."}, - {"cat": "sports", "setup": "Why can't basketball players go on vacation?", "punch": "They'd get called for traveling."}, - {"cat": "sports", "setup": "Why did the football coach go to the bank?", "punch": "To get his quarterback."}, - {"cat": "sports", "setup": "What's a runner's favorite subject?", "punch": "Jog-raphy."}, - {"cat": "sports", "setup": "Why did the soccer player bring string to the game?", "punch": "So he could tie the score."}, - {"cat": "sports", "setup": "Why don't prisoners play tennis?", "punch": "Too many serves."}, - {"cat": "sports", "setup": "Why did the baseball player get arrested?", "punch": "He stole second base."}, - {"cat": "sports", "setup": "Why are basketball courts always wet?", "punch": "Because the players dribble."}, - {"cat": "sports", "setup": "What's the difference between a bad golfer and a bad skydiver?", "punch": "A bad golfer goes WHACK... dang! A bad skydiver goes dang!... WHACK."}, - {"cat": "sports", "setup": "I asked the marathon runner how the race went.", "punch": "He said it was a long story."}, - {"cat": "sports", "setup": "What does a hockey player have in common with a magician?", "punch": "Hat tricks."}, - {"cat": "sports", "setup": "Why was the baseball stadium so cool?", "punch": "Because it was full of fans."}, - {"cat": "sports", "setup": "I told my personal trainer I wanted to learn to do the splits.", "punch": "She asked: 'How flexible are you?' I said: 'I can't make Tuesdays.'"}, - {"cat": "sports", "setup": "Why did the golfer bring an extra sock?", "punch": "In case he got a hole in one."}, - - {"cat": "tech", "setup": "Why do programmers prefer dark mode?", "punch": "Because light attracts bugs."}, - {"cat": "tech", "setup": "Why did the computer go to the doctor?", "punch": "It caught a virus."}, - {"cat": "tech", "setup": "Why did the computer show up late to work?", "punch": "It had a hard drive."}, - {"cat": "tech", "setup": "Why was the smartphone wearing glasses?", "punch": "It lost all its contacts."}, - {"cat": "tech", "setup": "What do you call 8 hobbits?", "punch": "A hobbyte."}, - {"cat": "tech", "setup": "Why did the developer go broke?", "punch": "He used up all his cache."}, - {"cat": "tech", "setup": "Why do Java developers wear glasses?", "punch": "Because they don't C#."}, - {"cat": "tech", "setup": "What do you call a computer that sings?", "punch": "A Dell."}, - {"cat": "tech", "setup": "What did the mouse say to the keyboard?", "punch": "You're just my type."}, - {"cat": "tech", "setup": "Why did the robot go on vacation?", "punch": "He needed to recharge."}, - {"cat": "tech", "setup": "What's a computer's favorite snack?", "punch": "Microchips."}, - {"cat": "tech", "setup": "Why did the Wi-Fi go to therapy?", "punch": "It felt disconnected."}, - {"cat": "tech", "setup": "How does a computer get drunk?", "punch": "It takes screenshots."}, - {"cat": "tech", "setup": "Why did the PowerPoint presentation cross the road?", "punch": "To get to the other slide."}, - {"cat": "tech", "setup": "How many programmers does it take to change a light bulb?", "punch": "None โ€” that's a hardware problem."}, - {"cat": "tech", "setup": "Why was the JavaScript developer sad?", "punch": "He didn't Node how to Express himself."}, - {"cat": "tech", "setup": "What did the router say to the doctor?", "punch": "It hurts when IP."}, - {"cat": "tech", "setup": "Why do keyboards never sleep?", "punch": "They have two shifts."}, - {"cat": "tech", "setup": "I would tell you a UDP joke...", "punch": "...but you might not get it."}, - - {"cat": "family", "setup": "Kid: 'Dad, can you put my shoes on?'", "punch": "Dad: 'No, they don't fit me.'"}, - {"cat": "family", "setup": "Kid: 'Dad, make me a sandwich!'", "punch": "Dad: 'Poof! You're a sandwich.'"}, - {"cat": "family", "setup": "Why did the dad bring a pencil to bed?", "punch": "To draw the curtains."}, - {"cat": "family", "setup": "Why did grandpa put wheels on his rocking chair?", "punch": "He wanted to rock and roll."}, - {"cat": "family", "setup": "What did the mother rope say to the child rope?", "punch": "Don't be knotty."}, - {"cat": "family", "setup": "Why did the toddler put sugar under his pillow?", "punch": "He wanted sweet dreams."}, - {"cat": "family", "setup": "Kid: 'Dad, how do I look?'", "punch": "Dad: 'With your eyes.'"}, - {"cat": "family", "setup": "I used to play piano by ear.", "punch": "Now I use my hands."}, - {"cat": "family", "setup": "Kid: 'Dad, can you tell me what a solar eclipse is?'", "punch": "Dad: 'No sun.'"}, - {"cat": "family", "setup": "Why did the baby strawberry cry?", "punch": "Its parents were in a jam."}, - {"cat": "family", "setup": "Kid: 'I'm cold.'", "punch": "Dad: 'Go stand in the corner โ€” it's 90 degrees.'"}, - {"cat": "family", "setup": "Why did the dad stare at the carton of orange juice?", "punch": "Because it said 'concentrate.'"}, - {"cat": "family", "setup": "What did the baby computer call its parent?", "punch": "Data."}, - {"cat": "family", "setup": "Why did the cookie cry in kindergarten?", "punch": "His mom had been a wafer too long."}, - - {"cat": "weather", "setup": "What does a cloud wear under its raincoat?", "punch": "Thunderwear."}, - {"cat": "weather", "setup": "What did one raindrop say to the other?", "punch": "Two's company, three's a cloud."}, - {"cat": "weather", "setup": "What do you call it when it rains chickens and ducks?", "punch": "Fowl weather."}, - {"cat": "weather", "setup": "I tried to catch some fog earlier.", "punch": "I mist."}, - {"cat": "weather", "setup": "What does the wind say on Valentine's Day?", "punch": "I'm a big fan of yours."}, - {"cat": "weather", "setup": "Why did the sun go to school?", "punch": "To get brighter."}, - {"cat": "weather", "setup": "What falls in winter but never gets hurt?", "punch": "Snow."}, - {"cat": "weather", "setup": "Why did the weather want privacy?", "punch": "It was changing."}, - {"cat": "weather", "setup": "What's a tornado's favorite game?", "punch": "Twister."}, - {"cat": "weather", "setup": "What do snowmen call their offspring?", "punch": "Chill-dren."}, - {"cat": "weather", "setup": "What's a rainbow's favorite accessory?", "punch": "A bow-tie."}, - {"cat": "weather", "setup": "Why don't mountains ever get cold in winter?", "punch": "They wear snowcaps."}, - {"cat": "weather", "setup": "What do you get when you leave a dog out in the cold?", "punch": "A chili dog."}, - {"cat": "weather", "setup": "The forecast said it would rain cats and dogs.", "punch": "I went outside and stepped in a poodle."}, - - {"cat": "space", "setup": "How does the moon cut its hair?", "punch": "Eclipse it."}, - {"cat": "space", "setup": "How do you organize a party in space?", "punch": "You planet."}, - {"cat": "space", "setup": "Why is the moon always broke?", "punch": "It's down to its last quarter."}, - {"cat": "space", "setup": "What do you call an alien with three eyes?", "punch": "An aliiien."}, - {"cat": "space", "setup": "Why did the astronaut break up with his girlfriend?", "punch": "He needed space."}, - {"cat": "space", "setup": "What kind of music do planets listen to?", "punch": "Neptunes."}, - {"cat": "space", "setup": "What did Saturn say to Jupiter?", "punch": "Give me a ring sometime."}, - {"cat": "space", "setup": "What do astronauts use to keep their pants up?", "punch": "An asteroid belt."}, - {"cat": "space", "setup": "Why aren't astronauts hungry after they blast off?", "punch": "Because they've had a big launch."}, - {"cat": "space", "setup": "What's an astronaut's favorite part of a computer?", "punch": "The space bar."}, - {"cat": "space", "setup": "Why did the cow become an astronaut?", "punch": "To see the moooon."}, - {"cat": "space", "setup": "What do you call a space magician?", "punch": "A flying saucerer."}, - {"cat": "space", "setup": "Why couldn't the astronaut book a hotel room on the moon?", "punch": "Because it was full."}, - {"cat": "space", "setup": "How do astronauts serve their dinner?", "punch": "On flying saucers."}, - - {"cat": "travel", "setup": "What do you call a mountain that's really funny?", "punch": "Hill-arious."}, - {"cat": "travel", "setup": "Why did the airplane get sent to its room?", "punch": "For bad altitude."}, - {"cat": "travel", "setup": "Why did the car apply for a job?", "punch": "It was tired of sitting idle."}, - {"cat": "travel", "setup": "What did the beach say as the tide came in?", "punch": "Long time, no sea."}, - {"cat": "travel", "setup": "Where do sharks go on vacation?", "punch": "Fin-land."}, - {"cat": "travel", "setup": "Where do pencils go on vacation?", "punch": "Pencil-vania."}, - {"cat": "travel", "setup": "What do you call a train that sneezes?", "punch": "Achoo-choo train."}, - {"cat": "travel", "setup": "Why did the man bring a ladder to the airport?", "punch": "He heard the fares were sky high."}, - {"cat": "travel", "setup": "Why did the suitcase go to therapy?", "punch": "It had too much baggage."}, - {"cat": "travel", "setup": "Where do sick boats go?", "punch": "To the dock."}, - {"cat": "travel", "setup": "What's the fastest country in the world?", "punch": "Rush-a."}, - {"cat": "travel", "setup": "What's the fastest vegetable?", "punch": "A runner bean."}, - {"cat": "travel", "setup": "What did the ocean say to the beach?", "punch": "Nothing โ€” it just waved."}, - {"cat": "travel", "setup": "Where do math teachers go on vacation?", "punch": "Times Square."}, - {"cat": "travel", "setup": "Why don't calendars ever take a vacation?", "punch": "Their days are numbered."}, - - {"cat": "halloween", "setup": "Why don't skeletons ever fight each other?", "punch": "They don't have the guts."}, - {"cat": "halloween", "setup": "What do you call a witch's garage?", "punch": "A broom closet."}, - {"cat": "halloween", "setup": "Why did the vampire subscribe to the newspaper?", "punch": "He heard it had great circulation."}, - {"cat": "halloween", "setup": "What's a ghost's favorite dessert?", "punch": "I scream."}, - {"cat": "halloween", "setup": "Why don't mummies take vacations?", "punch": "They're afraid to unwind."}, - {"cat": "halloween", "setup": "What do you call a monster who poisons corn flakes?", "punch": "A cereal killer."}, - {"cat": "halloween", "setup": "Why are ghosts such bad liars?", "punch": "You can see right through them."}, - {"cat": "halloween", "setup": "What do you get when you cross a snowman with a vampire?", "punch": "Frostbite."}, - {"cat": "halloween", "setup": "Why did the skeleton go to the party alone?", "punch": "He had no body to go with him."}, - {"cat": "halloween", "setup": "What kind of music do mummies listen to?", "punch": "Wrap music."}, - {"cat": "halloween", "setup": "What did the skeleton order at the restaurant?", "punch": "Spare ribs."}, - {"cat": "halloween", "setup": "Why do witches ride brooms?", "punch": "Vacuum cleaners are too expensive."}, - {"cat": "halloween", "setup": "What do you call a haunted chicken?", "punch": "A poultry-geist."}, - {"cat": "halloween", "setup": "Why did the ghost go to the bar?", "punch": "For the boos."}, - {"cat": "halloween", "setup": "How do you make a skeleton laugh?", "punch": "Tickle its funny bone."}, - {"cat": "halloween", "setup": "What's a vampire's favorite fruit?", "punch": "A blood orange."}, - - {"cat": "christmas", "setup": "What do you call Santa's little helpers?", "punch": "Subordinate Clauses."}, - {"cat": "christmas", "setup": "What do you call Santa when he takes a break?", "punch": "Santa Pause."}, - {"cat": "christmas", "setup": "What do snowmen eat for breakfast?", "punch": "Snowflakes."}, - {"cat": "christmas", "setup": "What did one snowman say to the other snowman?", "punch": "Do you smell carrots?"}, - {"cat": "christmas", "setup": "Why does Santa go down the chimney?", "punch": "Because it soots him."}, - {"cat": "christmas", "setup": "What do you get when you cross Santa with a duck?", "punch": "A Christmas quacker."}, - {"cat": "christmas", "setup": "Why did the Christmas tree go to the barber?", "punch": "It needed a trim."}, - {"cat": "christmas", "setup": "What do elves learn in school?", "punch": "The elf-abet."}, - {"cat": "christmas", "setup": "What's every parent's favorite Christmas carol?", "punch": "Silent Night."}, - {"cat": "christmas", "setup": "What do you call an old snowman?", "punch": "Water."}, - {"cat": "christmas", "setup": "Why are Christmas trees so bad at knitting?", "punch": "They always drop their needles."}, - {"cat": "christmas", "setup": "What does the gingerbread man use to make his bed?", "punch": "Cookie sheets."}, - {"cat": "christmas", "setup": "Why did Rudolph get a bad report card?", "punch": "Because he went down in history."}, - {"cat": "christmas", "setup": "What do you sing at a snowman's birthday party?", "punch": "Freeze a jolly good fellow."}, - {"cat": "christmas", "setup": "What's a Christmas tree's favorite candy?", "punch": "Orna-mints."}, - - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nBoo.\nBoo who?", "punch": "Don't cry โ€” it's just a joke!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nLettuce.\nLettuce who?", "punch": "Lettuce in, it's freezing out here!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nCargo.\nCargo who?", "punch": "No โ€” car go vroom vroom!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nTank.\nTank who?", "punch": "You're welcome!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nInterrupting cow.\nInterrupting cโ€”", "punch": "MOO!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nHarry.\nHarry who?", "punch": "Harry up and open the door!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nOlive.\nOlive who?", "punch": "Olive you and I miss you!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nCandice.\nCandice who?", "punch": "Candice door open, or what?"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nAtch.\nAtch who?", "punch": "Bless you!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nAmish.\nAmish who?", "punch": "Aww โ€” I miss you too!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nEurope.\nEurope who?", "punch": "No, YOU'RE a poo!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nJustin.\nJustin who?", "punch": "Justin time for dinner!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nDwayne.\nDwayne who?", "punch": "Dwayne the bathtub, I'm dwowning!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nKen.\nKen who?", "punch": "Ken you open the door, please?"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nAlpaca.\nAlpaca who?", "punch": "Alpaca the suitcase, you load up the car!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nAnita.\nAnita who?", "punch": "Anita borrow your pencil!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nGorilla.\nGorilla who?", "punch": "Gorilla cheese sandwich for me, I'm starving!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nNobel.\nNobel who?", "punch": "No bell โ€” that's why I knocked!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nDoris.\nDoris who?", "punch": "Doris locked โ€” that's why I'm knocking!"}, - {"cat": "knock-knock", "type": "knock", "setup": "Knock knock.\nWho's there?\nAardvark.\nAardvark who?", "punch": "Aardvark a hundred miles for one of your smiles!"}, - - {"cat": "one-liners", "type": "line", "setup": "I'm reading a book about anti-gravity.", "punch": "I just can't put it down."}, - {"cat": "one-liners", "type": "line", "setup": "I used to hate facial hair.", "punch": "Then it grew on me."}, - {"cat": "one-liners", "type": "line", "setup": "What's brown and sticky?", "punch": "A stick."}, - {"cat": "one-liners", "type": "line", "setup": "I told my wife she was drawing her eyebrows too high.", "punch": "She looked surprised."}, - {"cat": "one-liners", "type": "line", "setup": "I don't trust stairs.", "punch": "They're always up to something."}, - {"cat": "one-liners", "type": "line", "setup": "I'm on a seafood diet.", "punch": "I see food and I eat it."}, - {"cat": "one-liners", "type": "line", "setup": "What do you call a fish wearing a bowtie?", "punch": "Sofishticated."}, - {"cat": "one-liners", "type": "line", "setup": "I stayed up all night wondering where the sun went.", "punch": "Then it dawned on me."}, - {"cat": "one-liners", "type": "line", "setup": "I'd tell you a construction joke...", "punch": "...but I'm still working on it."}, - {"cat": "one-liners", "type": "line", "setup": "I know a lot of jokes about retired people.", "punch": "Sadly, none of them work."}, - {"cat": "one-liners", "type": "line", "setup": "I ordered a chicken and an egg from an online store.", "punch": "I'll let you know."}, - {"cat": "one-liners", "type": "line", "setup": "What time did the man go to the dentist?", "punch": "Tooth hurty."}, - {"cat": "one-liners", "type": "line", "setup": "Did you hear about the kidnapping at school?", "punch": "It's fine โ€” he woke up."}, - {"cat": "one-liners", "type": "line", "setup": "What do you call a boomerang that doesn't come back?", "punch": "A stick."}, - {"cat": "one-liners", "type": "line", "setup": "I only know 25 letters of the alphabet.", "punch": "I don't know y."}, - {"cat": "one-liners", "type": "line", "setup": "Did you hear about the restaurant on the moon?", "punch": "Great food, no atmosphere."}, - {"cat": "one-liners", "type": "line", "setup": "Why do we tell actors to 'break a leg'?", "punch": "Because every play has a cast."}, - {"cat": "one-liners", "type": "line", "setup": "What do you call a factory that makes okay products?", "punch": "A satisfactory."}, - {"cat": "one-liners", "type": "line", "setup": "Dear algebra,", "punch": "Stop asking us to find your X. She's never coming back โ€” and don't ask Y."}, - {"cat": "one-liners", "type": "line", "setup": "What's the difference between ignorance and apathy?", "punch": "I don't know and I don't care."}, - {"cat": "one-liners", "type": "line", "setup": "I asked my dog what's two minus two.", "punch": "He said nothing."}, - {"cat": "one-liners", "type": "line", "setup": "What do you call a parade of rabbits hopping backwards?", "punch": "A receding hare-line."}, - {"cat": "one-liners", "type": "line", "setup": "What do you call a sleeping dinosaur?", "punch": "A dino-snore."}, - {"cat": "one-liners", "type": "line", "setup": "What do you call a man with a rubber toe?", "punch": "Roberto."}, - {"cat": "one-liners", "type": "line", "setup": "Why did the two 4s skip dinner?", "punch": "They already 8."}, - {"cat": "one-liners", "type": "line", "setup": "What do you call a sheep with no legs?", "punch": "A cloud."}, - {"cat": "one-liners", "type": "line", "setup": "Why did the picture go to jail?", "punch": "Because it was framed."}, - {"cat": "one-liners", "type": "line", "setup": "What did the man say when he walked into a bar?", "punch": "Ouch."}, - {"cat": "one-liners", "type": "line", "setup": "Why did the man fall down the well?", "punch": "He couldn't see that well."}, - {"cat": "one-liners", "type": "line", "setup": "What's red and bad for your teeth?", "punch": "A brick."}, - {"cat": "one-liners", "type": "line", "setup": "Why was 6 afraid of 7?", "punch": "Because 7 8 9."}, - {"cat": "one-liners", "type": "line", "setup": "What did the zero say to the eight?", "punch": "Nice belt."}, - {"cat": "one-liners", "type": "line", "setup": "Why can't your nose be 12 inches long?", "punch": "Because then it would be a foot."}, - {"cat": "one-liners", "type": "line", "setup": "I'm terrified of elevators.", "punch": "So I'm taking steps to avoid them."}, - {"cat": "one-liners", "type": "line", "setup": "Why don't scientists trust atoms?", "punch": "Because they make up everything."}, - {"cat": "one-liners", "type": "line", "setup": "What did the Buddhist monk say to the hot dog vendor?", "punch": "Make me one with everything."}, - - {"cat": "food", "setup": "What do you call a sad cup of coffee?", "punch": "A depres-so."}, - {"cat": "food", "setup": "How do coffee cups show their love?", "punch": "With mugs and kisses."}, - {"cat": "food", "setup": "Where do coffee beans go for a night out?", "punch": "The coffee bar."}, - {"cat": "food", "setup": "Why was the espresso checking its watch?", "punch": "It was pressed for time."}, - {"cat": "food", "setup": "What's a coffee's favorite magic spell?", "punch": "Espresso Patronum!"}, - {"cat": "food", "setup": "Why don't coffees ever gossip?", "punch": "They don't want to spill the beans."}, - {"cat": "food", "setup": "How does coffee show it cares?", "punch": "It gives you a latte attention."}, - - {"cat": "animals", "setup": "What do you call a pile of kittens?", "punch": "A meow-ntain."}, - {"cat": "animals", "setup": "Why don't cats play poker in the jungle?", "punch": "Too many cheetahs."}, - {"cat": "animals", "setup": "What's a cat's favorite dessert?", "punch": "Mice cream."}, - {"cat": "animals", "setup": "Why did the cat sit on the computer?", "punch": "To keep an eye on the mouse."}, - {"cat": "animals", "setup": "What do you call a cat that works at a hospital?", "punch": "A purr-fessional."}, - {"cat": "animals", "setup": "How do cats end a fight?", "punch": "They hiss and make up."}, - {"cat": "animals", "setup": "Why is everyone so excited about the cat's new job?", "punch": "Because it's feline good."}, - {"cat": "animals", "setup": "Why did the dog cross the road?", "punch": "To get to the barking lot."}, - {"cat": "animals", "setup": "What kind of dog loves surround sound?", "punch": "A subwoofer."}, - {"cat": "animals", "setup": "Why do dogs run in circles?", "punch": "Because it's too hard to run in squares."}, - {"cat": "animals", "setup": "What's a dog's favorite instrument?", "punch": "The trom-BONE."}, - {"cat": "animals", "setup": "How did the puppy feel after finishing its homework?", "punch": "Ruff."}, - {"cat": "animals", "setup": "Why did the dachshund sit by the fire?", "punch": "It was a cold dog."}, - {"cat": "animals", "setup": "What breed of dog can jump higher than a house?", "punch": "Any breed โ€” houses can't jump."}, - - {"cat": "work", "setup": "Why did the penny get promoted?", "punch": "It made perfect cents."}, - {"cat": "work", "setup": "What do you call a rich fish?", "punch": "A goldfish."}, - {"cat": "work", "setup": "Why did the dollar bill break up with the coin?", "punch": "It needed some change."}, - {"cat": "work", "setup": "What happened to the man who put $20 into a vending machine?", "punch": "Nothing โ€” he's still waiting for his two cents."}, - {"cat": "work", "setup": "Why was the money exhausted?", "punch": "It was spent."}, - {"cat": "work", "setup": "Where do cows invest their money?", "punch": "In moo-tual funds."}, - - {"cat": "one-liners", "type": "line", "setup": "What's a nap's favorite kind of movie?", "punch": "A sleeper hit."}, - {"cat": "one-liners", "type": "line", "setup": "Why did the pillow go to the doctor?", "punch": "It was feeling down."}, - {"cat": "one-liners", "type": "line", "setup": "Why are blankets so good at ending arguments?", "punch": "They know how to wrap things up."}, - {"cat": "one-liners", "type": "line", "setup": "I'm great at sleeping.", "punch": "I can do it with my eyes closed."}, - {"cat": "one-liners", "type": "line", "setup": "Why was everyone so tired on the morning of April 1st?", "punch": "They'd just finished a 31-day March."}, - - {"cat": "family", "setup": "Why did the two antennas get married?", "punch": "The ceremony was okay, but the reception was excellent."}, - {"cat": "family", "setup": "What did the paper clip say to the magnet?", "punch": "I find you very attractive."}, - {"cat": "family", "setup": "What's the most romantic part about the ocean?", "punch": "When the buoy meets gull."}, - {"cat": "family", "setup": "Kid: 'Do you have a date for Valentine's Day?'", "punch": "Dad: 'Sure โ€” February 14th.'"}, - {"cat": "family", "setup": "My wife told me to stop impersonating a flamingo.", "punch": "I had to put my foot down."}, - {"cat": "family", "setup": "Why do candles love birthdays?", "punch": "They get to be the light of the party."}, - {"cat": "family", "setup": "What did the elephant want for its birthday?", "punch": "A trunk full of presents."}, - {"cat": "family", "setup": "Why was the birthday cake as hard as a rock?", "punch": "It was marble cake."}, - {"cat": "family", "setup": "What does a clam do on its birthday?", "punch": "It shellabrates."}, - {"cat": "family", "setup": "What did the pirate say on his 80th birthday?", "punch": "Aye matey!"}, - - {"cat": "food", "setup": "What does an aardvark like on its pizza?", "punch": "Ant-chovies."}, - {"cat": "food", "setup": "Why did the hipster burn his tongue on his pizza?", "punch": "He ate it before it was cool."}, - {"cat": "food", "setup": "What kind of pizza do dogs order?", "punch": "Pup-peroni."}, - {"cat": "food", "setup": "What's the difference between a pizza and my pizza jokes?", "punch": "The pizza can actually feed a family."}, - - {"cat": "weather", "setup": "How does a snowman get around town?", "punch": "On an icicle."}, - {"cat": "weather", "setup": "What did the icy road say to the truck?", "punch": "Want to go for a spin?"}, - {"cat": "weather", "setup": "How do you prevent a summer cold?", "punch": "Catch it in the winter."} - ] -} diff --git a/bdj/deploy.sh b/bdj/deploy.sh deleted file mode 100755 index 7279fcb..0000000 --- a/bdj/deploy.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -# Build sites and sync docroots IN PLACE (no dir swap: hostPath mounts bind to -# the directory inode, so replacing the dir would leave pods serving a stale -# path). Routing/TLS are handled by Traefik + cert-manager; no reloads needed. -set -euo pipefail -cd "$(dirname "$0")" -BASE=/home/opc/zai-home-base - -echo "==> building site" -python3 build.py - -mkdir -p "$BASE/sites/bestdadjokes" -if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" = "Enforcing" ]; then - chcon -Rt container_file_t "$BASE/sites/bestdadjokes" 2>/dev/null || true -fi -rsync -a --delete site/ "$BASE/sites/bestdadjokes/" - -echo "==> verifying through Traefik (origin)" -sleep 1 -for host in bestdadjokes.lol www.bestdadjokes.lol myadhd.dev; do - code=$(curl -sk -o /dev/null -w '%{http_code}' --resolve "$host:443:127.0.0.1" "https://$host/" --max-time 10) - echo " https://$host -> $code (origin)" -done - -echo "==> asset smoke checks" -fail=0 -hp=$(curl -sk --resolve bestdadjokes.lol:443:127.0.0.1 https://bestdadjokes.lol/ --max-time 10) -echo "$hp" | grep -q 'rel="stylesheet"' || { echo " FAIL: stylesheet not linked in homepage"; fail=1; } -for asset in /static/style.css /static/main.js /static/jokes-data.js /static/favicon.svg; do - out=$(curl -sk -o /dev/null -w '%{http_code} %{content_type}' --resolve bestdadjokes.lol:443:127.0.0.1 "https://bestdadjokes.lol$asset" --max-time 10) - echo " $asset -> $out" - case "$out" in 200*) ;; *) fail=1 ;; esac -done -# every referenced local asset must exist (catches missing files) -echo "$hp" | grep -oE '(src|href)="/[^"]+"' | sed -E 's/(src|href)="([^"]+)"/\2/' | sort -u | while read -r ref; do - case "$ref" in //*) continue ;; esac - code=$(curl -sk -o /dev/null -w '%{http_code}' --resolve bestdadjokes.lol:443:127.0.0.1 "https://bestdadjokes.lol$ref" --max-time 10) - [ "$code" = "200" ] || echo " WARN: referenced asset $ref -> $code" -done -[ "$fail" = "0" ] || { echo "==> DEPLOY FAILED ASSET CHECKS"; exit 1; } -echo "==> deploy complete" diff --git a/bdj/k8s/letsencrypt-issuer.yaml b/bdj/k8s/letsencrypt-issuer.yaml deleted file mode 100644 index 8bc9b20..0000000 --- a/bdj/k8s/letsencrypt-issuer.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Let's Encrypt via cert-manager, DNS-01 through the Cloudflare API token. -# The token lives in Secret cert-manager/cloudflare-api-token (created out-of-band -# via `kubectl create secret ... --from-file=api-token=...`; never committed). -apiVersion: cert-manager.io/v1 -kind: ClusterIssuer -metadata: - name: letsencrypt-prod -spec: - acme: - server: https://acme-v02.api.letsencrypt.org/directory - email: hello@bestdadjokes.lol - privateKeySecretRef: - name: letsencrypt-prod-account-key - solvers: - - dns01: - cloudflare: - apiTokenSecretRef: - name: cloudflare-api-token - key: api-token - selector: - dnsZones: - - bestdadjokes.lol - - myadhd.dev - - helpme.tips - - ch4t.buzz diff --git a/bdj/k8s/static-sites.yaml b/bdj/k8s/static-sites.yaml deleted file mode 100644 index 4a5ce32..0000000 --- a/bdj/k8s/static-sites.yaml +++ /dev/null @@ -1,262 +0,0 @@ -# Static site fleet: one nginx Deployment + Service + Ingress + Certificate per site. -# Docroots come from hostPath /home/opc/zai-home-base/sites/ (built by deploy scripts). -# Edge routing/TLS: Traefik (80/443) + cert-manager Let's Encrypt secrets. ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: static-nginx-conf -data: - nginx.conf: | - worker_processes 1; - error_log /dev/stderr warn; - pid /run/nginx.pid; - events { worker_connections 1024; } - http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - access_log /dev/stdout; - sendfile on; - gzip on; - gzip_types text/plain text/css application/javascript application/json application/xml image/svg+xml; - gzip_min_length 1024; - server { - listen 80; - root /usr/share/nginx/html; - index index.html; - error_page 404 /404.html; - add_header X-Content-Type-Options nosniff always; - add_header Referrer-Policy strict-origin-when-cross-origin always; - location ~* \.(css|js|svg|png|jpg|ico|woff2?)$ { - expires 7d; - add_header Cache-Control "public"; - } - location / { - try_files $uri $uri/ $uri/index.html =404; - } - } - } ---- -# ---------------- bestdadjokes.lol ---------------- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-bdj - labels: - site: bestdadjokes.lol -spec: - replicas: 1 - selector: - matchLabels: - site: bestdadjokes.lol - template: - metadata: - labels: - site: bestdadjokes.lol - spec: - nodeSelector: - kubernetes.io/hostname: djg-oracle-sl - tolerations: - - key: node-role.kubernetes.io/master - operator: Exists - effect: NoSchedule - - key: node-role.kubernetes.io/control-plane - operator: Exists - effect: NoSchedule - containers: - - name: nginx - image: nginx:alpine - volumeMounts: - - name: conf - mountPath: /etc/nginx/nginx.conf - subPath: nginx.conf - readOnly: true - - name: html - mountPath: /usr/share/nginx/html - readOnly: true - resources: - requests: - cpu: 20m - memory: 32Mi - limits: - memory: 128Mi - readinessProbe: - httpGet: {path: /, port: 80} - initialDelaySeconds: 2 - periodSeconds: 20 - volumes: - - name: conf - configMap: - name: static-nginx-conf - - name: html - hostPath: - path: /home/opc/zai-home-base/sites/bestdadjokes - type: Directory ---- -apiVersion: v1 -kind: Service -metadata: - name: static-bdj -spec: - selector: - site: bestdadjokes.lol - ports: - - port: 80 - targetPort: 80 ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: bestdadjokes -spec: - rules: - - host: bestdadjokes.lol - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: static-bdj - port: - number: 80 - - host: www.bestdadjokes.lol - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: static-bdj - port: - number: 80 - tls: - - hosts: - - bestdadjokes.lol - - www.bestdadjokes.lol - secretName: bestdadjokes-tls ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: bestdadjokes-lol -spec: - secretName: bestdadjokes-tls - issuerRef: - name: letsencrypt-prod - kind: ClusterIssuer - dnsNames: - - bestdadjokes.lol - - www.bestdadjokes.lol ---- -# ---------------- myadhd.dev (placeholder) ---------------- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-adhd - labels: - site: myadhd.dev -spec: - replicas: 1 - selector: - matchLabels: - site: myadhd.dev - template: - metadata: - labels: - site: myadhd.dev - spec: - nodeSelector: - kubernetes.io/hostname: djg-oracle-sl - tolerations: - - key: node-role.kubernetes.io/master - operator: Exists - effect: NoSchedule - - key: node-role.kubernetes.io/control-plane - operator: Exists - effect: NoSchedule - containers: - - name: nginx - image: nginx:alpine - volumeMounts: - - name: conf - mountPath: /etc/nginx/nginx.conf - subPath: nginx.conf - readOnly: true - - name: html - mountPath: /usr/share/nginx/html - readOnly: true - resources: - requests: - cpu: 20m - memory: 32Mi - limits: - memory: 128Mi - readinessProbe: - httpGet: {path: /, port: 80} - initialDelaySeconds: 2 - periodSeconds: 20 - volumes: - - name: conf - configMap: - name: static-nginx-conf - - name: html - hostPath: - path: /home/opc/zai-home-base/sites/myadhd.dev - type: Directory ---- -apiVersion: v1 -kind: Service -metadata: - name: static-adhd -spec: - selector: - site: myadhd.dev - ports: - - port: 80 - targetPort: 80 ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: myadhd -spec: - rules: - - host: myadhd.dev - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: static-adhd - port: - number: 80 - - host: www.myadhd.dev - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: static-adhd - port: - number: 80 - tls: - - hosts: - - myadhd.dev - - www.myadhd.dev - secretName: myadhd-tls ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: myadhd-dev -spec: - secretName: myadhd-tls - issuerRef: - name: letsencrypt-prod - kind: ClusterIssuer - dnsNames: - - myadhd.dev - - www.myadhd.dev diff --git a/bdj/k8s/traefik.yaml b/bdj/k8s/traefik.yaml deleted file mode 100644 index 0e11159..0000000 --- a/bdj/k8s/traefik.yaml +++ /dev/null @@ -1,113 +0,0 @@ -# Traefik v2.11 LTS as the cluster edge: hostNetwork 80/443, global http->https redirect. -# TLS: entrypoint terminates TLS; certificates provisioned by cert-manager -# (Let's Encrypt DNS-01 via Cloudflare) into the Ingress TLS secrets. -# NOTE: --entrypoints.websecure.http.tls=true is REQUIRED โ€” without it TLS -# routers never engage the mux and every HTTPS request 404s. -apiVersion: v1 -kind: ServiceAccount -metadata: - name: traefik - namespace: kube-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: traefik -rules: - - apiGroups: [""] - resources: ["services", "endpoints", "secrets", "namespaces", "nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["discovery.k8s.io"] - resources: ["endpointslices"] - verbs: ["get", "list", "watch"] - - apiGroups: ["networking.k8s.io"] - resources: ["ingresses", "ingressclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["networking.k8s.io"] - resources: ["ingresses/status"] - verbs: ["update"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: traefik -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: traefik -subjects: - - kind: ServiceAccount - name: traefik - namespace: kube-system ---- -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: traefik - namespace: kube-system - labels: - app: traefik -spec: - selector: - matchLabels: - app: traefik - template: - metadata: - labels: - app: traefik - spec: - serviceAccountName: traefik - hostNetwork: true - dnsPolicy: ClusterFirstWithHostNet - nodeSelector: - kubernetes.io/hostname: djg-oracle-sl - tolerations: - - key: node-role.kubernetes.io/master - operator: Exists - effect: NoSchedule - - key: node-role.kubernetes.io/control-plane - operator: Exists - effect: NoSchedule - containers: - - name: traefik - image: traefik:v2.11.2 - args: - - --providers.kubernetesingress - - --providers.kubernetescrd=false - - --entrypoints.web.address=:80 - - --entrypoints.web.http.redirections.entrypoint.to=websecure - - --entrypoints.web.http.redirections.entrypoint.scheme=https - - --entrypoints.websecure.address=:443 - - --entrypoints.websecure.http.tls=true - - --log.level=WARN - ports: - - containerPort: 80 - hostPort: 80 - protocol: TCP - - containerPort: 443 - hostPort: 443 - protocol: TCP - resources: - requests: - cpu: 50m - memory: 64Mi - limits: - memory: 256Mi - readinessProbe: - httpGet: - path: / - port: 80 - httpHeaders: - - name: Host - value: bestdadjokes.lol - initialDelaySeconds: 5 - periodSeconds: 15 - livenessProbe: - httpGet: - path: / - port: 80 - httpHeaders: - - name: Host - value: bestdadjokes.lol - initialDelaySeconds: 15 - periodSeconds: 30 diff --git a/bdj/static/favicon.svg b/bdj/static/favicon.svg deleted file mode 100644 index f0176fa..0000000 --- a/bdj/static/favicon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/bdj/static/main.js b/bdj/static/main.js deleted file mode 100644 index 63e0d5c..0000000 --- a/bdj/static/main.js +++ /dev/null @@ -1,235 +0,0 @@ -/* BestDadJokes.lol client-side behavior */ -(function () { - 'use strict'; - document.body.classList.add('js-ux'); - - var JOKES = (window.BDJ && window.BDJ.jokes) || []; - function byId(id) { return JOKES[id]; } - - function $(sel, root) { return (root || document).querySelector(sel); } - function $$(sel, root) { return Array.prototype.slice.call((root || document).querySelectorAll(sel)); } - - /* ---- punchline reveal ---- */ - document.addEventListener('click', function (e) { - var punch = e.target.closest && e.target.closest('[data-punch]'); - if (punch && !punch.classList.contains('revealed')) { - punch.classList.add('revealed'); - } - }); - - /* ---- random ---- */ - var lastId = -1; - function randomJoke() { - if (!JOKES.length) return null; - var j, guard = 0; - do { j = JOKES[Math.floor(Math.random() * JOKES.length)]; guard++; } - while (j.id === lastId && guard < 20 && JOKES.length > 1); - lastId = j.id; - return j; - } - function fillRandom(setupEl, punchEl) { - var j = randomJoke(); - if (!j) return null; - setupEl.textContent = j.s; - punchEl.textContent = j.p; - punchEl.classList.add('revealed'); - punchEl.setAttribute('data-no-blur', ''); - return j; - } - - /* ---- clipboard ---- */ - function copyText(text, btn) { - function ok() { if (btn) { var o = btn.textContent; btn.textContent = 'โœ… Copied'; setTimeout(function () { btn.textContent = o; }, 1400); } } - if (navigator.clipboard && navigator.clipboard.writeText) { - navigator.clipboard.writeText(text).then(ok, function () { legacy(); }); - } else { legacy(); } - function legacy() { - var ta = document.createElement('textarea'); - ta.value = text; document.body.appendChild(ta); ta.select(); - try { document.execCommand('copy'); ok(); } catch (err) {} - document.body.removeChild(ta); - } - } - - /* ---- share card (1080x1350) ---- */ - function wrapText(ctx, text, x, y, maxW, lh) { - var words = text.split(' '), line = '', lines = []; - words.forEach(function (w) { - var test = line + w + ' '; - if (ctx.measureText(test).width > maxW && line) { lines.push(line.trim()); line = w + ' '; } - else { line = test; } - }); - lines.push(line.trim()); - lines.forEach(function (l, i) { ctx.fillText(l, x, y + i * lh); }); - return y + lines.length * lh; - } - function makeCard(setup, punch, cb) { - var c = document.createElement('canvas'); - c.width = 1080; c.height = 1350; - var ctx = c.getContext('2d'); - ctx.fillStyle = '#fff8ec'; ctx.fillRect(0, 0, 1080, 1350); - // border frame - ctx.strokeStyle = '#2b2118'; ctx.lineWidth = 8; - ctx.strokeRect(36, 36, 1008, 1278); - // emoji - ctx.font = '110px serif'; ctx.textAlign = 'center'; - ctx.fillText('๐Ÿ˜‚', 540, 240); - // setup - ctx.fillStyle = '#2b2118'; ctx.textAlign = 'center'; - ctx.font = '700 54px ui-rounded, -apple-system, "Segoe UI", Arial, sans-serif'; - var y = wrapText(ctx, setup, 540, 380, 880, 72); - // punchline - ctx.fillStyle = '#e4572e'; - ctx.font = '800 66px ui-rounded, -apple-system, "Segoe UI", Arial, sans-serif'; - y = wrapText(ctx, punch, 540, y + 150, 880, 86); - // footer - ctx.fillStyle = '#5c4f43'; ctx.font = '700 34px -apple-system, "Segoe UI", Arial, sans-serif'; - ctx.fillText('BestDadJokes.lol', 540, 1240); - cb(c); - } - function shareCard(setup, punch) { - var name = 'dad-joke.png'; - makeCard(setup, punch, function (canvas) { - if (navigator.canShare && navigator.share) { - canvas.toBlob(function (blob) { - if (!blob) return; - var file = new File([blob], name, { type: 'image/png' }); - if (navigator.canShare({ files: [file] })) { - navigator.share({ files: [file], title: 'A dad joke for you' }).catch(function () {}); - } - }, 'image/png'); - } else { - var a = document.createElement('a'); - a.download = name; a.href = canvas.toDataURL('image/png'); - document.body.appendChild(a); a.click(); document.body.removeChild(a); - } - }); - } - - function wireCard(opts) { - var setupEl = $(opts.setup), punchEl = $(opts.punch); - if (!setupEl || !punchEl) return; - var current = opts.initial || null; - if (opts.nextBtn) { - $(opts.nextBtn).addEventListener('click', function () { current = fillRandom(setupEl, punchEl); }); - } - if (opts.copyBtn) { - $(opts.copyBtn).addEventListener('click', function (e) { - if (current) copyText(current.s + ' ' + current.p, e.target); - }); - } - if (opts.shareBtn) { - $(opts.shareBtn).addEventListener('click', function () { - if (current) shareCard(current.s, current.p); - }); - } - return { - get: function () { return current; }, - set: function (j) { current = j; setupEl.textContent = j.s; punchEl.textContent = j.p; punchEl.classList.add('revealed'); punchEl.setAttribute('data-no-blur', ''); } - }; - } - - /* ---- page-specific wiring ---- */ - // hero - var hero = wireCard({ setup: '[data-hero-setup]', punch: '[data-hero-punch]', nextBtn: '[data-hero-next]', copyBtn: '[data-copy-hero]', shareBtn: '[data-share-hero]' }); - if (hero) hero.set(randomJoke()); - - // generator - wireCard({ setup: '[data-gen-setup]', punch: '[data-gen-punch]', nextBtn: '[data-gen-next]', copyBtn: '[data-gen-copy]', shareBtn: '[data-gen-share]' }); - - // joke of the day (+ ?d= override for archive permalinks) - var jodSetup = $('[data-jod-setup]'); - if (jodSetup) { - var m = /[?&]d=(\d{4}-\d{2}-\d{2})/.exec(location.search); - var base = new Date(Date.UTC(2026, 8, 15)); // launch epoch for the rotation - var jod = null; - if (m) { - var parts = m[1].split('-'), d = new Date(Date.UTC(+parts[0], +parts[1] - 1, +parts[2])); - if (!isNaN(d.getTime())) { - var idx = Math.floor((d - new Date(d.getUTCFullYear(), 0, 1)) / 86400000) + d.getUTCMonth() * 31 + d.getUTCDate(); - jod = JOKES[(d.getTime() / 86400000 | 0) % JOKES.length] || JOKES[0]; - $('[data-jod-date]').textContent = d.toUTCString().slice(0, 16); - var ctl = { setup: '[data-jod-setup]', punch: '[data-jod-punch]' }; - jodSetup.textContent = jod.s; - $('[data-jod-punch]').textContent = jod.p; - $('[data-jod-punch]').classList.add('revealed'); - $('[data-jod-punch]').setAttribute('data-no-blur', ''); - } - } - var jodCopy = $('[data-jod-copy]'); - if (jodCopy) { - var punchEl = $('[data-jod-punch]'); - jodCopy.addEventListener('click', function (e) { copyText(jodSetup.textContent + ' ' + punchEl.textContent, e.target); }); - } - var jodShare = $('[data-jod-share]'); - if (jodShare) { - jodShare.addEventListener('click', function () { shareCard(jodSetup.textContent, $('[data-jod-punch]').textContent); }); - } - // archive: last 30 days - var arch = $('[data-jod-archive]'); - if (arch && JOKES.length) { - var today = new Date(); - var html = ''; - for (var i = 1; i <= 30; i++) { - var dd = new Date(today.getTime() - i * 86400000); - var iso = dd.toISOString().slice(0, 10); - var jj = JOKES[Math.floor(dd.getTime() / 86400000) % JOKES.length]; - html += '

' + iso + '

' + - '

' + jj.s.replace(/' + - '

' + jj.p.replace(/
'; - } - arch.innerHTML = html; - } - } - - /* ---- per-card copy/share buttons ---- */ - document.addEventListener('click', function (e) { - var t = e.target; - if (t.hasAttribute && t.hasAttribute('data-copy')) { - var j = byId(+t.getAttribute('data-copy')); - if (j) copyText(j.s + ' ' + j.p, t); - } - if (t.hasAttribute && t.hasAttribute('data-share')) { - var jj = byId(+t.getAttribute('data-share')); - if (jj) shareCard(jj.s, jj.p); - } - }); - - /* ---- search ---- */ - var box = $('#search-box'); - if (box) { - var results = $('[data-search-results]'); - var countEl = $('[data-search-count]'); - var emptyEl = $('[data-search-empty]'); - function renderResults(q) { - q = q.trim().toLowerCase(); - var hits = []; - if (q.length > 1) { - hits = JOKES.filter(function (j) { - return (j.s + ' ' + j.p).toLowerCase().indexOf(q) !== -1; - }).slice(0, 60); - } - countEl.textContent = q.length > 1 ? hits.length + ' joke' + (hits.length === 1 ? '' : 's') + ' matching "' + q + '"' : ''; - emptyEl.style.display = (q.length > 1 && !hits.length) ? 'block' : 'none'; - results.innerHTML = hits.map(function (j) { - return '

' + j.s.replace(/' + - '

' + j.p.replace(/' + - '
' + - 'Category โ†’
'; - }).join(''); - } - box.addEventListener('input', function () { renderResults(box.value); }); - var q = /[?&]q=([^&]*)/.exec(location.search); - if (q) { box.value = decodeURIComponent(q[1].replace(/\+/g, ' ')); } - renderResults(box.value); - box.focus(); - } - - /* ---- close mobile nav on selection ---- */ - $$('.site-nav a').forEach(function (a) { - a.addEventListener('click', function () { - var t = $('#navtoggle'); - if (t) t.checked = false; - }); - }); -})(); diff --git a/bdj/static/style.css b/bdj/static/style.css deleted file mode 100644 index 86ce4f3..0000000 --- a/bdj/static/style.css +++ /dev/null @@ -1,169 +0,0 @@ -/* BestDadJokes.lol โ€” comic cream */ -* { box-sizing: border-box; margin: 0; padding: 0; } -:root { - --brand: #e4572e; - --brand-dark: #c74623; - --brand2: #f5a623; - --ink: #2b2118; - --ink-soft: #5c4f43; - --cream: #fff8ec; - --card: #ffffff; - --radius: 16px; -} -html { scroll-behavior: smooth; } -body { - font-family: ui-rounded, "SF Pro Rounded", -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - background: var(--cream); - color: var(--ink); - line-height: 1.6; - display: flex; - min-height: 100vh; - flex-direction: column; -} -main { flex: 1; } -.wrap { max-width: 1080px; margin: 0 auto; padding: 0 20px; } -.wrap.narrow { max-width: 800px; } -a { color: var(--brand); } -h1 { font-size: clamp(1.9rem, 4.5vw, 2.9rem); line-height: 1.15; letter-spacing: -0.5px; margin: 18px 0 10px; } -h2 { font-size: clamp(1.3rem, 3vw, 1.7rem); margin: 40px 0 14px; letter-spacing: -0.3px; } -p.page-sub { color: var(--ink-soft); font-size: 1.08rem; max-width: 46rem; margin-bottom: 26px; } - -/* header */ -.site-header { background: var(--card); border-bottom: 2px solid var(--ink); position: sticky; top: 0; z-index: 50; } -.header-row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; padding-top: 12px; padding-bottom: 12px; gap: 10px; } -.logo { font-size: 1.35rem; text-decoration: none; color: var(--ink); letter-spacing: -0.5px; } -.logo strong span { color: var(--brand); } -.logo em { font-style: normal; color: var(--ink-soft); font-size: 1rem; } -.site-nav { display: flex; gap: 4px; flex-wrap: wrap; } -.site-nav a { text-decoration: none; color: var(--ink); font-weight: 600; padding: 7px 12px; border-radius: 999px; } -.site-nav a:hover { background: var(--cream); } -.site-nav a.on { background: var(--ink); color: var(--cream); } -.catbar { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 12px; } -.catbar a { flex: 0 0 auto; text-decoration: none; color: var(--ink); font-weight: 600; font-size: .86rem; background: var(--cream); border: 1.5px solid var(--ink); border-radius: 999px; padding: 3px 11px; white-space: nowrap; } -.catbar a:hover { background: var(--brand2); } -.navtoggle, .navburger { display: none; } - -/* hero */ -.hero { text-align: center; padding-top: 34px; } -.hero-sub { color: var(--ink-soft); font-size: 1.15rem; max-width: 44rem; margin: 0 auto 26px; } - -/* joke cards */ -.joke-card, .hero-card, .jod-banner { - background: var(--card); - border: 2px solid var(--ink); - border-radius: var(--radius); - box-shadow: 5px 5px 0 var(--ink); - padding: 22px 24px; - margin-bottom: 18px; -} -.hero-card { max-width: 640px; margin: 0 auto 26px; text-align: left; } -.joke-setup { font-size: 1.13rem; font-weight: 600; } -.joke-setup.multi { line-height: 1.75; } -.joke-punch { - margin-top: 12px; font-size: 1.22rem; font-weight: 800; color: var(--brand); - transition: filter .25s ease; -} -.joke-punch::before { content: "โ†’ "; color: var(--brand2); } -body.js-ux .joke-punch:not(.revealed):not([data-no-blur]) { - filter: blur(6px); - cursor: pointer; - user-select: none; -} -body.js-ux .joke-punch:not(.revealed):hover { filter: blur(3px); } -.joke-actions { margin-top: 14px; display: flex; gap: 8px; flex-wrap: wrap; } -.btn-tiny { - font: inherit; font-size: .8rem; font-weight: 700; background: var(--cream); - border: 1.5px solid var(--ink); border-radius: 999px; padding: 3px 11px; cursor: pointer; color: var(--ink); -} -.btn-tiny:hover { background: var(--brand2); } -.joke-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); gap: 18px; } -.joke-grid .joke-card { margin-bottom: 0; } - -/* buttons */ -.btn { - display: inline-block; font: inherit; font-weight: 800; font-size: 1rem; - background: var(--brand); color: #fff; border: 2px solid var(--ink); - border-radius: 999px; padding: 10px 22px; cursor: pointer; - box-shadow: 3px 3px 0 var(--ink); text-decoration: none; - transition: transform .08s ease, box-shadow .08s ease; -} -.btn:hover { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 var(--ink); } -.btn:active { transform: translate(2px, 2px); box-shadow: 1px 1px 0 var(--ink); } -.btn-ghost { background: var(--card); color: var(--ink); } -.hero-btns { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; } - -/* joke of the day */ -.jod-banner { background: linear-gradient(135deg, #fff, #fff4dd); text-align: left; } -.jod-label { font-weight: 800; text-transform: uppercase; letter-spacing: 1px; font-size: .85rem; color: var(--brand-dark); margin-bottom: 8px; } -.jod-date { font-size: .9rem; font-weight: 700; color: var(--ink-soft); margin-bottom: 6px; } -.jod-page { margin: 0 0 30px; } -.jod-more { margin-top: 14px; font-size: .95rem; } -.jod-more a { font-weight: 700; } - -/* categories */ -.cat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 16px; } -.cat-card { - background: var(--card); border: 2px solid var(--ink); border-radius: var(--radius); - box-shadow: 4px 4px 0 var(--ink); padding: 20px 18px; text-decoration: none; color: var(--ink); - display: flex; flex-direction: column; gap: 4px; - transition: transform .08s ease, box-shadow .08s ease; -} -.cat-card:hover { transform: translate(-2px, -2px); box-shadow: 6px 6px 0 var(--ink); background: #fffdf5; } -.cat-emoji { font-size: 1.8rem; } -.cat-name { font-weight: 800; letter-spacing: -0.2px; } -.cat-count { color: var(--ink-soft); font-size: .88rem; } -.chips { display: flex; gap: 10px; flex-wrap: wrap; } -.chip { border: 1.5px solid var(--ink); border-radius: 999px; padding: 5px 14px; text-decoration: none; color: var(--ink); font-weight: 600; background: var(--card); } -.chip:hover { background: var(--brand2); } -.center-link { text-align: center; margin: 30px 0 10px; } - -/* generator */ -.gen-card { margin-top: 6px; } -.plain-list { margin: 10px 0 20px 22px; } -.plain-list li { margin-bottom: 8px; } - -/* search */ -.search-form input { - width: 100%; font: inherit; font-size: 1.1rem; padding: 13px 18px; - border: 2px solid var(--ink); border-radius: 999px; background: var(--card); outline: none; - box-shadow: 3px 3px 0 var(--ink); margin: 6px 0 18px; -} -.search-count { color: var(--ink-soft); margin-bottom: 16px; font-weight: 600; } - -/* archive */ -.archive { display: grid; grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); gap: 14px; margin-bottom: 20px; } -.archive .joke-card { margin-bottom: 0; padding: 16px 18px; } -.archive .joke-setup { font-size: 1rem; } -.archive .joke-punch { font-size: 1.05rem; margin-top: 8px; } -.archive .jod-date { margin-bottom: 2px; } - -/* misc */ -.crumbs { font-size: .9rem; margin: 18px 0 4px; color: var(--ink-soft); } -.crumbs a { color: var(--ink-soft); } -.seo-blurb { margin: 40px auto 50px; } -.seo-blurb p { max-width: 52rem; margin-bottom: 10px; color: var(--ink-soft); } -.center { text-align: center; padding: 80px 20px; } - -/* footer */ -.site-footer { background: var(--ink); color: var(--cream); margin-top: 60px; } -.site-footer a { color: var(--brand2); text-decoration: none; } -.site-footer a:hover { text-decoration: underline; } -.foot-grid { display: grid; grid-template-columns: 2fr 2fr 1fr; gap: 30px; padding: 40px 20px 10px; } -.foot-logo { font-size: 1.2rem; font-weight: 800; margin-bottom: 8px; } -.foot-logo span { color: var(--brand2); } -.foot-logo em { font-style: normal; opacity: .7; font-size: .95rem; } -.site-footer p { opacity: .85; font-size: .92rem; } -.foot-h { font-weight: 800; text-transform: uppercase; letter-spacing: 1px; font-size: .78rem; margin-bottom: 10px; } -.foot-links { display: flex; flex-direction: column; gap: 6px; font-size: .92rem; } -.foot-base { border-top: 1px solid rgba(255,248,236,.2); margin-top: 24px; padding-top: 16px; padding-bottom: 20px; font-size: .85rem; } - -@media (max-width: 860px) { - .foot-grid { grid-template-columns: 1fr; gap: 22px; } - .navburger { - display: block; font-size: 1.5rem; cursor: pointer; padding: 2px 10px; - border: 2px solid var(--ink); border-radius: 10px; background: var(--cream); - } - .navtoggle:checked ~ .site-nav { display: flex; } - .site-nav { display: none; flex-direction: column; width: 100%; padding-bottom: 12px; } - .catbar a:first-child { margin-left: 2px; } -} diff --git a/helpme-tips/build.py b/helpme-tips/build.py deleted file mode 100644 index 1a5c66d..0000000 --- a/helpme-tips/build.py +++ /dev/null @@ -1,298 +0,0 @@ -#!/usr/bin/env python3 -"""Static generator for helpme.tips โ€” arbitrage-ready content site. -Articles are multi-page (pagination = more ad impressions per session). -Python 3.6 stdlib only. Ad slots are emitted as comments -plus empty mount divs; the fleet ad wrapper fills them. -""" -import json, os, shutil, html, datetime - -ROOT = os.path.dirname(os.path.abspath(__file__)) -SITE_DIR = os.path.join(ROOT, 'site') -STATIC = os.path.join(ROOT, 'static') -SITE_URL = 'https://helpme.tips' -SITE_NAME = 'Help Me Tips' -YEAR = datetime.date.today().year - - -def esc(s): - return html.escape(s, quote=True) - - -def load_articles(): - with open(os.path.join(ROOT, 'content', 'articles.json')) as f: - arts = json.load(f) - for i, a in enumerate(arts): - a['id'] = i - return arts - - -def layout(title, desc, body, path='/', extra_head=''): - return f""" - - - - - -{esc(title)} - - - - - - - - -{extra_head} - - - - -
-{body} -
-
-
- -

Practical tips for everyday life โ€” home, sleep, coffee, habits and more. No fluff, no sales pitches.

- -

ยฉ {YEAR} {SITE_NAME}. All articles are for general information only and are not professional advice.

-
-
- - -""" - - -def article_card(a): - return f""" - - {esc(a['category'])} -

{esc(a['title'])}

-

{esc(a['dek'])}

- {a['pages_count']} pages ยท {a.get('read_min', 4)} min read -
""" - - -def page_index(articles): - cards = ''.join(article_card(a) for a in articles) - body = f""" -
-

Tips that actually help.

-

Short, practical guides for everyday problems โ€” kitchen, sleep, coffee, habits, home and your phone. No fluff, no 800-word intros.

-
- -
-

Latest tips

-
{cards}
-
- -
-

Why Help Me Tips?

-

Most tip articles online bury one useful sentence under a wall of filler. We do the opposite: every page is a tight, actionable list you can finish with your coffee. New guides are added every week across home, kitchen, sleep, tech and habits.

-
""" - title = 'Help Me Tips โ€” Practical Everyday Tips That Actually Help' - desc = ('Short, practical guides for everyday life: kitchen shortcuts, better sleep, ' - 'coffee, phone settings, habits and home. New tips weekly.') - return layout(title, desc, body, '/') - - -def page_article(a, page_idx, total): - pg = a['pages'][page_idx] - parts = [] - if pg.get('heading'): - parts.append('

%s

' % esc(pg['heading'])) - parts += ['

%s

' % esc(p) for p in pg['paras']] - for h, paras in pg.get('sections', []): - parts.append('

%s

' % esc(h)) - parts += ['

%s

' % esc(p) for p in paras] - # multi-item list support - content = '\n'.join(parts) - nav = [] - if page_idx > 0: - prev = '/tips/%s/' % a['slug'] if page_idx == 1 else '/tips/%s/%d/' % (a['slug'], page_idx) - nav.append('โ† Previous' % prev) - nav.append('Page %d of %d' % (page_idx + 1, total)) - if page_idx < total - 1: - nav.append('Next page โ†’' % (a['slug'], page_idx + 2)) - nav_html = '
%s
' % '\n'.join(nav) - dots = '
%s
' % ''.join( - '' % ('on' if i == page_idx else '') for i in range(total)) - body = f""" -
- -

{esc(a['title'])}

-

{esc(a['dek'])}

-

{a.get('updated', '')} ยท Page {page_idx + 1} of {total} ยท {a.get('read_min', 4)} min read

- -
- {content} -
- {dots} - - {nav_html} - -
-

Keep reading

-
{more_cards(a, articles_all)}
-
-
""" - suffix = '' if page_idx == 0 else ' (Page %d)' % (page_idx + 1) - title = '%s%s โ€” Help Me Tips' % (a['title'], suffix) - desc = a['dek'] - ld = {"@context": "https://schema.org", "@type": "Article", "headline": a['title'], - "description": a['dek'], "mainEntityOfPage": SITE_URL + '/tips/%s/' % a['slug'], - "datePublished": a.get('updated', '2026-09-16')} - extra = '' % esc(json.dumps(ld)) - path = '/tips/%s/' % a['slug'] if page_idx == 0 else '/tips/%s/%d/' % (a['slug'], page_idx + 1) - return layout(title, desc, body, path, extra) - - -def more_cards(current, all_arts): - others = [a for a in all_arts if a['slug'] != current['slug']][:4] - return ''.join(article_card(a) for a in others) - - -articles_all = [] # populated in build(); used by more_cards - - -def page_about(): - body = """ -
-

About Help Me Tips

-

Help Me Tips publishes short, practical guides for everyday problems. We believe a good tip article should respect your time: clear steps, no 800-word personal essays, no product pitches.

-

Topics we cover: kitchen and cooking shortcuts, sleep quality, coffee, phone and tech settings, home care, and small habits that compound.

-

Our editorial standard

-
    -
  • Every tip must be actionable today with things you already own.
  • -
  • No medical, legal or financial claims. General information only.
  • -
  • If a tip doesn't survive being tested in a real home, it doesn't ship.
  • -
-
""" - return layout('About โ€” Help Me Tips', 'What Help Me Tips is and how we pick tips.', body, '/about/') - - -def page_contact(): - body = """ -
-

Contact

-

Tip ideas, corrections, feedback? We read everything: hello@helpme.tips

-
""" - return layout('Contact โ€” Help Me Tips', 'Contact Help Me Tips.', body, '/contact/') - - -def page_privacy(): - body = """ -
-

Privacy Policy

-

Last updated: %s

-

Help Me Tips collects standard web server logs (IP address, browser type, pages visited) for security and aggregate statistics. We do not require accounts and do not collect names or emails.

-

Cookies and advertising

-

We display advertising to keep this site free. Advertising partners, which may include Google, use cookies and similar technologies to serve ads based on your prior visits to this and other websites. You may opt out of personalized advertising at Google Ads Settings or aboutads.info.

-

Where required by law (EEA/UK), a consent banner will let you accept or reject non-essential cookies, and your choice will be honored.

-

Data sharing

-

We do not sell personal information. Analytics, if used, are aggregate and not identifiable.

-

Contact

-

Questions: hello@helpme.tips

-
""" % datetime.date.today().strftime('%B %d, %Y') - return layout('Privacy Policy โ€” Help Me Tips', 'Privacy policy for helpme.tips.', body, '/privacy-policy/') - - -def page_terms(): - body = """ -
-

Terms of Use

-

Last updated: %s

-

By using helpme.tips you agree to these terms. Content is provided for general information only and is not professional, medical, legal or financial advice. Use tips at your own discretion.

-

Use of content

-

Personal, non-commercial use only. Bulk republication requires written permission.

-

No warranties

-

The site is provided "as is" without warranties of any kind. Results of following tips may vary.

-

Contact

-

Questions: hello@helpme.tips

-
""" % datetime.date.today().strftime('%B %d, %Y') - return layout('Terms of Use โ€” Help Me Tips', 'Terms for helpme.tips.', body, '/terms/') - - -def page_404(): - body = """ -
-

404

-

That page slipped out of the tips drawer.

-

Back to all tips โ†’

-
""" - return layout('404 โ€” Help Me Tips', 'Page not found.', body, '/404.html') - - -def write(path, content): - full = os.path.join(SITE_DIR, path) - os.makedirs(os.path.dirname(full) or SITE_DIR, exist_ok=True) - with open(full, 'w') as f: - f.write(content) - - -def build(): - global articles_all - if os.path.exists(SITE_DIR): - shutil.rmtree(SITE_DIR) - os.makedirs(SITE_DIR) - st = os.path.join(SITE_DIR, 'static') - os.makedirs(st, exist_ok=True) - for fn in os.listdir(STATIC): - shutil.copy(os.path.join(STATIC, fn), st) - - articles = load_articles() - articles_all = articles - articles.sort(key=lambda a: a.get('updated', ''), reverse=True) - for a in articles: - a['pages_count'] = len(a['pages']) - - write('index.html', page_index(articles)) - n_pages = 0 - for a in articles: - total = len(a['pages']) - a['pages_count'] = total - for i in range(total): - write('tips/%s/index.html' % a['slug'] if i == 0 - else 'tips/%s/%d/index.html' % (a['slug'], i + 1), - page_article(a, i, total)) - n_pages += 1 - write('about/index.html', page_about()) - write('contact/index.html', page_contact()) - write('privacy-policy/index.html', page_privacy()) - write('terms/index.html', page_terms()) - write('404.html', page_404()) - - pages = ['/', '/about/', '/contact/', '/privacy-policy/', '/terms/'] + \ - ['/tips/%s/' % a['slug'] for a in articles] - today = datetime.date.today().isoformat() - sm = ['', - ''] - for p in pages: - sm.append('%s%s%s' % (SITE_URL, p, today)) - sm.append('') - write('sitemap.xml', '\n'.join(sm)) - write('robots.txt', 'User-agent: *\nAllow: /\n\nSitemap: %s/sitemap.xml\n' % SITE_URL) - - nfiles = sum(len(fn) for _, _, fn in os.walk(SITE_DIR)) - total_bytes = sum(os.path.getsize(os.path.join(dp, f)) for dp, _, fn in os.walk(SITE_DIR) for f in fn) - print('Built %d files (%d article pages, %d articles) -> %s (%d bytes)' - % (nfiles, n_pages, len(articles), SITE_DIR, total_bytes)) - - -if __name__ == '__main__': - build() diff --git a/helpme-tips/content/articles.json b/helpme-tips/content/articles.json deleted file mode 100644 index 01a8373..0000000 --- a/helpme-tips/content/articles.json +++ /dev/null @@ -1,320 +0,0 @@ -[ -{ - "slug": "kitchen-habits-save-time", - "title": "15 Kitchen Habits That Save Time Every Single Day", - "category": "Kitchen", - "dek": "Small kitchen habits that quietly give you back 20โ€“30 minutes a day. No gadgets, no meal-prep Sundays required.", - "updated": "2026-09-16", - "read_min": 5, - "pages": [ - { - "heading": "Set up tomorrow's kitchen tonight", - "paras": [ - "The fastest way to a faster morning is to close the kitchen properly the night before. Three habits take about ten minutes total: run the dishwasher (or empty it so the sink stays clear), wipe the counters, and put tomorrow's breakfast things on the counter.", - "The third one sounds trivial, but it works because decisions cost more time than actions. A bowl, a spoon, and the coffee mug already on the counter remove three small decisions from a groggy morning.", - "If you pack lunches, pack them before you sit down for the evening โ€” not in the morning. Hungry, rushed packing is slower and worse." - ], - "sections": [ - ["Empty the dishwasher in the morning, not at night", - ["Flipping the order feels strange for two days, then becomes automatic. An empty dishwasher means dirty dishes throughout the day go straight in instead of piling up in the sink.", - "A full sink is what makes evening cleanup feel like a project. A clean sink makes it a two-minute habit."]] - ] - }, - { - "heading": "Cook once, eat twice (on purpose)", - "paras": [ - "You don't need a meal-prep Sunday to save time. Just double one component whenever you cook: rice, roasted vegetables, a protein, a sauce. Tonight's dinner becomes Thursday's lunch with almost zero effort.", - "Store the second portion the moment you plate dinner โ€” before you eat. Leftovers that make it into a container get eaten; leftovers left on the stove get picked at and thrown away.", - "Label leftovers with a sticky note and the date if your fridge is a shared one. The forgotten container is where time savings go to die." - ], - "sections": [ - ["Keep a 'use me first' shelf", - ["Put everything that needs eating soon at eye level in the front. Everything else goes below or behind.", - "This one shelf saves both time and groceries, because you stop hunting and stop re-buying things you already had."]] - ] - }, - { - "heading": "Make cleanup part of cooking", - "paras": [ - "Professional kitchens stay clean because cleanup happens during cooking, not after. While something simmers or roasts for ten minutes, that's your window: wash the cutting board, wipe the counter, put away the spice jars.", - "Two tools make this effortless: a bench scraper (pushes scraps into the bin in one sweep) and a small bowl for scraps on the counter instead of walking to the bin fifteen times.", - "Finish this loop for a week and notice that after-dinner cleanup shrinks to plates and pans. That's the twenty minutes you get back, every day." - ] - } - ] -}, -{ - "slug": "fall-asleep-faster", - "title": "How to Fall Asleep Faster: 10 Things That Actually Help", - "category": "Sleep", - "dek": "No supplements, no expensive gadgets. Ten boring, proven adjustments that shorten the time it takes to drift off.", - "updated": "2026-09-16", - "read_min": 5, - "pages": [ - { - "heading": "Fix the light before you fix anything else", - "paras": [ - "Bright overhead light in the evening tells your brain it's midday. The single highest-impact change for faster sleep costs nothing: after dinner, switch off overhead lights and use lamps instead. Warm, low, and below eye level.", - "Phones are blamed for everything, but the real issue is what you're doing on them. Scrolling keeps your mind alert; the screen brightness is the smaller problem. If you use your phone in bed, at least enable night mode and lower brightness to the minimum comfortable level.", - "If outside light hits your bedroom at night, blackout curtains or a cheap sleep mask close the loop. Most people underestimate how much stray light delays sleep onset." - ] - }, - { - "heading": "Give your brain a landing strip", - "paras": [ - "The main reason people lie awake is an active mind, and the most common trigger is unfinished business: tomorrow's tasks looping at 1 a.m. Keep a notepad by the bed (paper, not phone) and write down tomorrow's top three things before lights out.", - "The trick is writing them specifically enough that your brain trusts the list. 'Deal with car' keeps you awake; 'Book MOT for Thursday' doesn't.", - "A second landing-strip habit: a fixed wind-down cue. Ten minutes of the same low-key activity โ€” stretching, a paperback, tomorrow's clothes laid out โ€” becomes a signal your body learns to answer with sleepiness." - ], - "sections": [ - ["Temperature beats everything", - ["If you only try one thing from this article, make it this: cool the room. Most people sleep faster and deeper in a slightly too-cold room (around 18ยฐC / 65ยฐF).", - "A warm shower 60โ€“90 minutes before bed helps too โ€” the post-shower temperature drop mimics the natural cooling your body uses to fall asleep."]] - ] - }, - { - "heading": "The boring stuff that still matters", - "paras": [ - "Caffeine has a longer tail than people think: half of it can still be in your system six hours later. If you fall asleep slowly, move your last coffee to before 2 p.m. for a week and compare.", - "Alcohol famously makes you drowsy but fragments the second half of the night. Less is genuinely better here, especially on weeknights.", - "Finally, the rule sleep clinics repeat: if you've been awake in bed for roughly twenty minutes, get up and do something dull in low light until sleepy. Beds should mean sleep, not wrestling with wakefulness. It feels counterproductive and works remarkably well over a couple of weeks." - ] - } - ] -}, -{ - "slug": "coffee-taste-better", - "title": "12 Ways to Make Your Coffee Taste Better Without Spending More", - "category": "Coffee", - "dek": "Better coffee is mostly technique, not budget. Twelve fixes ordered by impact, starting with the two that change everything.", - "updated": "2026-09-16", - "read_min": 5, - "pages": [ - { - "heading": "The two fixes that matter most", - "paras": [ - "First: buy whole beans and grind them just before brewing. Ground coffee loses most of its aromatics within minutes of grinding. A basic hand grinder costs less than a month of cafรฉ coffee and outlasts it by years.", - "Second: weigh your coffee instead of scooping it. The universal starting point is 60 grams of coffee per liter of water โ€” about 1 gram per 16 ml. Whatever your brewing method, fixing this ratio removes the biggest source of bad coffee: random strength.", - "Do only these two things and most people find their coffee improves more than any equipment upgrade could manage." - ] - }, - { - "heading": "Water, temperature, and freshness", - "paras": [ - "Coffee is 98% water, so off-tasting tap water makes off-tasting coffee. If your tap water is drinkable but funky, use a simple filter jug. If it's fine, leave it โ€” bottled water is usually a waste.", - "Boiling water scorches ground coffee. If you pour by hand, let the kettle sit 30โ€“60 seconds after boiling (around 93โ€“96ยฐC) before pouring. Most automatic machines handle this for you.", - "Beans are fresh for weeks, not months. Check for a roast date on the bag (not a 'best before' date). If the bag only has an expiry date a year out, that's a sign to try a different roaster or brand." - ], - "sections": [ - ["Storage: the freezer myth, mostly", - ["Do not store daily beans in the fridge โ€” they absorb moisture and food smells. An airtight container in a dark cupboard is right for coffee you'll finish within two weeks.", - "Freezing is legitimate only for long-term storage: split into small airtight portions, freeze, and grind straight from frozen. Thawing and refreezing is what ruins beans."]] - ] - }, - { - "heading": "Small fixes with outsized results", - "paras": [ - "Rinse paper filters with hot water before brewing to remove the papery taste and preheat your brewer. Preheat your mug too โ€” coffee poured into a cold ceramic mug can drop several degrees instantly, and lukewarm coffee tastes flat.", - "Stir the brew once after pouring (in any method). Grounds float and clump; a single stir evens out extraction more than people expect.", - "If your coffee tastes sour, grind finer or brew longer. If it tastes bitter and drying, grind coarser or brew shorter. Those two rules fix 90% of 'I don't like black coffee' cases โ€” sour and bitter are calibration errors, not personality traits.", - "Finally: clean your machine. Old oils rancidify and ruin every cup. Run a 1:2 vinegar-to-water cycle through drip machines monthly, and rinse French presses and reusable filters properly. It takes five minutes and tastes like a free upgrade." - ] - } - ] -}, -{ - "slug": "two-minute-rule-tiny-habits", - "title": "The Two-Minute Rule and Other Tiny Habits That Actually Stick", - "category": "Habits", - "dek": "Why willpower fails and frictions win: a practical guide to building habits small enough that you can't talk yourself out of them.", - "updated": "2026-09-16", - "read_min": 4, - "pages": [ - { - "heading": "Start stupidly small", - "paras": [ - "The two-minute rule has two halves. For new habits: whatever you want to do, shrink it until it takes two minutes. 'Read more' becomes 'read one page.' 'Get fit' becomes 'put on running shoes.' The point is not the two minutes โ€” it's showing up so reliably that the habit becomes part of who you are.", - "For overrun tasks: if something takes less than two minutes, do it now. Dishes in the machine, the reply to that one message, the bill on the counter. These micro-tasks are the grit in the gears of a tidy life.", - "The reason tiny works is that motivation is unreliable and friction is reliable. A habit you can do on your worst day is worth ten you can only do on your best." - ] - }, - { - "heading": "Anchor new habits to old ones", - "paras": [ - "The most reliable habit formula isn't a reminder or an app โ€” it's stacking: 'After I [existing habit], I will [new two-minute habit].' After I pour my coffee, I will stretch for two minutes. After I brush my teeth, I will lay out tomorrow's clothes.", - "Existing habits are automatic; you're just piggybacking on wiring that already exists. Choose anchors that happen daily at a predictable time, and keep the new action genuinely small for the first two weeks.", - "One habit at a time is the other rule people skip. Stacking three new habits at once means none of them get automatic before your enthusiasm runs out โ€” which it will, because that's normal, not weakness." - ], - "sections": [ - ["Make the streak visible, then protect it", - ["A paper calendar with an X for each day you did the thing works better than most apps, because it's physically in your way. The goal is not a perfect streak; it's never missing twice in a row.", - "Missing once is an accident. Missing twice is the start of a new (worse) habit."]] - ] - }, - { - "heading": "Design the room, not the willpower", - "paras": [ - "Every habit has friction on both sides: make good habits one step easier and bad habits one step harder. Fruit bowl on the counter, biscuits in a high cupboard. Guitar on a stand, not in a case. Junk food not in the house at all.", - "Environment beats intention because you only have to win the design decision once, instead of winning a willpower fight every evening.", - "Last thing: review monthly, not daily. Habits that survived a month deserve keeping; ones that didn't were probably too big. Shrink them and start again. The people with the most habits aren't the most disciplined โ€” they're the best at making the entry point tiny." - ] - } - ] -}, -{ - "slug": "house-smells-fresh", - "title": "How to Keep Your House Smelling Fresh All Week", - "category": "Home", - "dek": "Air fresheners mask smells for an hour. These six habits remove the sources โ€” and the fresh smell takes care of itself.", - "updated": "2026-09-16", - "read_min": 4, - "pages": [ - { - "heading": "Find the sources, not the symptoms", - "paras": [ - "A house smells stale for a handful of usual suspects: bin, fridge, soft furnishings, damp corners, and shoes by the door. Sprays sit on top of these for an hour and then the smell wins. Go source by source instead.", - "The bin is the big one. Wash it โ€” actually wash it with hot soapy water โ€” every time you empty it, and put a few sheets of kitchen paper at the bottom to absorb drips. A clean bin changes the whole kitchen.", - "The fridge comes second: a weekly ten-second scan for anything forgotten, plus an open box of baking soda at the back, replaced every couple of months." - ] - }, - { - "heading": "Airflow is the cheapest air freshener", - "paras": [ - "Stale smell is mostly just old air. Ten minutes of cross-ventilation โ€” a window front and back, doors open โ€” resets a room better than any product. Morning works well because outside air is coolest and freshest.", - "If a room smells musty rather than dirty, that's moisture. The fix is airflow or a small dehumidifier, because damp breeds the mildew smell no candle can cover.", - "Soft furnishings hold smells the longest. Cushions, throws, and curtains mostly need an hour of fresh air on a dry day rather than washing; carpets want a proper vacuum with the edges done, since edges are where smells settle." - ], - "sections": [ - ["The shoe rule and the laundry loop", - ["Shoes off at the door removes one of the biggest recurring smell sources in most homes โ€” a single doormat plus a shoe rack pays for itself immediately.", - "And don't let worn-once clothes sit in a closed laundry basket in a warm room; a ventilated basket (or a hook on the door for 'worn once' items) keeps bedrooms noticeably fresher."]] - ] - }, - { - "heading": "If you want a signature smell, do it last", - "paras": [ - "Once the sources are handled, a light signature scent actually works, because there's nothing underneath it fighting through. Simmer a pot of water with citrus peel and a cinnamon stick for twenty minutes; bake something; or just keep one room's window open on a breezy day.", - "Fabric spray done properly: a teaspoon of baking soda, a splash of fabric softener, water in a spray bottle โ€” mist curtains and sofas lightly. Test on a hidden spot first.", - "The weekly rhythm that keeps it all working: wash the bin, scan the fridge, vacuum edges, air the bedrooms, empty the laundry loop. Fifteen minutes total, and the house smells like someone who has their life together lives there." - ] - } - ] -}, -{ - "slug": "phone-settings-change-today", - "title": "10 Phone Settings You Should Change Today", - "category": "Tech", - "dek": "Ten minutes in your settings that make your phone quieter, faster, and easier on your battery and your brain.", - "updated": "2026-09-16", - "read_min": 5, - "pages": [ - { - "heading": "Reclaim your attention", - "paras": [ - "Turn off notifications for every app that isn't a person trying to reach you. Social feeds, shopping apps and games do not deserve interrupts; keep calls, messages, and calendar. On both Android and iOS this lives under Settings โ†’ Notifications, and the bulk purge takes ten minutes once and lasts years.", - "Next, move distracting apps off the home screen. Search is faster than hunting anyway. A home screen with only tools on it changes how often you open feeds โ€” you can't doomscroll an app you forgot is installed.", - "Finally, set a grayscale or bedtime mode to switch on automatically at night. A colorless screen at 11 p.m. is astonishingly boring, which is exactly the point." - ] - }, - { - "heading": "Battery and storage", - "paras": [ - "Disable 'always-on' background refresh for apps that don't need it (Settings โ†’ Battery, or Background App Refresh on iOS). Social apps refresh constantly for no benefit; maps and messaging need to stay live.", - "Turn on adaptive/optimized charging so the phone slows its charge overnight โ€” batteries age fastest pinned at 100% for hours. If your phone offers an 80% cap, use it for a longer battery lifespan.", - "For storage, the quiet killers are chat apps caching years of media. In WhatsApp/Telegram settings you can limit media auto-download and review large files; on iPhone, 'Offload Unused Apps' recovers space without losing data." - ], - "sections": [ - ["Safety basics people skip", - ["Set a longer unlock than a 4-digit PIN (biometrics plus a real password), and turn on remote-find for the device. Fifteen minutes now beats a lost-phone disaster later.", - "And enable automatic OS updates. Nearly every scary phone story starts with 'running a two-year-old unpatched system'."]] - ] - }, - { - "heading": "Quality-of-life settings", - "paras": [ - "Bump the text size up one notch โ€” most people run text smaller than comfortable and never notice the eye strain. Then turn on 'tap to wake' or lift-to-wake and skip a button press a hundred times a day.", - "Set 'do not disturb' to a schedule (sleep hours plus focused work blocks) with exceptions for starred contacts. This single setting is the closest thing to a digital off-switch that exists.", - "Last, open your keyboard settings and add text replacements for the things you type weekly โ€” your address, your email, 'running five minutes late'. Small thing, used hundreds of times a year, adds up to real minutes." - ] - } - ] -}, -{ - "slug": "store-groceries-longer", - "title": "How to Store Groceries So They Last Weeks Longer", - "category": "Kitchen", - "dek": "Most food waste is a storage problem, not a shopping problem. Where things go in the fridge โ€” and what never goes in it.", - "updated": "2026-09-16", - "read_min": 5, - "pages": [ - { - "heading": "The fridge has zones โ€” use them", - "paras": [ - "Your fridge is warmer at the door and coldest at the back bottom. Milk and raw meat belong in the cold zone, never the door โ€” the door is for condiments and things preserved by vinegar or salt, which is exactly what the door shelf was designed for.", - "The drawer most people call 'the crisper' is actually two drawers: one with a humidity vent open (for things that wilt โ€” leafy greens, herbs, carrots) and one closed (for things that rot โ€” berries, mushrooms, stone fruit). High humidity keeps greens crisp; low humidity keeps rot from spreading.", - "Keep greens away from apples, bananas and tomatoes. Those release ethylene gas, which ripens and then spoils everything around them โ€” useful if you want to ripen an avocado overnight, destructive if you don't." - ] - }, - { - "heading": "Things people refrigerate that shouldn't be", - "paras": [ - "Tomatoes lose flavor in the fridge โ€” the cold kills the enzymes that make them taste like tomatoes. Counter, stem-down, away from sun. Bread also goes stale faster in the fridge (staling is a cold reaction); a bread bin or freezer is right, the fridge is the worst of both.", - "Potatoes and onions want dark, cool, ventilated cupboard space โ€” and not next to each other: onions make potatoes sprout faster. Whole garlic, winter squash and unripe fruit join them on the counter.", - "Fresh herbs split into two camps: soft herbs (basil, parsley, cilantro) keep like flowers โ€” a jar of water, loose bag over the top. Hard herbs (rosemary, thyme) keep wrapped in a damp towel in the drawer." - ], - "sections": [ - ["The freezer is a pause button for almost everything", - ["Bread, grated cheese, butter, cooked rice, ripe bananas (for baking), stock, tomato paste in ice-cube trays, even milk. Portion before freezing โ€” a solid brick of anything is its own kind of waste.", - "Label everything with the date. The unlabeled frozen mystery is how freezers become graveyards."]] - ] - }, - { - "heading": "Small habits that finish the job", - "paras": [ - "Don't wash berries until you're about to eat them โ€” moisture accelerates mold. Do wash them in a vinegar-water bath (1:3) before storage if you bought a lot; dried thoroughly, they last noticeably longer.", - "Keep a running 'eat me first' container for the odds and ends, and put it at eye level. Half the battle against waste is visibility, not discipline.", - "And do a five-minute fridge check the day before your shopping day. It turns your list from a guess into an inventory, which saves money twice: once at the store and once in the bin you don't fill." - ] - } - ] -}, -{ - "slug": "walking-for-fitness-beginner", - "title": "Walking for Fitness: A Beginner Plan You'll Actually Stick To", - "category": "Fitness", - "dek": "Walking is the most underrated fitness tool there is. A four-week plan that starts embarrassingly easy on purpose.", - "updated": "2026-09-16", - "read_min": 5, - "pages": [ - { - "heading": "Why walking counts (it really does)", - "paras": [ - "Brisk walking gets your heart rate into the same moderate zone as a gentle jog, it's gentle on joints, it requires zero equipment, and โ€” the part everyone skips โ€” it's the exercise people actually keep doing. Consistency beats intensity for health outcomes, every time.", - "Brisk is the key word: you should be able to talk but not sing. If you can sing along to your podcast, speed up. If you can't speak, slow down.", - "The plan below starts at twenty minutes because starting too hard is why most fitness plans die in week two. The first fortnight's job is to build the habit, not your VO2 max." - ], - "sections": [ - ["Weeks 1โ€“2: earn the streak", - ["Walk 20 minutes at whatever pace feels easy, five days a week. Same time each day if you can โ€” mornings fail less often because the day hasn't gotten in the way yet.", - "That's it. Do not add intervals, weights, or hills yet. You're building the appointment, not the workout."]] - ] - }, - { - "heading": "Weeks 3โ€“4: add shape, not pain", - "paras": [ - "Now make two walks a week a 'structured' walk: five minutes easy, then ten minutes alternating one minute faster with one minute easy, then five minutes easy. That's it โ€” a gentle interval session that quietly doubles the fitness value.", - "On the other days, keep the easy twenty minutes and add five if it feels good. If life happens and you can only manage ten, do the ten โ€” the streak matters more than the duration.", - "Posture check worth doing once: look ahead, not down; relax the shoulders; let the arms swing. If your feet or shins complain, your shoes are the first suspect โ€” walking shoes or comfortable trainers, not stiff fashion sneakers." - ] - }, - { - "heading": "Making it stick past week four", - "paras": [ - "Attach the walk to something you like: a podcast you only allow yourself while walking, a route with a view, a standing coffee at the halfway point. The habit should carry its own small reward.", - "Track minutes, not weight. Fitness from walking shows up in energy and mood first and on the scale weeks later โ€” people who track weight only quit right before it starts working.", - "And have a rain plan. An umbrella, a covered shopping street, or a backup indoor loop turns the weather from a decision-maker into a detail. Walkers who last are simply walkers who don't renegotiate with themselves every day." - ] - } - ] -} -] diff --git a/helpme-tips/deploy.sh b/helpme-tips/deploy.sh deleted file mode 100755 index 6bc3c5d..0000000 --- a/helpme-tips/deploy.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -# Build + sync docroot in place (hostPath pattern: never swap directories). -set -euo pipefail -cd "$(dirname "$0")" -BASE=/home/opc/zai-home-base - -echo "==> building site" -python3 build.py - -mkdir -p "$BASE/sites/helpme-tips" -if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" = "Enforcing" ]; then - chcon -Rt container_file_t "$BASE/sites/helpme-tips" 2>/dev/null || true -fi -rsync -a --delete site/ "$BASE/sites/helpme-tips/" - -echo "==> verifying through Traefik (origin)" -sleep 1 -code=$(curl -sk -o /dev/null -w '%{http_code}' --resolve helpme.tips:443:127.0.0.1 https://helpme.tips/ --max-time 10) -echo " https://helpme.tips -> $code (origin)" -for asset in /static/style.css /static/main.js /sitemap.xml; do - out=$(curl -sk -o /dev/null -w '%{http_code} %{content_type}' --resolve helpme.tips:443:127.0.0.1 "https://helpme.tips$asset" --max-time 10) - echo " $asset -> $out" -done -echo "==> deploy complete" diff --git a/helpme-tips/k8s/helpme-tips.yaml b/helpme-tips/k8s/helpme-tips.yaml deleted file mode 100644 index 12eae48..0000000 --- a/helpme-tips/k8s/helpme-tips.yaml +++ /dev/null @@ -1,116 +0,0 @@ -# helpme.tips โ€” static site deployment. Reuses the shared static-nginx-conf ConfigMap -# (defined in bdj/k8s/static-sites.yaml) and the cluster pattern: -# Traefik edge + cert-manager Let's Encrypt. -# NOTE: Ingress/Certificate stay pending until helpme.tips is added to Cloudflare -# and the cert-manager token scope covers the zone. ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-hts - labels: - site: helpme.tips -spec: - replicas: 1 - selector: - matchLabels: - site: helpme.tips - template: - metadata: - labels: - site: helpme.tips - spec: - nodeSelector: - kubernetes.io/hostname: djg-oracle-sl - tolerations: - - key: node-role.kubernetes.io/master - operator: Exists - effect: NoSchedule - - key: node-role.kubernetes.io/control-plane - operator: Exists - effect: NoSchedule - containers: - - name: nginx - image: nginx:alpine - volumeMounts: - - name: conf - mountPath: /etc/nginx/nginx.conf - subPath: nginx.conf - readOnly: true - - name: html - mountPath: /usr/share/nginx/html - readOnly: true - resources: - requests: - cpu: 20m - memory: 32Mi - limits: - memory: 128Mi - readinessProbe: - httpGet: {path: /, port: 80} - initialDelaySeconds: 2 - periodSeconds: 20 - volumes: - - name: conf - configMap: - name: static-nginx-conf - - name: html - hostPath: - path: /home/opc/zai-home-base/sites/helpme-tips - type: Directory ---- -apiVersion: v1 -kind: Service -metadata: - name: static-hts -spec: - selector: - site: helpme.tips - ports: - - port: 80 - targetPort: 80 ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: helpme-tips -spec: - rules: - - host: helpme.tips - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: static-hts - port: - number: 80 - - host: www.helpme.tips - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: static-hts - port: - number: 80 - tls: - - hosts: - - helpme.tips - - www.helpme.tips - secretName: helpme-tips-tls ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: helpme-tips -spec: - secretName: helpme-tips-tls - issuerRef: - name: letsencrypt-prod - kind: ClusterIssuer - dnsNames: - - helpme.tips - - www.helpme.tips diff --git a/helpme-tips/static/favicon.svg b/helpme-tips/static/favicon.svg deleted file mode 100644 index 9d3685a..0000000 --- a/helpme-tips/static/favicon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/helpme-tips/static/main.js b/helpme-tips/static/main.js deleted file mode 100644 index 8d2b4b6..0000000 --- a/helpme-tips/static/main.js +++ /dev/null @@ -1,5 +0,0 @@ -/* helpme.tips โ€” minimal client behavior. Ad wrapper integration lands here. */ -(function () { - 'use strict'; - // Reading progress could go here; kept intentionally light for speed. -})(); diff --git a/helpme-tips/static/style.css b/helpme-tips/static/style.css deleted file mode 100644 index ad8f410..0000000 --- a/helpme-tips/static/style.css +++ /dev/null @@ -1,77 +0,0 @@ -/* helpme.tips โ€” bright, fast, clean */ -* { box-sizing: border-box; margin: 0; padding: 0; } -:root { - --brand: #0f766e; --brand2: #f59e0b; - --ink: #1f2937; --dim: #6b7280; --bg: #fafaf7; --card: #fff; - --radius: 14px; -} -body { - font-family: -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - background: var(--bg); color: var(--ink); line-height: 1.65; -} -a { color: var(--brand); } -.wrap { max-width: 900px; margin: 0 auto; padding: 0 20px; } -.narrow { max-width: 720px; } -.center { text-align: center; padding: 60px 20px; } - -.site-header { background: var(--card); border-bottom: 2px solid var(--ink); position: sticky; top: 0; z-index: 50; } -.head-row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px; padding: 12px 20px; } -.logo { font-size: 1.3rem; font-weight: 800; color: var(--ink); text-decoration: none; letter-spacing: -0.4px; } -.logo span { color: var(--brand); } -.nav { display: flex; gap: 4px; flex-wrap: wrap; } -.nav a { color: var(--ink); text-decoration: none; font-weight: 600; font-size: .95rem; padding: 6px 12px; border-radius: 999px; } -.nav a:hover { background: var(--bg); } - -.hero { padding: 40px 20px 10px; } -h1 { font-size: clamp(1.8rem, 4.5vw, 2.6rem); letter-spacing: -0.5px; line-height: 1.15; } -.hero-sub { color: var(--dim); font-size: 1.08rem; max-width: 42rem; margin-top: 8px; } -h2 { font-size: 1.4rem; margin: 34px 0 14px; letter-spacing: -0.3px; } -h3 { margin: 18px 0 6px; } - -.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; } -.card { - background: var(--card); border: 1.5px solid var(--ink); border-radius: var(--radius); - box-shadow: 4px 4px 0 rgba(31,41,55,.9); padding: 18px; text-decoration: none; color: var(--ink); - display: flex; flex-direction: column; gap: 6px; transition: transform .08s, box-shadow .08s; -} -.card:hover { transform: translate(-2px, -2px); box-shadow: 6px 6px 0 rgba(31,41,55,.9); } -.card .cat { color: var(--brand); font-weight: 800; font-size: .75rem; text-transform: uppercase; letter-spacing: 1px; } -.card h3 { margin: 0; font-size: 1.08rem; } -.card p { color: var(--dim); font-size: .92rem; } -.card .meta { color: var(--dim); font-size: .8rem; margin-top: auto; } - -.article { padding-top: 26px; } -.crumbs { font-size: .85rem; color: var(--dim); margin-bottom: 10px; } -.crumbs a { color: var(--dim); } -.article h1 { margin-bottom: 6px; } -.dek { color: var(--dim); font-size: 1.05rem; } -.meta { color: var(--dim); font-size: .85rem; margin: 8px 0 4px; } -.content { max-width: 44rem; } -.content p { margin: 0 0 14px; } -.dots { display: flex; gap: 6px; margin: 22px 0 10px; } -.dots span { width: 10px; height: 10px; border-radius: 50%; background: #e5e7eb; } -.dots span.on { background: var(--brand); } -.pager { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin: 10px 0 30px; } -.pageno { color: var(--dim); font-size: .9rem; font-weight: 600; } -.btn { - display: inline-block; font: inherit; font-weight: 700; text-decoration: none; cursor: pointer; - background: var(--brand); color: #fff; border: 2px solid var(--ink); border-radius: 999px; - padding: 9px 20px; box-shadow: 3px 3px 0 rgba(31,41,55,.9); -} -.btn:hover { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 rgba(31,41,55,.9); } -.btn.ghost { background: var(--card); color: var(--ink); } -.more { border-top: 1.5px solid #e5e7eb; padding-top: 8px; } -.seo { margin-top: 20px; } -.seo p { color: var(--dim); max-width: 46rem; } - -.page-sub { color: var(--dim); margin-bottom: 16px; } -.plain { margin: 10px 0 0 20px; } -.plain li { margin-bottom: 8px; } - -.site-footer { background: var(--ink); color: #f3f4f6; margin-top: 50px; padding: 30px 0; } -.site-footer .wrap p { margin-bottom: 10px; opacity: .85; font-size: .92rem; } -.foot-logo { font-weight: 800; font-size: 1.1rem; } -.foot-logo span { color: var(--brand2); } -.foot-links { display: flex; gap: 16px; flex-wrap: wrap; } -.foot-links a { color: var(--brand2); text-decoration: none; } -.tiny { font-size: .8rem; opacity: .6; }