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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions apps/jokes/all-jokes-compact.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
color: inherit;
}

.filter select {
background: rgba(14, 20, 36, 0.9);
border-color: rgba(148, 163, 209, 0.4);
color: inherit;
}

.home-link {
border-color: rgba(148, 163, 209, 0.45);
background: rgba(148, 163, 209, 0.18);
Expand Down Expand Up @@ -198,6 +204,25 @@
box-shadow: 0 0 0 3px rgba(112, 132, 255, 0.18);
}

.filter select {
width: 100%;
box-sizing: border-box;
border-radius: 14px;
border: 1px solid var(--table-border);
background: rgba(255, 255, 255, 0.92);
color: inherit;
padding: 12px 16px;
font: inherit;
cursor: pointer;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.filter select:focus-visible {
outline: 2px solid rgba(112, 132, 255, 0.5);
outline-offset: 2px;
box-shadow: 0 0 0 3px rgba(112, 132, 255, 0.18);
}

.table-wrapper {
border: 1px solid var(--table-border);
border-radius: 16px;
Expand Down Expand Up @@ -299,6 +324,10 @@ <h1>All Jokes (Compact)</h1>
<form class="filter" data-filter-form>
<label for="compact-joke-filter">Filter jokes</label>
<input id="compact-joke-filter" type="search" name="compact-joke-filter" placeholder="Type to filter…" autocomplete="off" data-filter-input>
<label for="compact-joke-category-select">Filter by category</label>
<select id="compact-joke-category-select" name="compact-joke-category-select" data-category-select aria-label="Joke category" disabled>
<option>Loading categories…</option>
</select>
</form>
<div class="table-wrapper" role="region" aria-live="polite">
<table>
Expand Down
29 changes: 29 additions & 0 deletions apps/jokes/all-jokes.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@
color: inherit;
}

.filter select {
background: rgba(18, 18, 18, 0.9);
border-color: rgba(241, 245, 255, 0.35);
color: inherit;
}

.category-pill {
background: rgba(148, 163, 209, 0.24);
color: rgba(241, 245, 255, 0.9);
Expand Down Expand Up @@ -164,6 +170,25 @@
box-shadow: 0 0 0 3px rgba(48, 86, 211, 0.18);
}

.filter select {
width: 100%;
box-sizing: border-box;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.2);
background: rgba(255, 255, 255, 0.95);
color: inherit;
padding: 10px 14px;
font: inherit;
cursor: pointer;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.filter select:focus-visible {
outline: 2px solid rgba(48, 86, 211, 0.55);
outline-offset: 2px;
box-shadow: 0 0 0 3px rgba(48, 86, 211, 0.18);
}

table {
width: 100%;
border-collapse: collapse;
Expand Down Expand Up @@ -210,6 +235,10 @@ <h1>All Jokes</h1>
<form class="filter" data-filter-form>
<label for="joke-filter">Filter jokes</label>
<input id="joke-filter" type="search" name="joke-filter" placeholder="Type to filter…" autocomplete="off" data-filter-input>
<label for="joke-category-select">Filter by category</label>
<select id="joke-category-select" name="joke-category-select" data-category-select aria-label="Joke category" disabled>
<option>Loading categories…</option>
</select>
</form>
<table>
<caption>Setup followed by punchline</caption>
Expand Down
103 changes: 97 additions & 6 deletions apps/jokes/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,30 @@
const prevButton = document.getElementById('prev-button');
const nextButton = document.getElementById('next-button');
const revealButton = document.getElementById('reveal-button');
const categorySelect = document.getElementById('category-select');
const categoryList = document.getElementById('joke-categories');

if (!setupEl || !punchlineEl || !prevButton || !nextButton || !revealButton) {
if (!setupEl || !punchlineEl || !prevButton || !nextButton || !revealButton || !categorySelect) {
return;
}

const jokes = Array.isArray(window.jokes)
? window.jokes.filter((entry) => entry && entry.joke)
? window.jokes
.map((entry) => {
if (!entry || typeof entry.joke !== 'string') {
return null;
}
const joke = entry.joke;
const punchline = typeof entry.punchline === 'string' ? entry.punchline : '';
return { joke, punchline };
})
.filter(Boolean)
: [];

let deck = [];
let index = 0;
let punchlineVisible = false;
let activeCategory = 'any';

const categorize =
window.jokeCategoryHelper && typeof window.jokeCategoryHelper.categorize === 'function'
Expand All @@ -28,6 +39,57 @@
? window.jokeCategoryHelper.fallback
: 'Classic Dad';

const normalizedJokes = jokes.map((entry) => {
const categories = categorize
? categorize(entry.joke, entry.punchline)
: null;
const list = Array.isArray(categories)
? categories
.map((label) => (typeof label === 'string' ? label.trim() : ''))
.filter((label, idx, array) => label && array.indexOf(label) === idx)
: [];
return {
joke: entry.joke,
punchline: entry.punchline,
categories: list.length ? list : [fallbackCategory],
};
});

const categorySet = new Set();
normalizedJokes.forEach((entry) => {
entry.categories.forEach((label) => {
categorySet.add(label);
});
});

const categoryOptions = Array.from(categorySet).sort((a, b) => a.localeCompare(b));

const optionFragment = document.createDocumentFragment();
const allOption = document.createElement('option');
allOption.value = 'any';
allOption.textContent = 'All jokes';
optionFragment.appendChild(allOption);

categoryOptions.forEach((label) => {
const option = document.createElement('option');
option.value = label;
option.textContent = label;
optionFragment.appendChild(option);
});

categorySelect.appendChild(optionFragment);
categorySelect.value = 'any';
if (categoryOptions.length) {
categorySelect.disabled = false;
}

function getActiveCollection() {
if (activeCategory === 'any') {
return normalizedJokes;
}
return normalizedJokes.filter((entry) => entry.categories.includes(activeCategory));
}

function renderCategories(values) {
if (!categoryList) {
return;
Expand Down Expand Up @@ -58,7 +120,8 @@

function ensureDeck() {
if (deck.length === 0) {
deck = shuffle(jokes);
const available = getActiveCollection();
deck = shuffle(available);
index = 0;
}
}
Expand All @@ -71,7 +134,9 @@
}

function render() {
if (!jokes.length) {
const available = getActiveCollection();

if (!available.length) {
setupEl.textContent = 'No jokes available.';
punchlineEl.textContent = '';
punchlineEl.classList.remove('is-visible');
Expand All @@ -90,10 +155,13 @@

ensureDeck();
const current = deck[index];
if (!current) {
return;
}
const hasPunchline = typeof current.punchline === 'string' && current.punchline.trim().length > 0;
const punchlineText = hasPunchline ? current.punchline : '💩';
const categories = categorize
? categorize(current.joke, current.punchline)
const categories = Array.isArray(current.categories) && current.categories.length
? current.categories
: [fallbackCategory];

setupEl.textContent = current.joke;
Expand All @@ -110,12 +178,18 @@

function showNext() {
ensureDeck();
if (!deck.length) {
return;
}
index = (index + 1) % deck.length;
render();
}

function showPrev() {
ensureDeck();
if (!deck.length) {
return;
}
index = (index - 1 + deck.length) % deck.length;
render();
}
Expand All @@ -127,6 +201,17 @@
setPunchlineVisible(!punchlineVisible);
}

function resetDeck() {
deck = [];
ensureDeck();
}

categorySelect.addEventListener('change', (event) => {
activeCategory = event.target.value;
resetDeck();
render();
});

prevButton.addEventListener('click', showPrev);
nextButton.addEventListener('click', showNext);
revealButton.addEventListener('click', togglePunchline);
Expand All @@ -136,6 +221,12 @@
return;
}

const target = event.target;
const tagName = target && target.tagName;
if (tagName && ['INPUT', 'TEXTAREA', 'SELECT'].includes(tagName)) {
return;
}

if (event.key === 'ArrowRight') {
event.preventDefault();
showNext();
Expand Down
56 changes: 56 additions & 0 deletions apps/jokes/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@
width: 100%;
}

.controls__category {
display: flex;
justify-content: center;
width: 100%;
}

button {
border: none;
border-radius: 999px;
Expand Down Expand Up @@ -223,6 +229,42 @@
outline-offset: 3px;
}

.category-picker {
position: relative;
display: inline-flex;
align-items: center;
}

.category-picker select {
border-radius: 999px;
border: 1px solid rgba(15, 23, 42, 0.18);
padding: 10px 18px;
font-size: 1rem;
background: rgba(255, 255, 255, 0.92);
color: inherit;
cursor: pointer;
transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
min-width: 200px;
}

.category-picker select:focus-visible {
outline: 2px solid rgba(112, 132, 255, 0.6);
outline-offset: 3px;
box-shadow: 0 0 0 3px rgba(112, 132, 255, 0.22);
}

.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

@media (hover: none) and (pointer: coarse) {
button:not(:disabled) {
transition: none;
Expand All @@ -235,6 +277,14 @@
box-shadow: none;
}
}

@media (prefers-color-scheme: dark) {
.category-picker select {
background: rgba(14, 20, 36, 0.9);
border-color: rgba(148, 163, 209, 0.35);
color: inherit;
}
}
</style>
</head>
<body>
Expand All @@ -260,6 +310,12 @@
<div class="controls__punchline">
<button id="reveal-button" class="secondary" type="button" aria-pressed="false">Punchline</button>
</div>
<div class="controls__category">
<label class="category-picker" for="category-select">
<span class="visually-hidden">Select joke category</span>
<select id="category-select" aria-label="Joke category" disabled></select>
</label>
</div>
</div>
</main>
<script src="jokes.js" defer></script>
Expand Down
Loading
Loading