{"id":18133,"date":"2026-09-16T13:03:35","date_gmt":"2026-09-16T13:03:35","guid":{"rendered":"https:\/\/blackbeltwhitehat.com\/?page_id=18133"},"modified":"2026-09-17T11:36:08","modified_gmt":"2026-09-17T11:36:08","slug":"exercise-directory","status":"publish","type":"page","link":"https:\/\/blackbeltwhitehat.com\/en\/exercise-directory\/","title":{"rendered":"Gym Exercise Directory"},"content":{"rendered":"\n<!-- Exercise Directory \u2014 paste this whole thing into a Gutenberg Custom HTML block -->\n<!-- Data source: Free Exercise DB, public domain (Unlicense). ~800 exercises fetched at page load. -->\n\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin=\"\">\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;500;600;700&amp;display=swap\" rel=\"stylesheet\">\n\n<style>\n\/* Let the exercise + technique directories fill more of your content area *\/\n.exdir .wrap,\n.tdir .wrap {\n    max-width: none;        \/* was 1240px *\/\n    padding-left: 24px;\n    padding-right: 24px;\n}\n\n  .exdir {\n    --bg: #FFFFFF;\n    --panel: #F8F6F1;\n    --card: #FFFFFF;\n    --ink: #0F0F0F;\n    --ink-2: #3A3A3A;\n    --meta: #767676;\n    --rule: #E4E1D8;\n    --rule-strong: #B8B3A6;\n    --accent: #B33A2F;\n    --accent-soft: rgba(179, 58, 47, 0.08);\n    --tag-bg: #F0EBE1;\n    --tag-ink: #5A4E3C;\n    --focus: #B33A2F;\n    --shadow: 0 1px 0 rgba(15,15,15,0.04);\n  }\n  @media (prefers-color-scheme: dark) {\n    .exdir:not([data-theme=\"light\"]) {\n      --bg: #14130F;\n      --panel: #1C1B17;\n      --card: #1C1B17;\n      --ink: #EDEAE1;\n      --ink-2: #C4C0B4;\n      --meta: #8A8578;\n      --rule: #2E2C26;\n      --rule-strong: #423F37;\n      --accent: #E86155;\n      --accent-soft: rgba(232, 97, 85, 0.14);\n      --tag-bg: #2A2822;\n      --tag-ink: #C4B99E;\n      --focus: #E86155;\n      --shadow: 0 1px 0 rgba(0,0,0,0.4);\n    }\n  }\n  .exdir[data-theme=\"dark\"] {\n    --bg: #14130F;\n    --panel: #1C1B17;\n    --card: #1C1B17;\n    --ink: #EDEAE1;\n    --ink-2: #C4C0B4;\n    --meta: #8A8578;\n    --rule: #2E2C26;\n    --rule-strong: #423F37;\n    --accent: #E86155;\n    --accent-soft: rgba(232, 97, 85, 0.14);\n    --tag-bg: #2A2822;\n    --tag-ink: #C4B99E;\n    --focus: #E86155;\n    --shadow: 0 1px 0 rgba(0,0,0,0.4);\n  }\n\n  .exdir, .exdir *, .exdir *::before, .exdir *::after { box-sizing: border-box; }\n  \n  .exdir {\n    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n    font-size: 15px;\n    line-height: 1.5;\n    color: var(--ink);\n    background: var(--bg);\n    -webkit-font-smoothing: antialiased;\n    text-rendering: optimizeLegibility;\n  }\n  .exdir img { max-width: 100%; height: auto; display: block; }\n  .exdir button { font: inherit; color: inherit; cursor: pointer; background: none; border: none; padding: 0; }\n\n  .exdir :focus-visible {\n    outline: 2px solid var(--focus);\n    outline-offset: 2px;\n  }\n\n  .exdir .wrap {\n    max-width: 1240px;\n    margin: 0 auto;\n    padding: 32px 24px 80px;\n  }\n  @media (max-width: 640px) {\n    .wrap { padding: 20px 16px 60px; }\n  }\n\n  \/* Header *\/\n  .exdir header.top {\n    display: flex;\n    align-items: flex-end;\n    justify-content: space-between;\n    gap: 24px;\n    padding-bottom: 20px;\n    border-bottom: 1px solid var(--rule-strong);\n    margin-bottom: 24px;\n  }\n  .exdir header.top h1 {\n    font-size: 32px;\n    font-weight: 700;\n    letter-spacing: -0.02em;\n    margin: 0 0 4px;\n    line-height: 1.1;\n  }\n  .exdir header.top .subtitle {\n    font-size: 15px;\n    color: var(--meta);\n    margin: 0;\n    max-width: 44ch;\n  }\n  .exdir header.top .count {\n    font-size: 13px;\n    color: var(--meta);\n    font-variant-numeric: tabular-nums;\n    white-space: nowrap;\n    padding-bottom: 4px;\n  }\n  .exdir header.top .count strong {\n    font-weight: 600;\n    color: var(--ink);\n  }\n  @media (max-width: 640px) {\n    header.top { flex-direction: column; align-items: flex-start; gap: 8px; }\n    header.top h1 { font-size: 26px; }\n  }\n\n  \/* Search + filters *\/\n  .exdir .controls {\n    display: grid;\n    grid-template-columns: 1fr;\n    gap: 20px;\n    margin-bottom: 32px;\n  }\n  .exdir .search-row {\n    position: relative;\n  }\n  .exdir .search-row input {\n    width: 100%;\n    padding: 12px 14px 12px 40px;\n    font-size: 15px;\n    font-family: inherit;\n    color: var(--ink);\n    background: var(--card);\n    border: 1px solid var(--rule);\n    border-radius: 6px;\n    transition: border-color 0.15s, box-shadow 0.15s;\n  }\n  .exdir .search-row input:focus {\n    outline: none;\n    border-color: var(--accent);\n    box-shadow: 0 0 0 3px var(--accent-soft);\n  }\n  .search-row input::placeholder { color: var(--meta); }\n  .exdir .search-row .search-icon {\n    position: absolute;\n    left: 14px;\n    top: 50%;\n    transform: translateY(-50%);\n    color: var(--meta);\n    pointer-events: none;\n    width: 16px;\n    height: 16px;\n  }\n\n  .exdir .filter-groups {\n    display: grid;\n    grid-template-columns: 1fr;\n    gap: 16px;\n  }\n  .exdir .filter-group {\n    display: grid;\n    grid-template-columns: 90px 1fr;\n    gap: 12px;\n    align-items: baseline;\n  }\n  .exdir .filter-group h3 {\n    font-size: 13px;\n    font-weight: 600;\n    color: var(--meta);\n    margin: 0;\n    padding-top: 6px;\n  }\n  .exdir .chips {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 6px;\n  }\n  .exdir .chip {\n    background: transparent;\n    border: 1px solid var(--rule);\n    color: var(--ink-2);\n    padding: 5px 11px;\n    border-radius: 4px;\n    font-size: 13px;\n    font-weight: 500;\n    line-height: 1.4;\n    transition: all 0.12s;\n    text-transform: capitalize;\n  }\n  .chip:hover { border-color: var(--rule-strong); color: var(--ink); }\n  .exdir .chip.active {\n    background: var(--ink);\n    color: var(--bg);\n    border-color: var(--ink);\n  }\n  .exdir[data-theme=\"dark\"] .chip.active,\n  .exdir:not([data-theme=\"light\"]) .chip.active {\n    background: var(--ink);\n    color: var(--bg);\n  }\n  @media (max-width: 640px) {\n    .filter-group { grid-template-columns: 1fr; gap: 4px; }\n    .filter-group h3 { padding-top: 0; }\n  }\n\n  .exdir .toolbar {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    gap: 12px;\n    margin-bottom: 20px;\n    padding-top: 16px;\n    border-top: 1px solid var(--rule);\n    font-size: 13px;\n    color: var(--meta);\n  }\n  .exdir .clear-btn {\n    background: transparent;\n    border: none;\n    padding: 4px 8px;\n    margin: -4px -8px;\n    border-radius: 4px;\n    color: var(--accent);\n    font-weight: 500;\n    font-size: 13px;\n  }\n  .clear-btn:hover { background: var(--accent-soft); }\n  .clear-btn[hidden] { display: none; }\n\n  \/* Grid *\/\n  .exdir .grid {\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n    gap: 16px;\n  }\n\n  .exdir .card {\n    background: var(--card);\n    border: 1px solid var(--rule);\n    border-radius: 6px;\n    overflow: hidden;\n    cursor: pointer;\n    transition: border-color 0.15s, transform 0.15s;\n    text-align: left;\n    padding: 0;\n    display: flex;\n    flex-direction: column;\n    font: inherit;\n    color: inherit;\n    width: 100%;\n  }\n  .exdir .card:hover {\n    border-color: var(--rule-strong);\n  }\n  .card:hover .card-name { color: var(--accent); }\n  .exdir .card:focus-visible {\n    outline-offset: -2px;\n  }\n\n  .exdir .card-img {\n    position: relative;\n    aspect-ratio: 4 \/ 3;\n    background: var(--panel);\n    overflow: hidden;\n    border-bottom: 1px solid var(--rule);\n  }\n  .exdir .card-img img {\n    position: absolute;\n    inset: 0;\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n  }\n  .exdir .card-img .frame-b {\n    animation: crossfade 1.8s ease-in-out infinite;\n  }\n  @keyframes crossfade {\n    0%, 30%   { opacity: 0; }\n    50%, 80%  { opacity: 1; }\n    100%      { opacity: 0; }\n  }\n  @media (prefers-reduced-motion: reduce) {\n    .card-img .frame-b { animation: none; opacity: 0.5; }\n  }\n\n  .exdir .card-body {\n    padding: 12px 14px 14px;\n    display: flex;\n    flex-direction: column;\n    gap: 6px;\n  }\n  .exdir .card-name {\n    font-size: 15px;\n    font-weight: 600;\n    color: var(--ink);\n    line-height: 1.3;\n    letter-spacing: -0.005em;\n    transition: color 0.15s;\n  }\n  .exdir .card-tags {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 4px;\n    align-items: center;\n  }\n  .exdir .tag {\n    background: var(--tag-bg);\n    color: var(--tag-ink);\n    font-size: 11.5px;\n    font-weight: 500;\n    padding: 2px 7px;\n    border-radius: 3px;\n    line-height: 1.5;\n    text-transform: capitalize;\n  }\n  .exdir .tag.muscle {\n    background: transparent;\n    color: var(--accent);\n    padding: 2px 0;\n    font-weight: 600;\n    font-size: 11.5px;\n  }\n\n  \/* Empty state *\/\n  .exdir .empty {\n    text-align: center;\n    padding: 60px 20px;\n    color: var(--meta);\n  }\n  .empty p { margin: 0 0 12px; font-size: 15px; }\n  .exdir .empty button {\n    background: var(--ink);\n    color: var(--bg);\n    border: none;\n    padding: 8px 16px;\n    border-radius: 4px;\n    font-size: 14px;\n    font-weight: 500;\n  }\n\n  \/* Footer *\/\n  .exdir footer {\n    margin-top: 60px;\n    padding-top: 20px;\n    border-top: 1px solid var(--rule);\n    font-size: 12.5px;\n    color: var(--meta);\n    line-height: 1.6;\n  }\n  footer a { color: var(--ink-2); }\n\n  \/* Modal *\/\n  .exdir .modal {\n    position: fixed;\n    inset: 0;\n    background: rgba(15, 15, 15, 0.5);\n    display: flex;\n    align-items: flex-start;\n    justify-content: center;\n    padding: 40px 20px;\n    z-index: 100;\n    opacity: 0;\n    pointer-events: none;\n    transition: opacity 0.15s;\n    overflow-y: auto;\n  }\n  .exdir .modal.open {\n    opacity: 1;\n    pointer-events: auto;\n  }\n  .exdir .modal-panel {\n    background: var(--card);\n    border-radius: 8px;\n    max-width: 720px;\n    width: 100%;\n    max-height: none;\n    display: flex;\n    flex-direction: column;\n    border: 1px solid var(--rule-strong);\n    box-shadow: 0 20px 60px rgba(0,0,0,0.15);\n  }\n  @media (max-width: 640px) {\n    .modal { padding: 12px; }\n  }\n  .exdir .modal-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: flex-start;\n    padding: 20px 24px 16px;\n    border-bottom: 1px solid var(--rule);\n    gap: 16px;\n  }\n  .exdir .modal-header h2 {\n    font-size: 22px;\n    font-weight: 700;\n    letter-spacing: -0.01em;\n    margin: 0;\n    line-height: 1.2;\n  }\n  .exdir .modal-close {\n    background: transparent;\n    border: 1px solid var(--rule);\n    width: 32px;\n    height: 32px;\n    border-radius: 4px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    color: var(--ink-2);\n    flex-shrink: 0;\n  }\n  .modal-close:hover { border-color: var(--rule-strong); color: var(--ink); }\n  .exdir .modal-body {\n    padding: 20px 24px 24px;\n    overflow-y: auto;\n  }\n  .exdir .modal-media {\n    aspect-ratio: 16 \/ 10;\n    background: var(--panel);\n    border-radius: 6px;\n    position: relative;\n    overflow: hidden;\n    margin-bottom: 20px;\n    border: 1px solid var(--rule);\n  }\n  .exdir .modal-media img {\n    position: absolute;\n    inset: 0;\n    width: 100%;\n    height: 100%;\n    object-fit: contain;\n  }\n  .exdir .modal-media .frame-b {\n    animation: crossfade 1.8s ease-in-out infinite;\n  }\n  .exdir .modal-meta {\n    display: grid;\n    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));\n    gap: 16px;\n    padding: 16px 0;\n    border-top: 1px solid var(--rule);\n    border-bottom: 1px solid var(--rule);\n    margin-bottom: 20px;\n  }\n  .exdir .modal-meta dt {\n    font-size: 12px;\n    color: var(--meta);\n    margin: 0 0 3px;\n    font-weight: 500;\n  }\n  .exdir .modal-meta dd {\n    font-size: 14px;\n    color: var(--ink);\n    margin: 0;\n    font-weight: 500;\n    text-transform: capitalize;\n  }\n  .exdir .modal-body h3 {\n    font-size: 14px;\n    font-weight: 600;\n    margin: 20px 0 10px;\n    color: var(--ink);\n  }\n  .modal-body h3:first-of-type { margin-top: 0; }\n  .exdir .modal-body ol {\n    padding-left: 22px;\n    margin: 0;\n    color: var(--ink-2);\n  }\n  .exdir .modal-body ol li {\n    margin-bottom: 10px;\n    line-height: 1.55;\n  }\n  .exdir .modal-body ol li::marker {\n    color: var(--accent);\n    font-weight: 600;\n  }\n  .exdir .muscle-list {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 6px;\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n  .exdir .muscle-list li {\n    background: var(--tag-bg);\n    color: var(--tag-ink);\n    padding: 3px 9px;\n    border-radius: 3px;\n    font-size: 13px;\n    text-transform: capitalize;\n    font-weight: 500;\n  }\n  .exdir .muscle-list li.primary {\n    background: var(--accent);\n    color: white;\n  }\n\n<\/style>\n\n<div class=\"exdir\">\n<div class=\"wrap\">\n  <header class=\"top\">\n    <div>\n      <h1>Exercise Directory<\/h1>\n      <p class=\"subtitle\">Filter by muscle group, equipment, or category. Tap a card for coaching notes and full instructions.<\/p>\n    <\/div>\n    <div class=\"count\"><strong id=\"exdir-visible-count\">0<\/strong> of <span id=\"exdir-total-count\">0<\/span> exercises<\/div>\n  <\/header>\n\n  <div class=\"controls\">\n    <div class=\"search-row\">\n      <svg class=\"search-icon\" 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><path d=\"m21 21-4.3-4.3\"><\/path><\/svg>\n      <input type=\"search\" id=\"exdir-search\" placeholder=\"Search \u2014 e.g. squat, dumbbell, hamstring\" autocomplete=\"off\">\n    <\/div>\n    <div class=\"filter-groups\" id=\"exdir-filter-groups\"><\/div>\n  <\/div>\n\n  <div class=\"toolbar\">\n    <div id=\"exdir-active-summary\">Showing all exercises<\/div>\n    <button class=\"clear-btn\" id=\"exdir-clear-btn\" hidden=\"\">Clear filters<\/button>\n  <\/div>\n\n  <main class=\"grid\" id=\"exdir-grid\" role=\"list\"><\/main>\n\n  <div class=\"empty\" id=\"exdir-empty\" hidden=\"\">\n    <p>No exercises match these filters.<\/p>\n    <button id=\"exdir-empty-clear\">Clear filters<\/button>\n  <\/div>\n\n  <footer>\n    <p>Data: <a href=\"https:\/\/github.com\/yuhonas\/free-exercise-db\" target=\"_blank\" rel=\"noopener\">Free Exercise DB<\/a> \u2014 public domain (Unlicense), 800+ exercises. Frames alternate between the start and peak of each rep.<\/p>\n  <\/footer>\n<\/div>\n\n<div class=\"modal\" id=\"exdir-modal\" hidden=\"\" aria-hidden=\"true\" role=\"dialog\" aria-labelledby=\"exdir-modal-title\" aria-modal=\"true\">\n  <div class=\"modal-panel\">\n    <div class=\"modal-header\">\n      <h2 id=\"exdir-modal-title\"><\/h2>\n      <button class=\"modal-close\" id=\"exdir-modal-close\" aria-label=\"Close\">\n        <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"><\/path><path d=\"m6 6 12 12\"><\/path><\/svg>\n      <\/button>\n    <\/div>\n    <div class=\"modal-body\" id=\"exdir-modal-body\"><\/div>\n  <\/div>\n<\/div>\n<\/div>\n\n<script>\n(function() {\n\n\/\/ Source: Free Exercise DB (public domain \/ Unlicense)\n\/\/ Fetches full 800+ exercise dataset from GitHub raw on page load.\nconst DATA_URL = 'https:\/\/raw.githubusercontent.com\/yuhonas\/free-exercise-db\/main\/dist\/exercises.json';\nconst IMAGE_BASE = 'https:\/\/raw.githubusercontent.com\/yuhonas\/free-exercise-db\/main\/exercises\/';\n\nlet EXERCISES = [];\nconst IMAGES = new Proxy({}, {\n  get(_, path) {\n    return typeof path === 'string' ? IMAGE_BASE + path : undefined;\n  }\n});\n\nconst FILTERS = [\n  { key: 'primary',   label: 'Muscle' },\n  { key: 'equipment', label: 'Kit' },\n  { key: 'category',  label: 'Type' },\n  { key: 'level',     label: 'Level' }\n];\n\nconst state = {\n  search: '',\n  active: { primary: null, equipment: null, category: null, level: null }\n};\n\n\/\/ -----------------------------------------------------------\n\/\/ Init\n\/\/ -----------------------------------------------------------\nconst grid = document.getElementById('exdir-grid');\nconst empty = document.getElementById('exdir-empty');\nconst searchInput = document.getElementById('exdir-search');\nconst filterGroups = document.getElementById('exdir-filter-groups');\nconst visibleCountEl = document.getElementById('exdir-visible-count');\nconst totalCountEl = document.getElementById('exdir-total-count');\nconst activeSummary = document.getElementById('exdir-active-summary');\nconst clearBtn = document.getElementById('exdir-clear-btn');\nconst emptyClear = document.getElementById('exdir-empty-clear');\nconst modal = document.getElementById('exdir-modal');\nconst modalTitle = document.getElementById('exdir-modal-title');\nconst modalBody = document.getElementById('exdir-modal-body');\nconst modalClose = document.getElementById('exdir-modal-close');\n\n\/\/ Compute options for each filter\nfunction optionsFor(key) {\n  const set = new Set();\n  for (const ex of EXERCISES) {\n    if (key === 'primary') {\n      (ex.primaryMuscles || []).forEach(m => set.add(m));\n    } else if (ex[key]) {\n      set.add(ex[key]);\n    }\n  }\n  return [...set].sort();\n}\n\n\/\/ Render filter chips\nfunction renderFilters() {\n  filterGroups.innerHTML = '';\n  for (const f of FILTERS) {\n    const opts = optionsFor(f.key);\n    if (!opts.length) continue;\n    const wrap = document.createElement('div');\n    wrap.className = 'filter-group';\n    wrap.dataset.key = f.key;\n    wrap.innerHTML = `<h3>${f.label}<\/h3><div class=\"chips\"><\/div>`;\n    const chipsContainer = wrap.querySelector('.chips');\n    for (const opt of opts) {\n      const btn = document.createElement('button');\n      btn.className = 'chip';\n      btn.type = 'button';\n      btn.textContent = opt;\n      btn.dataset.key = f.key;\n      btn.dataset.value = opt;\n      btn.addEventListener('click', () => {\n        state.active[f.key] = state.active[f.key] === opt ? null : opt;\n        update();\n      });\n      chipsContainer.appendChild(btn);\n    }\n    filterGroups.appendChild(wrap);\n  }\n}\n\n\/\/ Filter logic\nfunction matches(ex) {\n  if (state.search) {\n    const q = state.search.toLowerCase();\n    const hay = (ex.name + ' ' + (ex.primaryMuscles || []).join(' ') + ' ' + (ex.equipment || '')).toLowerCase();\n    if (!hay.includes(q)) return false;\n  }\n  for (const key of Object.keys(state.active)) {\n    const val = state.active[key];\n    if (!val) continue;\n    if (key === 'primary') {\n      if (!(ex.primaryMuscles || []).includes(val)) return false;\n    } else {\n      if (ex[key] !== val) return false;\n    }\n  }\n  return true;\n}\n\n\/\/ Render grid\nfunction renderGrid() {\n  const filtered = EXERCISES.filter(matches);\n  visibleCountEl.textContent = filtered.length;\n\n  if (filtered.length === 0) {\n    grid.hidden = true;\n    empty.hidden = false;\n    return;\n  }\n  grid.hidden = false;\n  empty.hidden = true;\n\n  const frag = document.createDocumentFragment();\n  for (const ex of filtered) {\n    const card = document.createElement('button');\n    card.className = 'card';\n    card.type = 'button';\n    card.setAttribute('role', 'listitem');\n    card.addEventListener('click', () => openModal(ex));\n\n    const imgs = (ex.images || []).map(p => IMAGES[p]).filter(Boolean);\n    const [imgA, imgB] = imgs.length >= 2 ? imgs : [imgs[0], imgs[0]];\n\n    const primary = (ex.primaryMuscles || [])[0] || '';\n    const equip = ex.equipment && ex.equipment !== 'body only' ? ex.equipment : (ex.equipment === 'body only' ? 'bodyweight' : '');\n\n    card.innerHTML = `\n      <div class=\"card-img\">\n        ${imgA ? `<img decoding=\"async\" loading=\"lazy\" src=\"${imgA}\" alt=\"\">` : ''}\n        ${imgB ? `<img decoding=\"async\" loading=\"lazy\" class=\"frame-b\" src=\"${imgB}\" alt=\"\">` : ''}\n      <\/div>\n      <div class=\"card-body\">\n        <div class=\"card-name\">${escapeHtml(ex.name)}<\/div>\n        <div class=\"card-tags\">\n          ${primary ? `<span class=\"tag muscle\">${escapeHtml(primary)}<\/span>` : ''}\n          ${equip ? `<span class=\"tag\">${escapeHtml(equip)}<\/span>` : ''}\n        <\/div>\n      <\/div>\n    `;\n    frag.appendChild(card);\n  }\n  grid.innerHTML = '';\n  grid.appendChild(frag);\n}\n\nfunction renderChipStates() {\n  document.querySelectorAll('.chip').forEach(chip => {\n    const key = chip.dataset.key;\n    const val = chip.dataset.value;\n    chip.classList.toggle('active', state.active[key] === val);\n  });\n}\n\nfunction renderSummary() {\n  const parts = [];\n  for (const f of FILTERS) {\n    if (state.active[f.key]) parts.push(state.active[f.key]);\n  }\n  if (state.search) parts.push('\"' + state.search + '\"');\n  const anyActive = parts.length > 0;\n  activeSummary.textContent = anyActive\n    ? 'Filtered: ' + parts.join(' \u00b7 ')\n    : 'Showing all exercises';\n  clearBtn.hidden = !anyActive;\n}\n\nfunction update() {\n  renderChipStates();\n  renderSummary();\n  renderGrid();\n}\n\nfunction clearAll() {\n  state.search = '';\n  searchInput.value = '';\n  for (const key of Object.keys(state.active)) state.active[key] = null;\n  update();\n}\n\n\/\/ Modal\nfunction openModal(ex) {\n  modalTitle.textContent = ex.name;\n  const imgs = (ex.images || []).map(p => IMAGES[p]).filter(Boolean);\n  const [imgA, imgB] = imgs.length >= 2 ? imgs : [imgs[0], imgs[0]];\n\n  const primaryMuscles = (ex.primaryMuscles || []).map(m => `<li class=\"primary\">${escapeHtml(m)}<\/li>`).join('');\n  const secondaryMuscles = (ex.secondaryMuscles || []).map(m => `<li>${escapeHtml(m)}<\/li>`).join('');\n  const allMuscles = primaryMuscles + secondaryMuscles;\n\n  const instructions = (ex.instructions || []).map(i => `<li>${escapeHtml(i)}<\/li>`).join('');\n\n  const meta = [\n    { l: 'Equipment', v: ex.equipment || '\u2014' },\n    { l: 'Category', v: ex.category || '\u2014' },\n    { l: 'Level', v: ex.level || '\u2014' },\n    { l: 'Force', v: ex.force || '\u2014' },\n    { l: 'Mechanic', v: ex.mechanic || '\u2014' }\n  ].map(m => `<div><dt>${m.l}<\/dt><dd>${escapeHtml(String(m.v))}<\/dd><\/div>`).join('');\n\n  modalBody.innerHTML = `\n    <div class=\"modal-media\">\n      ${imgA ? `<img decoding=\"async\" src=\"${imgA}\" alt=\"\">` : ''}\n      ${imgB ? `<img decoding=\"async\" class=\"frame-b\" src=\"${imgB}\" alt=\"\">` : ''}\n    <\/div>\n    <dl class=\"modal-meta\">${meta}<\/dl>\n    ${allMuscles ? `<h3>Muscles worked<\/h3><ul class=\"muscle-list\">${allMuscles}<\/ul>` : ''}\n    ${instructions ? `<h3>Instructions<\/h3><ol>${instructions}<\/ol>` : ''}\n  `;\n\n  modal.hidden = false;\n  requestAnimationFrame(() => modal.classList.add('open'));\n  modal.setAttribute('aria-hidden', 'false');\n  modalClose.focus();\n  document.body.style.overflow = 'hidden';\n}\n\nfunction closeModal() {\n  modal.classList.remove('open');\n  modal.setAttribute('aria-hidden', 'true');\n  document.body.style.overflow = '';\n  setTimeout(() => { modal.hidden = true; }, 150);\n}\n\n\/\/ Utils\nfunction escapeHtml(s) {\n  return String(s).replace(\/[&<>\"']\/g, c => ({\n    '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;'\n  })[c]);\n}\n\n\/\/ Event handlers\nsearchInput.addEventListener('input', e => {\n  state.search = e.target.value.trim();\n  update();\n});\nclearBtn.addEventListener('click', clearAll);\nemptyClear.addEventListener('click', clearAll);\nmodalClose.addEventListener('click', closeModal);\nmodal.addEventListener('click', e => { if (e.target === modal) closeModal(); });\ndocument.addEventListener('keydown', e => {\n  if (e.key === 'Escape' && !modal.hidden) closeModal();\n});\n\n\/\/ Init: fetch dataset then render\nasync function init() {\n  activeSummary.textContent = 'Loading exercises\u2026';\n  try {\n    const res = await fetch(DATA_URL);\n    if (!res.ok) throw new Error('HTTP ' + res.status);\n    EXERCISES = await res.json();\n  } catch (err) {\n    activeSummary.textContent = 'Could not load exercises. Check your connection and reload.';\n    console.error(err);\n    return;\n  }\n  totalCountEl.textContent = EXERCISES.length;\n  renderFilters();\n  update();\n}\ninit();\n\n})();\n<\/script>\n","protected":false},"excerpt":{"rendered":"<p>Exercise Directory Filter by muscle group, equipment, or category. Tap a card for coaching notes and full instructions. 0 of 0 exercises Showing all exercises Clear filters No exercises match these filters. Clear filters Data: Free Exercise DB \u2014 public domain (Unlicense), 800+ exercises. Frames alternate between the start and peak of each rep.<\/p>\n","protected":false},"author":41980142,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","_wpcom_ai_launchpad_about_page":false,"_wpcom_ai_launchpad_gallery_page":false,"_wpcom_ai_launchpad_contact_page":false,"_wpcom_ai_launchpad_events_page":false,"_wpcom_ai_launchpad_video_page":false,"_wpcom_ai_launchpad_portfolio_piece":false,"footnotes":""},"class_list":["post-18133","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Gym Exercise Directory - Fitness &amp; MMA Blog - BlackBeltWhiteHat.com<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blackbeltwhitehat.com\/en\/exercise-directory\/\" \/>\n<meta property=\"og:locale\" content=\"en_GB\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Gym Exercise Directory - Fitness &amp; MMA Blog - BlackBeltWhiteHat.com\" \/>\n<meta property=\"og:description\" content=\"Exercise Directory Filter by muscle group, equipment, or category. Tap a card for coaching notes and full instructions. 0 of 0 exercises Showing all exercises Clear filters No exercises match these filters. Clear filters Data: Free Exercise DB \u2014 public domain (Unlicense), 800+ exercises. Frames alternate between the start and peak of each rep.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blackbeltwhitehat.com\/en\/exercise-directory\/\" \/>\n<meta property=\"og:site_name\" content=\"Fitness &amp; MMA Blog - BlackBeltWhiteHat.com\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/BlackBeltWhiteHat\/\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-17T11:36:08+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:site\" content=\"@MMATraining1980\" \/>\n<meta name=\"twitter:label1\" content=\"Estimated reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Gym Exercise Directory - Fitness &amp; MMA Blog - BlackBeltWhiteHat.com","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blackbeltwhitehat.com\/en\/exercise-directory\/","og_locale":"en_GB","og_type":"article","og_title":"Gym Exercise Directory - Fitness &amp; MMA Blog - BlackBeltWhiteHat.com","og_description":"Exercise Directory Filter by muscle group, equipment, or category. Tap a card for coaching notes and full instructions. 0 of 0 exercises Showing all exercises Clear filters No exercises match these filters. Clear filters Data: Free Exercise DB \u2014 public domain (Unlicense), 800+ exercises. Frames alternate between the start and peak of each rep.","og_url":"https:\/\/blackbeltwhitehat.com\/en\/exercise-directory\/","og_site_name":"Fitness &amp; MMA Blog - BlackBeltWhiteHat.com","article_publisher":"https:\/\/www.facebook.com\/BlackBeltWhiteHat\/","article_modified_time":"2026-09-17T11:36:08+00:00","twitter_card":"summary_large_image","twitter_site":"@MMATraining1980","twitter_misc":{"Estimated reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/blackbeltwhitehat.com\/exercise-directory\/","url":"https:\/\/blackbeltwhitehat.com\/exercise-directory\/","name":"Gym Exercise Directory - Fitness &amp; MMA Blog - BlackBeltWhiteHat.com","isPartOf":{"@id":"https:\/\/blackbeltwhitehat.com\/en\/#website"},"datePublished":"2026-09-16T13:03:35+00:00","dateModified":"2026-09-17T11:36:08+00:00","breadcrumb":{"@id":"https:\/\/blackbeltwhitehat.com\/exercise-directory\/#breadcrumb"},"inLanguage":"en-GB","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blackbeltwhitehat.com\/exercise-directory\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/blackbeltwhitehat.com\/exercise-directory\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blackbeltwhitehat.com\/"},{"@type":"ListItem","position":2,"name":"Gym Exercise Directory"}]},{"@type":"WebSite","@id":"https:\/\/blackbeltwhitehat.com\/en\/#website","url":"https:\/\/blackbeltwhitehat.com\/en\/","name":"Fitness & MMA Blog - BlackBeltWhiteHat.com","description":"Fitness, MMA and Nutrition Blog UK","publisher":{"@id":"https:\/\/blackbeltwhitehat.com\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blackbeltwhitehat.com\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-GB"},{"@type":"Organization","@id":"https:\/\/blackbeltwhitehat.com\/en\/#organization","name":"Fitness & MMA Blog - BlackBeltWhiteHat.com","url":"https:\/\/blackbeltwhitehat.com\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/blackbeltwhitehat.com\/en\/#\/schema\/logo\/image\/","url":"https:\/\/blackbeltwhitehat.com\/wp-content\/uploads\/2019\/06\/cropped-mmabloglogodis-copy.png","contentUrl":"https:\/\/blackbeltwhitehat.com\/wp-content\/uploads\/2019\/06\/cropped-mmabloglogodis-copy.png","width":352,"height":352,"caption":"Fitness & MMA Blog - BlackBeltWhiteHat.com"},"image":{"@id":"https:\/\/blackbeltwhitehat.com\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/BlackBeltWhiteHat\/","https:\/\/x.com\/MMATraining1980"]}]}},"jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/P2Sx3t-4It","jetpack-related-posts":[{"id":18137,"url":"https:\/\/blackbeltwhitehat.com\/en\/exercise-directory\/essential-mma-techniques-for-beginners\/","url_meta":{"origin":18133,"position":0},"title":"Essential MMA Techniques for Beginners","author":"Dave","date":"September 17, 2026","format":false,"excerpt":"MMA Technique Directory Curated demonstrations for MMA, BJJ, wrestling and Muay Thai. Filter by style, position or category \u2014 tap for coaching notes. 0 of 0 techniques Showing all techniques Clear filters No techniques match these filters. Clear filters Video credits & attribution Videos above are embedded from these instructors'\u2026","rel":"","context":"Similar post","block_context":{"text":"Similar post","link":""},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":4665,"url":"https:\/\/blackbeltwhitehat.com\/en\/technique-directory\/striking\/boxing-combinations\/","url_meta":{"origin":18133,"position":1},"title":"Boxing Combinations","author":"Dave","date":"May 18, 2016","format":false,"excerpt":"\u00a0 Here are some combinations to try out on the pads and punchbag. Boxing Combinations Double Jab Jab, Cross Double Jab, Cross Left Jab, Cross, Left Hook, Cross Left Jab to the head, Right cross to the body, Left Hook to the head Left Jab, Left Hook Left Jab, Left\u2026","rel":"","context":"Similar post","block_context":{"text":"Similar post","link":""},"img":{"alt_text":"boxing jab","src":"https:\/\/i0.wp.com\/blackbeltwhitehat.com\/wp-content\/uploads\/2013\/06\/jab2a.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":4535,"url":"https:\/\/blackbeltwhitehat.com\/en\/technique-directory\/striking\/cross-and-cross-counter\/","url_meta":{"origin":18133,"position":2},"title":"How to Throw a Cross in Boxing","author":"Dave","date":"May 13, 2016","format":false,"excerpt":"The cross is thrown from the backhand as opposed to the jab which is thrown from your hand nearest to the opponent. It is also know as the 'backhand' and the 'straight right' or 'straight left' http:\/\/www.sneakpunch.com Conventionally a cross is thrown with a 'twist' at the end, whereby the\u2026","rel":"","context":"Similar post","block_context":{"text":"Similar post","link":""},"img":{"alt_text":"cross boxing punch","src":"https:\/\/i0.wp.com\/blackbeltwhitehat.com\/wp-content\/uploads\/2016\/05\/cross_bw.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blackbeltwhitehat.com\/wp-content\/uploads\/2016\/05\/cross_bw.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blackbeltwhitehat.com\/wp-content\/uploads\/2016\/05\/cross_bw.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":4567,"url":"https:\/\/blackbeltwhitehat.com\/en\/technique-directory\/wrestling\/overhook\/","url_meta":{"origin":18133,"position":3},"title":"Overhook in MMA &amp; Wrestling","author":"Dave","date":"May 15, 2016","format":false,"excerpt":"The overhook in wrestling, is also called 'a wizzer' An overhook is established by looping your arm over and around your opponent's It is often used as a counter to an 'underhook' - when an opponent's arm loops under your arm pit. In the image below, Anderson Silva has an\u2026","rel":"","context":"Similar post","block_context":{"text":"Similar post","link":""},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blackbeltwhitehat.com\/wp-content\/uploads\/2019\/05\/overhook3.gif?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":70,"url":"https:\/\/blackbeltwhitehat.com\/en\/","url_meta":{"origin":18133,"position":4},"title":"Home","author":"Dave","date":"December 14, 2012","format":false,"excerpt":"Welcome to my Fitness Blog & MMA Blog.\u00a0It's basically a mash-up of all things I'm interested in. I am a former MMA fighter and kickboxer. I have a first class Degree in Sport Science from Loughborough University and a Master's degree in Exercise & Nutrition. I also run my own\u2026","rel":"","context":"Similar post","block_context":{"text":"Similar post","link":""},"img":{"alt_text":"mma blog","src":"https:\/\/i0.wp.com\/blackbeltwhitehat.com\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_l6p36ml6p36ml6p3.png?fit=1200%2C545&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blackbeltwhitehat.com\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_l6p36ml6p36ml6p3.png?fit=1200%2C545&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/blackbeltwhitehat.com\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_l6p36ml6p36ml6p3.png?fit=1200%2C545&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/blackbeltwhitehat.com\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_l6p36ml6p36ml6p3.png?fit=1200%2C545&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/blackbeltwhitehat.com\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_l6p36ml6p36ml6p3.png?fit=1200%2C545&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":4537,"url":"https:\/\/blackbeltwhitehat.com\/en\/technique-directory\/striking\/","url_meta":{"origin":18133,"position":5},"title":"Striking","author":"Dave","date":"May 13, 2016","format":false,"excerpt":"Striking & Boxing Techniques A handy directory of striking and boxing techniques to keep your MMA and boxing game up to speed. If you're a beginner or an advanced fighter, it's always handy to have techniques arranged in this way. How to Jab in Boxing and MMA How to Double\u2026","rel":"","context":"Similar post","block_context":{"text":"Similar post","link":""},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/blackbeltwhitehat.com\/en\/wp-json\/wp\/v2\/pages\/18133","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blackbeltwhitehat.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/blackbeltwhitehat.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/blackbeltwhitehat.com\/en\/wp-json\/wp\/v2\/users\/41980142"}],"replies":[{"embeddable":true,"href":"https:\/\/blackbeltwhitehat.com\/en\/wp-json\/wp\/v2\/comments?post=18133"}],"version-history":[{"count":3,"href":"https:\/\/blackbeltwhitehat.com\/en\/wp-json\/wp\/v2\/pages\/18133\/revisions"}],"predecessor-version":[{"id":18136,"href":"https:\/\/blackbeltwhitehat.com\/en\/wp-json\/wp\/v2\/pages\/18133\/revisions\/18136"}],"wp:attachment":[{"href":"https:\/\/blackbeltwhitehat.com\/en\/wp-json\/wp\/v2\/media?parent=18133"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}