Track 1: bestdadjokes.lol — 273-joke content site, edge nginx on k3s
This commit is contained in:
commit
0e68abc772
11 changed files with 1397 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
site/
|
||||
sites/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
node_modules/
|
||||
.env
|
||||
38
README.md
Normal file
38
README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# zai-home-base — revenue projects on djg-oracle-sl
|
||||
|
||||
Workspace for projects built to generate revenue. All sites are served by a
|
||||
shared nginx edge DaemonSet in k3s (hostPort 80) reading from `sites/`.
|
||||
|
||||
## Layout
|
||||
|
||||
- `bdj/` — bestdadjokes.lol (dad jokes content site, Track 1)
|
||||
- `content/jokes.json` — the dataset (append-only; joke ids = array index)
|
||||
- `build.py` — stdlib-only static site generator (Python 3.6)
|
||||
- `static/` — css/js/favicon (jokes-data.js is generated, don't hand-edit)
|
||||
- `site/` — build output (gitignored)
|
||||
- `nginx/conf.d/` — per-site nginx vhosts (mounted into edge pod)
|
||||
- `k8s/nginx-edge.yaml` — shared edge DaemonSet (hostNetwork, port 80)
|
||||
- `deploy.sh` — build + atomic swap + reload + verify
|
||||
- `sites/` — live docroots served by the edge pod (generated, gitignored)
|
||||
- `nginx-ingress.yaml` (in /home/opc) — leftover demo manifest, unused
|
||||
|
||||
## Deploy a content change
|
||||
|
||||
cd bdj && ./deploy.sh
|
||||
|
||||
## Server facts
|
||||
|
||||
- VM: djg-oracle-sl (Oracle Cloud ARM, 4c/23GB), public IP 144.24.30.131
|
||||
- k3s single node (control-plane taint — pods need the toleration in k8s/nginx-edge.yaml)
|
||||
- kubectl: `sudo /usr/local/bin/k3s kubectl` (no standalone kubeconfig for opc)
|
||||
- Local test: `curl -H 'Host: bestdadjokes.lol' http://127.0.0.1/`
|
||||
- Inbound 80/443 must be open in the OCI VCN security list (cloud console side)
|
||||
- DNS: user manages Cloudflare; site goes live with proxied A record → 144.24.30.131
|
||||
|
||||
## Roadmap (bdj)
|
||||
|
||||
1. DNS live → submit to Google Search Console (HTML meta verification)
|
||||
2. Social content (needs user-provided accounts) — daily share-card images
|
||||
3. Ad integration: user's MCM/AdX stack; ad slots are marked `<!-- AD_SLOT_* -->`
|
||||
4. Expand dataset daily; long-tail topic pages ("dad jokes about coffee")
|
||||
5. Phase 2 (myadhd.dev): ADHD micro-tools + Paddle/Stripe when user ready
|
||||
490
bdj/build.py
Normal file
490
bdj/build.py
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Static site generator for bestdadjokes.lol. Python 3.6 stdlib only."""
|
||||
import json, os, 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
|
||||
|
||||
|
||||
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 = '<p class="joke-setup multi">%s</p>' % esc(j['setup']).replace('\n', '<br>')
|
||||
else:
|
||||
body = '<p class="joke-setup">%s</p>' % esc(j['setup'])
|
||||
punch = '<div class="joke-punch" data-punch>%s</div>' % esc(j['punch'])
|
||||
share = ('<button class="btn-tiny" data-share="%d">🖼️ Share as image</button>'
|
||||
'<button class="btn-tiny" data-copy="%d">📋 Copy</button>' % (j['id'], j['id']))
|
||||
return ('<figure class="%s" id="joke-%d">%s%s'
|
||||
'<figcaption class="joke-actions">%s</figcaption></figure>'
|
||||
% (cls, j['id'], body, punch, share))
|
||||
|
||||
|
||||
def layout(title, desc, body, path='/', extra_head=''):
|
||||
nav_cats = ''.join(
|
||||
'<a href="/category/%s/">%s %s</a>' % (c['slug'], c['emoji'], esc(c['name']))
|
||||
for c in CATS[:7])
|
||||
rest = ''.join('<a href="/category/%s/">%s %s</a>' % (c['slug'], c['emoji'], esc(c['name']))
|
||||
for c in CATS[7:])
|
||||
foot_cats = ''.join('<a href="/category/%s/">%s</a>' % (c['slug'], esc(c['name'])) for c in CATS)
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{esc(title)}</title>
|
||||
<meta name="description" content="{esc(desc)}">
|
||||
<link rel="canonical" href="{SITE_URL}{esc(path)}">
|
||||
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
|
||||
<link rel="alternate" type="application/rss+xml" title="Dad Joke of the Day" href="/rss.xml">
|
||||
<meta property="og:site_name" content="{SITE_NAME}">
|
||||
<meta property="og:title" content="{esc(title)}">
|
||||
<meta property="og:description" content="{esc(desc)}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{SITE_URL}{esc(path)}">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<style>:root{{--brand:#e4572e;--brand2:#f5a623;--ink:#2b2118;--cream:#fff8ec;--card:#ffffff}}</style>
|
||||
{extra_head}
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
<div class="wrap header-row">
|
||||
<a class="logo" href="/">😂 <strong>Best<span>Dad</span>Jokes</strong><em>.lol</em></a>
|
||||
<input type="checkbox" id="navtoggle" class="navtoggle">
|
||||
<label for="navtoggle" class="navburger" aria-label="Menu">☰</label>
|
||||
<nav class="site-nav">
|
||||
<a href="/" {'class="on"' if path == '/' else ''}>Home</a>
|
||||
<a href="/dad-joke-generator/" {'class="on"' if path.startswith('/dad-joke-generator') else ''}>Joke Generator</a>
|
||||
<a href="/joke-of-the-day/" {'class="on"' if path.startswith('/joke-of-the-day') else ''}>Joke of the Day</a>
|
||||
<a href="/search/" {'class="on"' if path.startswith('/search') else ''}>Search</a>
|
||||
<a href="/about/" {'class="on"' if path.startswith('/about') else ''}>About</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="wrap catbar">
|
||||
{nav_cats}<a href="#all-cats">more →</a>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
{body}
|
||||
</main>
|
||||
<footer class="site-footer">
|
||||
<div class="wrap foot-grid">
|
||||
<div>
|
||||
<p class="foot-logo">😂 Best<span>Dad</span>Jokes<em>.lol</em></p>
|
||||
<p>Hi, I'm a dad. These are my jokes. Someone has to laugh at them, and it might as well be the internet.</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="foot-h">Categories</p>
|
||||
<div class="foot-links">{foot_cats}</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="foot-h">More</p>
|
||||
<div class="foot-links">
|
||||
<a href="/dad-joke-generator/">Dad Joke Generator</a>
|
||||
<a href="/joke-of-the-day/">Joke of the Day</a>
|
||||
<a href="/search/">Search Jokes</a>
|
||||
<a href="/rss.xml">RSS Feed</a>
|
||||
<a href="/api/v1/random.json">Free JSON API</a>
|
||||
<a href="/about/">About</a>
|
||||
<a href="/contact/">Contact</a>
|
||||
<a href="/privacy-policy/">Privacy Policy</a>
|
||||
<a href="/terms/">Terms</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrap foot-base">
|
||||
<p>© {YEAR} {SITE_NAME} · Family-friendly jokes, dad-certified since the Big Dad Energy era.</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/static/jokes-data.js" defer></script>
|
||||
<script src="/static/main.js" defer></script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
||||
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]
|
||||
cat_cards = ''.join(f"""
|
||||
<a class="cat-card" href="/category/{c['slug']}/">
|
||||
<span class="cat-emoji">{c['emoji']}</span>
|
||||
<span class="cat-name">{esc(c['name'])}</span>
|
||||
<span class="cat-count">{joke_count(c['slug'])} jokes</span>
|
||||
</a>""" for c in CATS)
|
||||
latest_cards = ''.join(joke_card(j) for j in latest)
|
||||
body = f"""
|
||||
<section class="hero wrap">
|
||||
<h1>The Best Dad Jokes on the Internet</h1>
|
||||
<p class="hero-sub">Short. Clean. Painfully punny. {len(JOKES)}+ jokes your kids don't want to hear but absolutely need to.</p>
|
||||
<div class="hero-card">
|
||||
<p class="joke-setup" data-hero-setup>{esc(rnd['setup'])}</p>
|
||||
<div class="joke-punch" data-hero-punch>{esc(rnd['punch'])}</div>
|
||||
<div class="hero-btns">
|
||||
<button class="btn" data-hero-next>🎲 Another one</button>
|
||||
<button class="btn btn-ghost" data-copy-hero>📋 Copy</button>
|
||||
<button class="btn btn-ghost" data-share-hero>🖼️ Share as image</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- AD_SLOT_TOP -->
|
||||
|
||||
<section class="wrap">
|
||||
<div class="jod-banner">
|
||||
<p class="jod-label">😄 Dad Joke of the Day</p>
|
||||
<p class="joke-setup">{esc(jod['setup'])}</p>
|
||||
<div class="joke-punch" data-punch>{esc(jod['punch'])}</div>
|
||||
<p class="jod-more"><a href="/joke-of-the-day/">See the archive →</a></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="wrap" id="all-cats">
|
||||
<h2>Browse Dad Jokes by Category</h2>
|
||||
<div class="cat-grid">{cat_cards}</div>
|
||||
</section>
|
||||
|
||||
<!-- AD_SLOT_MID -->
|
||||
|
||||
<section class="wrap">
|
||||
<h2>Fresh Out of the Dad Oven</h2>
|
||||
<div class="joke-grid">{latest_cards}</div>
|
||||
<p class="center-link"><a class="btn" href="/dad-joke-generator/">Open the Dad Joke Generator →</a></p>
|
||||
</section>
|
||||
|
||||
<section class="wrap seo-blurb">
|
||||
<h2>What is a dad joke?</h2>
|
||||
<p>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.</p>
|
||||
<p>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 <a href="/dad-joke-generator/">dad joke generator</a>, browse the <a href="#all-cats">categories</a>, or come back every day for a new <a href="/joke-of-the-day/">dad joke of the day</a>.</p>
|
||||
</section>"""
|
||||
title = 'Best Dad Jokes — 273+ Funny, Clean Dad Jokes (One-Liners, Puns & Knock-Knocks)'
|
||||
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, '/',
|
||||
'<script type="application/ld+json">%s</script>' % 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('<a class="chip" href="/category/%s/">%s %s</a>' % (o['slug'], o['emoji'], esc(o['name'])) for o in others)
|
||||
body = f"""
|
||||
<div class="wrap">
|
||||
<nav class="crumbs"><a href="/">Home</a> › <span>{esc(c['name'])}</span></nav>
|
||||
<h1>{c['emoji']} {esc(c['name'])}</h1>
|
||||
<p class="page-sub">{esc(c['blurb'])} That's {len(js)} jokes of pure dad energy.</p>
|
||||
<div class="joke-grid">{cards}</div>
|
||||
<!-- AD_SLOT_CAT -->
|
||||
<h2>Keep the groans going</h2>
|
||||
<div class="chips">{others_html}</div>
|
||||
<p class="center-link"><a class="btn" href="/dad-joke-generator/">🎲 Random Dad Joke Generator</a></p>
|
||||
</div>"""
|
||||
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,
|
||||
'<script type="application/ld+json">%s</script>' % esc(json.dumps(ld)))
|
||||
|
||||
|
||||
def page_generator():
|
||||
body = """
|
||||
<div class="wrap narrow">
|
||||
<h1>🎲 Dad Joke Generator</h1>
|
||||
<p class="page-sub">One click. One random dad joke. Zero apologies. Works at parties, in the car line, and mid-argument.</p>
|
||||
<div class="hero-card gen-card">
|
||||
<p class="joke-setup" data-gen-setup>Click the button to summon a dad joke.</p>
|
||||
<div class="joke-punch" data-gen-punch></div>
|
||||
<div class="hero-btns">
|
||||
<button class="btn" data-gen-next>🎲 Generate dad joke</button>
|
||||
<button class="btn btn-ghost" data-gen-copy>📋 Copy</button>
|
||||
<button class="btn btn-ghost" data-gen-share>🖼️ Share as image</button>
|
||||
</div>
|
||||
</div>
|
||||
<h2>How to weaponize a dad joke</h2>
|
||||
<ol class="plain-list">
|
||||
<li><strong>Deliver with confidence.</strong> Eye contact is everything. The dad joke fears hesitation.</li>
|
||||
<li><strong>Pause before the punchline.</strong> Let the suspense marinate. Then pounce.</li>
|
||||
<li><strong>Ignore the groans.</strong> The groan is applause in dad dialect.</li>
|
||||
<li><strong>Repeat the best ones.</strong> Repetition builds tradition. Tradition builds groans.</li>
|
||||
</ol>
|
||||
<p>Looking for something specific? <a href="/search/">Search all dad jokes</a> or browse by <a href="/#all-cats">category</a>.</p>
|
||||
</div>"""
|
||||
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"""
|
||||
<div class="wrap narrow">
|
||||
<h1>😄 Dad Joke of the Day</h1>
|
||||
<p class="page-sub">A brand-new dad joke every single day. Doctors recommend exactly one per day. We won't tell if you don't.</p>
|
||||
<div class="jod-banner jod-page">
|
||||
<p class="jod-date" data-jod-date>{d.strftime('%B %d, %Y')}</p>
|
||||
<p class="joke-setup" data-jod-setup>{esc(j['setup'])}</p>
|
||||
<div class="joke-punch" data-punch data-jod-punch>{esc(j['punch'])}</div>
|
||||
<div class="hero-btns">
|
||||
<button class="btn btn-ghost" data-jod-copy>📋 Copy</button>
|
||||
<button class="btn btn-ghost" data-jod-share>🖼️ Share as image</button>
|
||||
</div>
|
||||
</div>
|
||||
<h2>The dad joke archive</h2>
|
||||
<p>Every recent dad joke of the day, one per day, no repeats (for a few months, anyway):</p>
|
||||
<div class="archive" data-jod-archive></div>
|
||||
<p class="center-link"><a class="btn" href="/dad-joke-generator/">Can't wait a whole day? Generator →</a></p>
|
||||
</div>"""
|
||||
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/',
|
||||
'<link rel="canonical" href="%s/joke-of-the-day/">' % SITE_URL +
|
||||
'<script type="application/ld+json">%s</script>' % esc(json.dumps(ld)))
|
||||
|
||||
|
||||
def page_search():
|
||||
body = """
|
||||
<div class="wrap narrow">
|
||||
<h1>🔎 Search Dad Jokes</h1>
|
||||
<p class="page-sub">Looking for a dad joke about coffee, cats, or cardio? Type it in. We probably have a pun for that.</p>
|
||||
<form class="search-form" onsubmit="return false;">
|
||||
<input type="search" id="search-box" placeholder="Try 'pizza', 'cow', 'work'..." autocomplete="off">
|
||||
</form>
|
||||
<p class="search-count" data-search-count></p>
|
||||
<div class="joke-grid" data-search-results></div>
|
||||
<p data-search-empty style="display:none">No jokes matched. Which, honestly, is a great setup for a new dad joke — send it to us via the <a href="/contact/">contact page</a>.</p>
|
||||
</div>"""
|
||||
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 = """
|
||||
<div class="wrap narrow">
|
||||
<h1>👋 About Best Dad Jokes</h1>
|
||||
<p>Welcome to <strong>BestDadJokes.lol</strong> — 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.</p>
|
||||
<p>The mission is simple: build the most useful dad joke resource on the internet. That means:</p>
|
||||
<ul class="plain-list">
|
||||
<li><strong>Curated, not scraped.</strong> Every joke is hand-picked for maximum groan. If it doesn't make someone audibly sigh, it doesn't make the cut.</li>
|
||||
<li><strong>Family-friendly, always.</strong> Clean humor that's safe to tell at the dinner table, the office, or the school pickup line.</li>
|
||||
<li><strong>Fast and simple.</strong> No pop-ups, no logins, no nonsense. Just jokes.</li>
|
||||
</ul>
|
||||
<h2>Why dad jokes?</h2>
|
||||
<p>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.</p>
|
||||
<h2>Get in touch</h2>
|
||||
<p>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 <a href="/contact/">contact page</a>.</p>
|
||||
</div>"""
|
||||
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 = """
|
||||
<div class="wrap narrow">
|
||||
<h1>📮 Contact</h1>
|
||||
<p>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.</p>
|
||||
<p>Email us at <a href="mailto:hello@bestdadjokes.lol">hello@bestdadjokes.lol</a> — responses may be delayed by naps.</p>
|
||||
<h2>Joke submissions</h2>
|
||||
<p>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.</p>
|
||||
</div>"""
|
||||
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 = """
|
||||
<div class="wrap narrow">
|
||||
<h1>Privacy Policy</h1>
|
||||
<p class="page-sub">Last updated: %s</p>
|
||||
<p>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:</p>
|
||||
<h2>What we collect</h2>
|
||||
<p>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.</p>
|
||||
<h2>Cookies and advertising</h2>
|
||||
<p>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.</p>
|
||||
<p>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 <a href="https://www.google.com/settings/ads" rel="nofollow noopener">Google Ads Settings</a>, or opt out of third-party vendor cookies at <a href="https://www.aboutads.info" rel="nofollow noopener">aboutads.info</a>.</p>
|
||||
<p>If we run a consent banner for visitors from the EEA/UK, your choices will be honored and stored locally.</p>
|
||||
<h2>Analytics</h2>
|
||||
<p>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.</p>
|
||||
<h2>Data sharing</h2>
|
||||
<p>We do not sell personal data. We have no personal data to sell. We barely have data.</p>
|
||||
<h2>Children</h2>
|
||||
<p>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.</p>
|
||||
<h2>Contact</h2>
|
||||
<p>Questions about this policy? Email <a href="mailto:hello@bestdadjokes.lol">hello@bestdadjokes.lol</a>.</p>
|
||||
</div>""" % 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 = """
|
||||
<div class="wrap narrow">
|
||||
<h1>Terms of Use</h1>
|
||||
<p class="page-sub">Last updated: %s</p>
|
||||
<p>By using BestDadJokes.lol you agree to the following terms. Reading them aloud in a dad voice is optional but encouraged.</p>
|
||||
<h2>Use of content</h2>
|
||||
<p>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.</p>
|
||||
<h2>No warranties</h2>
|
||||
<p>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.</p>
|
||||
<h2>Limitation of liability</h2>
|
||||
<p>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.</p>
|
||||
<h2>External links</h2>
|
||||
<p>We are not responsible for the content of external websites, including their jokes, which are almost certainly worse than ours.</p>
|
||||
<h2>Changes</h2>
|
||||
<p>We may update these terms occasionally. Continued use of the site means you accept the updated terms, and possibly a fresh dad joke.</p>
|
||||
</div>""" % 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 = """
|
||||
<div class="wrap narrow center">
|
||||
<h1>404</h1>
|
||||
<p class="page-sub">This page is like a dad joke that got cut from the site — it just didn't land.</p>
|
||||
<div class="hero-card">
|
||||
<p class="joke-setup">Why did the web page go to therapy?</p>
|
||||
<div class="joke-punch" data-punch>It had too many broken links.</div>
|
||||
</div>
|
||||
<p><a class="btn" href="/">Take me home →</a></p>
|
||||
</div>"""
|
||||
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']))
|
||||
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]
|
||||
write('robots.txt', 'User-agent: *\nAllow: /\n\nSitemap: %s/sitemap.xml\n' % SITE_URL)
|
||||
today = datetime.date.today().isoformat()
|
||||
sm = ['<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">']
|
||||
for p in pages:
|
||||
sm.append('<url><loc>%s%s</loc><lastmod>%s</lastmod><changefreq>%s</changefreq><priority>%s</priority></url>'
|
||||
% (SITE_URL, p, today, 'weekly' if p == '/' else 'monthly', '1.0' if p == '/' else '0.8'))
|
||||
sm.append('</urlset>')
|
||||
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(
|
||||
'<item><title>%s</title><link>%s/joke-of-the-day/?d=%s</link>'
|
||||
'<guid isPermaLink="false">bjd-daily-%s</guid>'
|
||||
'<pubDate>%s</pubDate><description>%s — %s</description></item>'
|
||||
% (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 = ('<?xml version="1.0" encoding="UTF-8"?>'
|
||||
'<rss version="2.0"><channel><title>%s — Dad Joke of the Day</title>'
|
||||
'<link>%s/joke-of-the-day/</link><description>%s</description>'
|
||||
'<language>en-us</language>%s</channel></rss>'
|
||||
% (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()
|
||||
306
bdj/content/jokes.json
Normal file
306
bdj/content/jokes.json
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
{
|
||||
"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."}
|
||||
]
|
||||
}
|
||||
37
bdj/deploy.sh
Executable file
37
bdj/deploy.sh
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env bash
|
||||
# Build the static site and ensure the edge nginx serves it.
|
||||
# Content changes need no pod restart (hostPath is live); config changes trigger a reload.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
BASE=/home/opc/zai-home-base
|
||||
K="sudo /usr/local/bin/k3s kubectl"
|
||||
|
||||
echo "==> building site"
|
||||
python3 build.py
|
||||
|
||||
mkdir -p "$BASE/sites"
|
||||
rm -rf "$BASE/sites/bestdadjokes.new"
|
||||
cp -r site "$BASE/sites/bestdadjokes.new"
|
||||
# ensure SELinux-friendly context if SELinux is enforcing
|
||||
if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" = "Enforcing" ]; then
|
||||
chcon -Rt container_file_t "$BASE/sites/bestdadjokes.new" 2>/dev/null || true
|
||||
chcon -Rt container_file_t "$BASE/bdj/nginx/conf.d" 2>/dev/null || true
|
||||
fi
|
||||
rm -rf "$BASE/sites/bestdadjokes.old"
|
||||
[ -d "$BASE/sites/bestdadjokes" ] && mv "$BASE/sites/bestdadjokes" "$BASE/sites/bestdadjokes.old"
|
||||
mv "$BASE/sites/bestdadjokes.new" "$BASE/sites/bestdadjokes"
|
||||
rm -rf "$BASE/sites/bestdadjokes.old"
|
||||
|
||||
echo "==> applying edge manifest"
|
||||
$K apply -f k8s/nginx-edge.yaml
|
||||
|
||||
POD=$($K get pods -l app=nginx-edge -o jsonpath='{.items[0].metadata.name}')
|
||||
echo "==> reloading nginx config in pod $POD"
|
||||
$K exec "$POD" -- nginx -s reload 2>/dev/null || true
|
||||
|
||||
echo "==> verifying"
|
||||
sleep 1
|
||||
code=$($K exec "$POD" -- wget -q -O- --header="Host: bestdadjokes.lol" http://127.0.0.1/ | head -c 60)
|
||||
echo "first bytes of homepage: $code"
|
||||
echo "==> deploy complete"
|
||||
66
bdj/k8s/nginx-edge.yaml
Normal file
66
bdj/k8s/nginx-edge.yaml
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: nginx-edge
|
||||
namespace: default
|
||||
labels:
|
||||
app: nginx-edge
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx-edge
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx-edge
|
||||
spec:
|
||||
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: nginx
|
||||
image: nginx:alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- name: conf
|
||||
mountPath: /etc/nginx/conf.d
|
||||
readOnly: true
|
||||
- name: sites
|
||||
mountPath: /srv/www
|
||||
readOnly: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: 20m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
memory: 128Mi
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
httpHeaders:
|
||||
- name: Host
|
||||
value: bestdadjokes.lol
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 30
|
||||
volumes:
|
||||
- name: conf
|
||||
hostPath:
|
||||
path: /home/opc/zai-home-base/bdj/nginx/conf.d
|
||||
type: Directory
|
||||
- name: sites
|
||||
hostPath:
|
||||
path: /home/opc/zai-home-base/sites
|
||||
type: DirectoryOrCreate
|
||||
37
bdj/nginx/conf.d/bestdadjokes.lol.conf
Normal file
37
bdj/nginx/conf.d/bestdadjokes.lol.conf
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# bestdadjokes.lol
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name bestdadjokes.lol;
|
||||
|
||||
root /srv/www/bestdadjokes;
|
||||
index index.html;
|
||||
|
||||
error_page 404 /404.html;
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/javascript application/json application/xml image/svg+xml;
|
||||
gzip_min_length 1024;
|
||||
|
||||
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 = /favicon.ico { return 302 /static/favicon.svg; }
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ $uri/index.html =404;
|
||||
}
|
||||
}
|
||||
|
||||
# www redirect
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name www.bestdadjokes.lol;
|
||||
return 301 http://bestdadjokes.lol$request_uri;
|
||||
}
|
||||
7
bdj/nginx/conf.d/default.conf
Normal file
7
bdj/nginx/conf.d/default.conf
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# catch-all: drop requests for unknown hosts
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
return 444;
|
||||
}
|
||||
6
bdj/static/favicon.svg
Normal file
6
bdj/static/favicon.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<rect x="2" y="2" width="60" height="60" rx="14" fill="#e4572e" stroke="#2b2118" stroke-width="4"/>
|
||||
<circle cx="23" cy="26" r="4.5" fill="#fff8ec"/>
|
||||
<circle cx="41" cy="26" r="4.5" fill="#fff8ec"/>
|
||||
<path d="M18 38c3 8 10 12 14 12s11-4 14-12z" fill="#fff8ec"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 336 B |
235
bdj/static/main.js
Normal file
235
bdj/static/main.js
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
/* 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 += '<figure class="joke-card"><p class="jod-date">' + iso + '</p>' +
|
||||
'<p class="joke-setup">' + jj.s.replace(/</g, '<') + '</p>' +
|
||||
'<div class="joke-punch" data-punch>' + jj.p.replace(/</g, '<') + '</div></figure>';
|
||||
}
|
||||
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 '<figure class="joke-card"><p class="joke-setup">' + j.s.replace(/</g, '<') + '</p>' +
|
||||
'<div class="joke-punch" data-punch>' + j.p.replace(/</g, '<') + '</div>' +
|
||||
'<figcaption class="joke-actions"><button class="btn-tiny" data-copy="' + j.id + '">📋 Copy</button>' +
|
||||
'<a class="btn-tiny" style="text-decoration:none" href="/category/' + j.cat + '/">Category →</a></figcaption></figure>';
|
||||
}).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;
|
||||
});
|
||||
});
|
||||
})();
|
||||
169
bdj/static/style.css
Normal file
169
bdj/static/style.css
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/* 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; }
|
||||
}
|
||||
Loading…
Reference in a new issue