<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Load Tailwind CSS safely without resetting WordPress theme styles -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
corePlugins: {
preflight: false, // Critical: Prevents Tailwind from breaking your existing WordPress layout
}
}
</script>
<style>
/* Scoped styles to ensure the app looks perfect inside WordPress */
.laundry-app-wrapper {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.5;
}
.laundry-app-wrapper * {
box-sizing: border-box;
}
.laundry-app-wrapper h1, .laundry-app-wrapper h3, .laundry-app-wrapper p {
margin: 0;
padding: 0;
}
.laundry-app-wrapper input {
font-family: inherit;
}
.laundry-app-wrapper input:focus {
outline: none;
}
.laundry-app-wrapper button {
font-family: inherit;
border: none;
background: none;
cursor: pointer;
}
.laundry-app-wrapper svg {
display: block;
}
</style>
</head>
<body>
<div class="laundry-app-wrapper min-h-screen bg-slate-50 text-slate-900 p-6 rounded-2xl">
<div class="max-w-5xl mx-auto space-y-8">
<!-- Header Section -->
<header class="text-center space-y-4 pt-8 pb-4">
<div class="inline-flex items-center justify-center p-4 bg-blue-100 rounded-full mb-2" id="header-icon">
<!-- SVG inserted via JS -->
</div>
<h1 class="text-4xl md:text-5xl font-extrabold tracking-tight text-slate-800" style="margin-bottom: 1rem;">
Laundry Symbol Decoder
</h1>
<p class="text-lg text-slate-600 max-w-2xl mx-auto">
Stop guessing what those cryptic tags mean. Search or browse to discover exactly how to care for your linens and clothing.
</p>
</header>
<!-- Controls Section -->
<div class="flex flex-col md:flex-row justify-between items-center gap-4 bg-white p-4 rounded-2xl shadow-sm border border-slate-200">
<!-- Tabs -->
<div id="tab-container" class="flex flex-wrap gap-2 w-full md:w-auto"></div>
<!-- Search Bar -->
<div class="relative w-full md:w-72">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none" id="search-icon-container"></div>
<input
id="search-input"
type="text"
class="block w-full pl-10 pr-3 py-2 border border-slate-300 rounded-full leading-5 bg-white placeholder-slate-400 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm transition-shadow"
placeholder="Search meaning (e.g., 'gentle', 'hot')"
/>
</div>
</div>
<!-- Grid Display -->
<div id="grid-container"></div>
</div>
</div>
<script>
// --- SVG Icons Data ---
const Icons = {
Search: `<svg class="h-4 w-4 text-slate-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>`,
Info: `<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>`,
Droplets: `<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 16.3c2.2 0 4-1.83 4-4.05 0-1.16-.57-2.26-1.71-3.19S7 6.3 7 6.3s-2.29 2.76-3.29 3.76S2 11.09 2 12.25c0 2.22 1.8 4.05 4 4.05z"></path><path d="M12.56 6.6A10.97 10.97 0 0 0 14 3.02c.5 2.5 2 4.9 4 6.5s3 3.5 3 5.5a6.98 6.98 0 0 1-11.91 4.97"></path></svg>`,
Triangle: `<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13.73 4a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path></svg>`,
Wind: `<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.7 7.7a2.5 2.5 0 1 1 1.8 4.3H2"></path><path d="M9.6 4.6A2 2 0 1 1 11 8H2"></path><path d="M12.6 19.4A2 2 0 1 0 14 16H2"></path></svg>`,
Navigation: `<svg class="w-4 h-4 rotate-90" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="3 11 22 2 13 21 11 13 3 11"></polygon></svg>`,
Circle: `<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>`,
SearchLarge: `<svg class="w-12 h-12 text-slate-300 mx-auto mb-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>`
};
// --- Dynamic SVG Generators ---
const renderTub = ({ dots = 0, lines = 0, hand = false, crossed = false } = {}) => {
let svg = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="w-12 h-12 text-slate-700"><path d="M4 6l2 11h12l2-11" /><path d="M2 6h20" /><path d="M5 10c2.33-1 4.67 1 7 0s4.67-1 7 0" />`;
if (lines > 0) svg += `<line x1="5" y1="20" x2="19" y2="20" />`;
if (lines > 1) svg += `<line x1="5" y1="22" x2="19" y2="22" />`;
if (dots === 1) svg += `<circle cx="12" cy="14" r="1.5" fill="currentColor" />`;
if (dots === 2) svg += `<circle cx="10" cy="14" r="1.5" fill="currentColor" /><circle cx="14" cy="14" r="1.5" fill="currentColor" />`;
if (dots === 3) svg += `<circle cx="8" cy="14" r="1.5" fill="currentColor" /><circle cx="12" cy="14" r="1.5" fill="currentColor" /><circle cx="16" cy="14" r="1.5" fill="currentColor" />`;
if (dots === 4) svg += `<circle cx="7" cy="14" r="1.5" fill="currentColor" /><circle cx="10.3" cy="14" r="1.5" fill="currentColor" /><circle cx="13.6" cy="14" r="1.5" fill="currentColor" /><circle cx="17" cy="14" r="1.5" fill="currentColor" />`;
if (hand) svg += `<path d="M14 8.5l-1.5 2.5a1.5 1.5 0 01-2 0.5L9 10m5-1.5A2.5 2.5 0 0011 6v3" stroke-width="1" />`;
if (crossed) svg += `<path d="M2 2l20 20M22 2L2 22" stroke-width="2" />`;
return svg + `</svg>`;
};
const renderTriangle = ({ lines = false, crossed = false } = {}) => {
let svg = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="w-12 h-12 text-slate-700"><polygon points="12 3 2 20 22 20" />`;
if (lines) svg += `<line x1="7" y1="20" x2="13" y2="9.8" /><line x1="12" y1="20" x2="18" y2="9.8" />`;
if (crossed) svg += `<path d="M1 1l22 22M23 1L1 23" stroke-width="2" />`;
return svg + `</svg>`;
};
const renderSquare = ({ circle = false, lines = 0, dots = 0, flat = false, hang = false, drip = false, shade = false, crossed = false } = {}) => {
let svg = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="w-12 h-12 text-slate-700"><rect x="4" y="3" width="16" height="16" rx="1" />`;
if (circle) svg += `<circle cx="12" cy="11" r="5.5" />`;
if (dots === 1) svg += `<circle cx="12" cy="11" r="1.2" fill="currentColor" />`;
if (dots === 2) svg += `<circle cx="9.5" cy="11" r="1.2" fill="currentColor" /><circle cx="14.5" cy="11" r="1.2" fill="currentColor" />`;
if (dots === 3) svg += `<circle cx="7.5" cy="11" r="1.2" fill="currentColor" /><circle cx="12" cy="11" r="1.2" fill="currentColor" /><circle cx="16.5" cy="11" r="1.2" fill="currentColor" />`;
if (lines > 0) svg += `<line x1="6" y1="21" x2="18" y2="21" />`;
if (lines > 1) svg += `<line x1="6" y1="23" x2="18" y2="23" />`;
if (flat) svg += `<line x1="8" y1="11" x2="16" y2="11" />`;
if (hang) svg += `<path d="M8 6c2-2 6-2 8 0" />`;
if (drip) svg += `<line x1="9" y1="7" x2="9" y2="15" /><line x1="12" y1="7" x2="12" y2="15" /><line x1="15" y1="7" x2="15" y2="15" />`;
if (shade) svg += `<line x1="4" y1="3" x2="8" y2="7" /><line x1="8" y1="3" x2="12" y2="7" />`;
if (crossed) svg += `<path d="M2 2l20 20M22 2L2 22" stroke-width="2" />`;
return svg + `</svg>`;
};
const renderIron = ({ dots = 0, steamCrossed = false, crossed = false } = {}) => {
let svg = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="w-12 h-12 text-slate-700"><path d="M5 15h13a3 3 0 003-3v-1a4 4 0 00-4-4H9a2 2 0 00-2 2v2l-2 4z" /><path d="M4 15v1h16v-1" />`;
if (dots === 1) svg += `<circle cx="12" cy="11" r="1.2" fill="currentColor" />`;
if (dots === 2) svg += `<circle cx="9.5" cy="11" r="1.2" fill="currentColor" /><circle cx="14.5" cy="11" r="1.2" fill="currentColor" />`;
if (dots === 3) svg += `<circle cx="7.5" cy="11" r="1.2" fill="currentColor" /><circle cx="12" cy="11" r="1.2" fill="currentColor" /><circle cx="16.5" cy="11" r="1.2" fill="currentColor" />`;
if (steamCrossed) svg += `<path d="M8 19l-1 3M12 19v3M16 19l1 3" /><path d="M5 23l14-6" stroke-width="1.5" />`;
if (crossed) svg += `<path d="M2 2l20 20M22 2L2 22" stroke-width="2" />`;
return svg + `</svg>`;
};
const renderCircle = ({ letter = "", crossed = false } = {}) => {
let svg = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="w-12 h-12 text-slate-700"><circle cx="12" cy="12" r="9" />`;
if (letter) svg += `<text x="12" y="16.5" text-anchor="middle" font-size="12" font-weight="bold" stroke-width="0.5" fill="currentColor" class="font-sans">${letter}</text>`;
if (crossed) svg += `<path d="M2 2l20 20M22 2L2 22" stroke-width="2" />`;
return svg + `</svg>`;
};
// --- Data Source ---
const laundryData = [
{ id: 'w1', cat: 'Wash', title: 'Machine Wash Normal', desc: 'Wash with normal cycle. Any temperature or detergent.', icon: renderTub({}) },
{ id: 'w2', cat: 'Wash', title: 'Permanent Press', desc: 'Machine wash on permanent press cycle (cool down/slow spin).', icon: renderTub({lines: 1}) },
{ id: 'w3', cat: 'Wash', title: 'Delicate / Gentle', desc: 'Machine wash on delicate or gentle cycle.', icon: renderTub({lines: 2}) },
{ id: 'w4', cat: 'Wash', title: 'Hand Wash', desc: 'Wash manually by hand. Do not machine wash.', icon: renderTub({hand: true}) },
{ id: 'w5', cat: 'Wash', title: 'Do Not Wash', desc: 'Garment may not be safely washed in water. Usually must be dry cleaned.', icon: renderTub({crossed: true}) },
{ id: 'w6', cat: 'Wash', title: 'Cold Wash', desc: 'Wash in cold water.', icon: renderTub({dots: 1}) },
{ id: 'w7', cat: 'Wash', title: 'Warm Wash', desc: 'Wash in warm water.', icon: renderTub({dots: 2}) },
{ id: 'w8', cat: 'Wash', title: 'Hot Wash', desc: 'Wash in hot water.', icon: renderTub({dots: 3}) },
{ id: 'w9', cat: 'Wash', title: 'Very Hot Wash', desc: 'Wash in very hot water.', icon: renderTub({dots: 4}) },
{ id: 'b1', cat: 'Bleach', title: 'Any Bleach', desc: 'Any commercially available bleach product may be used.', icon: renderTriangle({}) },
{ id: 'b2', cat: 'Bleach', title: 'Non-Chlorine Bleach', desc: 'Use only color-safe, non-chlorine bleach.', icon: renderTriangle({lines: true}) },
{ id: 'b3', cat: 'Bleach', title: 'Do Not Bleach', desc: 'No bleach product may be used. Garment is not colorfast or structurally able to withstand bleach.', icon: renderTriangle({crossed: true}) },
{ id: 'd1', cat: 'Dry', title: 'Tumble Dry Normal', desc: 'Machine dry on normal cycle.', icon: renderSquare({circle: true}) },
{ id: 'd2', cat: 'Dry', title: 'Tumble Dry Perm Press', desc: 'Machine dry on permanent press cycle.', icon: renderSquare({circle: true, lines: 1}) },
{ id: 'd3', cat: 'Dry', title: 'Tumble Dry Delicate', desc: 'Machine dry on delicate or gentle cycle.', icon: renderSquare({circle: true, lines: 2}) },
{ id: 'd4', cat: 'Dry', title: 'Tumble Dry Low', desc: 'Machine dry at low heat setting.', icon: renderSquare({circle: true, dots: 1}) },
{ id: 'd5', cat: 'Dry', title: 'Tumble Dry Medium', desc: 'Machine dry at medium heat setting.', icon: renderSquare({circle: true, dots: 2}) },
{ id: 'd6', cat: 'Dry', title: 'Tumble Dry High', desc: 'Machine dry at high heat setting.', icon: renderSquare({circle: true, dots: 3}) },
{ id: 'd7', cat: 'Dry', title: 'Do Not Tumble Dry', desc: 'Do not put in the dryer. Alternative drying method required.', icon: renderSquare({circle: true, crossed: true}) },
{ id: 'd8', cat: 'Dry', title: 'Line Dry', desc: 'Hang damp garment from line or bar, indoors or outdoors.', icon: renderSquare({hang: true}) },
{ id: 'd9', cat: 'Dry', title: 'Drip Dry', desc: 'Hang dripping wet garment to dry. Do not wring or spin.', icon: renderSquare({drip: true}) },
{ id: 'd10', cat: 'Dry', title: 'Dry Flat', desc: 'Lay garment out horizontally on a flat surface to dry.', icon: renderSquare({flat: true}) },
{ id: 'd11', cat: 'Dry', title: 'Dry in Shade', desc: 'Dry away from direct sunlight.', icon: renderSquare({shade: true}) },
{ id: 'i1', cat: 'Iron', title: 'Iron Any Temp', desc: 'Regular ironing, steaming, or dry, may be performed at any available setting.', icon: renderIron({}) },
{ id: 'i2', cat: 'Iron', title: 'Iron Low', desc: 'Iron at low temperature setting. Often for synthetics like acrylic, nylon.', icon: renderIron({dots: 1}) },
{ id: 'i3', cat: 'Iron', title: 'Iron Medium', desc: 'Iron at medium temperature setting. Often for polyester, silk.', icon: renderIron({dots: 2}) },
{ id: 'i4', cat: 'Iron', title: 'Iron High', desc: 'Iron at high temperature setting. Often for cotton, linen.', icon: renderIron({dots: 3}) },
{ id: 'i5', cat: 'Iron', title: 'Do Not Steam', desc: 'Steam ironing will harm garment. Use dry iron.', icon: renderIron({steamCrossed: true}) },
{ id: 'i6', cat: 'Iron', title: 'Do Not Iron', desc: 'Item may not be smoothed or finished with an iron.', icon: renderIron({crossed: true}) },
{ id: 'dc1', cat: 'Dry Clean', title: 'Dry Clean', desc: 'Garment should be professionally dry cleaned.', icon: renderCircle({}) },
{ id: 'dc2', cat: 'Dry Clean', title: 'Do Not Dry Clean', desc: 'Garment must not be commercially dry cleaned.', icon: renderCircle({crossed: true}) },
{ id: 'dc3', cat: 'Dry Clean', title: 'Dry Clean Any Solvent', desc: 'Can be dry cleaned with any chemical solvent.', icon: renderCircle({letter: 'A'}) },
{ id: 'dc4', cat: 'Dry Clean', title: 'Dry Clean Petroleum Only', desc: 'Dry clean using petroleum-based solvents only.', icon: renderCircle({letter: 'F'}) },
{ id: 'dc5', cat: 'Dry Clean', title: 'Dry Clean Perchloroethylene', desc: 'Dry clean using perchloroethylene or petroleum solvents.', icon: renderCircle({letter: 'P'}) },
];
const tabs = [
{ name: 'All', icon: Icons.Info },
{ name: 'Wash', icon: Icons.Droplets },
{ name: 'Bleach', icon: Icons.Triangle },
{ name: 'Dry', icon: Icons.Wind },
{ name: 'Iron', icon: Icons.Navigation },
{ name: 'Dry Clean', icon: Icons.Circle },
];
// --- State and Rendering ---
let activeTab = 'All';
let searchTerm = '';
// Initialize fixed icons
document.getElementById('header-icon').innerHTML = renderTub({dots: 2});
document.getElementById('search-icon-container').innerHTML = Icons.Search;
function renderTabs() {
const tabContainer = document.getElementById('tab-container');
tabContainer.innerHTML = tabs.map(tab => {
const isActive = activeTab === tab.name;
const activeClass = isActive
? "bg-blue-600 text-white shadow-md border border-blue-600"
: "bg-slate-100 text-slate-600 hover:bg-slate-200 border border-transparent";
return `
<button
onclick="setTab('${tab.name}')"
class="flex items-center gap-2 px-4 py-2 rounded-full text-sm font-medium transition-all ${activeClass}"
>
${tab.icon} ${tab.name}
</button>
`;
}).join('');
}
function renderGrid() {
const gridContainer = document.getElementById('grid-container');
const filteredData = laundryData.filter(item => {
const matchesTab = activeTab === 'All' || item.cat === activeTab;
const matchesSearch = item.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.desc.toLowerCase().includes(searchTerm.toLowerCase());
return matchesTab && matchesSearch;
});
if (filteredData.length > 0) {
gridContainer.innerHTML = `
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
${filteredData.map(item => `
<div class="group flex flex-col bg-white rounded-2xl p-6 shadow-sm border border-slate-200 hover:shadow-md hover:border-blue-300 transition-all duration-200">
<div class="flex items-start gap-4">
<div class="flex-shrink-0 p-3 bg-slate-50 rounded-xl group-hover:scale-110 group-hover:bg-blue-50 transition-all duration-300">
${item.icon}
</div>
<div>
<span class="text-xs font-semibold tracking-wider text-blue-600 uppercase mb-1 block">
${item.cat}
</span>
<h3 class="text-lg font-bold text-slate-900 mb-2 leading-tight" style="margin-bottom: 0.5rem;">
${item.title}
</h3>
<p class="text-sm text-slate-600 leading-relaxed" style="margin-bottom: 0;">
${item.desc}
</p>
</div>
</div>
</div>
`).join('')}
</div>
`;
} else {
gridContainer.innerHTML = `
<div class="text-center py-20 bg-white rounded-2xl border border-dashed border-slate-300">
${Icons.SearchLarge}
<h3 class="text-lg font-medium text-slate-900" style="margin-bottom: 0.25rem;">No symbols found</h3>
<p class="text-slate-500 mt-1" style="margin-bottom: 0;">Try adjusting your search term or category.</p>
</div>
`;
}
}
// --- Event Listeners ---
window.setTab = function(tabName) {
activeTab = tabName;
renderTabs();
renderGrid();
};
document.getElementById('search-input').addEventListener('input', (e) => {
searchTerm = e.target.value;
renderGrid();
});
// Boot the app
renderTabs();
renderGrid();
</script>
</body>
</html>