staging.bradycandell.com BradyCandell.com
  • —
  • Career Timeline
  • Case Study

Complex Requirements

Simple,
Measurable Results

Making WordPress do what you need, not what it wants.

rocket_launch

Fast Delivery

code_blocks

Clean Code

menu_book
  View the Case Study
timeline
  Career Timeline & Resume

Featured Projects

Shared Vision

Clear communication helps align diverse perspectives. Whether sketching strategies or mapping next steps, I value using visuals and teamwork to build momentum.

Insight in Action

Turning raw data into meaningful direction through open discussion and analysis.

Ideas in Motion

Innovation doesn’t wait for the boardroom. Sharing ideas on the move keeps projects agile, conversations fresh, and solutions practical.

Focused Execution

Where planning meets precision — collaborating hands-on to ensure accuracy and results.

Interactive Card Stack

The client needed a clear, engaging way to present information, and the UX designer proposed an interactive card stack. I implemented the design by solving challenges with z-index, transitions, and transforms, delivering a dynamic, layered experience that makes content easy and fun to explore.

Code Review

<?php
/**
 * Course Catalog Loop Block
 * Version: 3.1 - Server-side rendering (no AJAX)
 */

$args = [
    'post_type'      => 'course',
    'posts_per_page' => -1,
    'orderby'        => 'title',
    'order'          => 'ASC',
    'post_status'    => 'publish'
];

$query = new WP_Query($args);

if ($query->have_posts()) {
    echo '<div class="course-grid">';
    
    while ($query->have_posts()) {
        $query->the_post();
        $post_id = get_the_ID();

        $title = esc_html(get_the_title());
        $summary = get_field('summary_text', $post_id);
        $details = get_field('additional_details', $post_id);
        $custom_link = get_field('custom_link', $post_id);
        $custom_link_url = $custom_link['url'] ?? '';
        $custom_link_label = $custom_link['title'] ?? 'Learn More';

        ?>
        <article class="course-card" data-course-id="<?php echo $post_id; ?>">
            <h2><?php echo $title; ?></h2>
            
            <?php if ($summary): ?>
                <div class="summary-text"><?php echo wp_kses_post($summary); ?></div>
            <?php endif; ?>
            
            <?php if ($details): ?>
                <div class="additional-details"><?php echo wp_kses_post($details); ?></div>
            <?php endif; ?>
            
            <?php if ($custom_link_url): ?>
                <p><a class="custom-link" href="<?php echo esc_url($custom_link_url); ?>" target="_blank" rel="noopener"><?php echo esc_html($custom_link_label); ?></a></p>
            <?php endif; ?>
            
            <?php
            // Display ACF taxonomy tags
            $custom_taxonomies = get_field('custom_taxonomies', $post_id);
            if ($custom_taxonomies && is_array($custom_taxonomies)):
            ?>
                <div class="acf-taxonomy-tags">
                    <?php foreach ($custom_taxonomies as $taxonomy_group): 
                        // Ensure taxonomy_group is an array
                        if (!is_array($taxonomy_group)) continue;
                        ?>
                        <div class="taxonomy-group">
                            <?php if (!empty($taxonomy_group['taxonomy_text'])): ?>
                                <h4 class="taxonomy-header"><?php echo esc_html($taxonomy_group['taxonomy_text']); ?></h4>
                            <?php endif; ?>
                            
                            <?php 
                            // Check if terms exists and is an array
                            if (!empty($taxonomy_group['terms']) && is_array($taxonomy_group['terms'])): 
                                foreach ($taxonomy_group['terms'] as $term): 
                                    // Ensure term is an array
                                    if (!is_array($term)) continue;
                                    ?>
                                    <div class="term-tag-group">
                                        <?php
                                        $term_name = $term['term_name'] ?? '';
                                        $term_color = $term['term_tile_color'] ?? '#ccc';
                                        if ($term_name):
                                        ?>
                                            <span class="term-tag" style="background-color: <?php echo esc_attr($term_color); ?>">
                                                <?php echo esc_html($term_name); ?>
                                            </span>
                                        <?php endif; ?>
                                        
                                        <?php 
                                        // Check if sub_terms exists and is an array
                                        if (!empty($term['sub_terms']) && is_array($term['sub_terms'])): ?>
                                            <div class="sub-term-tags">
                                                <?php foreach ($term['sub_terms'] as $sub_term): 
                                                    // Ensure sub_term is an array
                                                    if (!is_array($sub_term)) continue;
                                                    
                                                    $sub_name = $sub_term['sub_term_name'] ?? '';
                                                    $sub_color = $sub_term['sub_term_tile_color'] ?? '#eee';
                                                    if ($sub_name):
                                                    ?>
                                                        <span class="sub-term-tag" style="background-color: <?php echo esc_attr($sub_color); ?>">
                                                            <?php echo esc_html($sub_name); ?>
                                                        </span>
                                                    <?php endif; ?>
                                                <?php endforeach; ?>
                                            </div>
                                        <?php endif; ?>
                                    </div>
                                <?php endforeach; 
                            endif; ?>
                        </div>
                    <?php endforeach; ?>
                </div>
            <?php endif; ?>
        </article>
        <?php
    }
    
    echo '</div>';
    wp_reset_postdata();
} else {
    echo '<div class="course-grid"><p class="no-courses">No courses found.</p></div>';
}
?>
<script>
/**
 * Catalog interactions (framework-free)
 * - Flip card toggle
 * - Code area tab switching
 *
 * Assumptions:
 * - Each component is wrapped in an element with class `.flipBox` (or add [data-flip-container]).
 * - Any flip button inside the wrapper has class `.flipToggle`.
 * - Code buttons have a `data-target="<panel-id>"` attribute.
 * - Code panels have IDs matching those targets and share a class `.codeArea`.
 *
 * This script uses container scoping so you can have multiple instances on a page
 * without relying on server-rendered IDs.
 */
(function () {
  "use strict";

  function hide(el) {
    el.style.display = "none";
  }
  function show(el) {
    el.style.display = "block";
  }

  document.addEventListener("DOMContentLoaded", function () {
    // Look for all instances on the page
    var containers = document.querySelectorAll(".flipBox, [data-flip-container]");
    if (!containers.length) return;

    containers.forEach(function (container) {
      // ----- Flip Toggle -----
      var flipBox = container; // treat the wrapper as the flipping element
      var flipButtons = container.querySelectorAll(".flipToggle, [data-flip-toggle]");

      flipButtons.forEach(function (btn) {
        btn.addEventListener("click", function (e) {
          e.preventDefault();
          flipBox.classList.toggle("flipped");
          // Update aria-pressed if present
          var pressed = btn.getAttribute("aria-pressed");
          if (pressed !== null) {
            btn.setAttribute("aria-pressed", String(!(pressed === "true")));
          }
        });
      });

      // ----- Code Area Tabs -----
      var codeButtons = container.querySelectorAll("[data-target]");
      if (!codeButtons.length) return; // No code buttons in this container

      // Collect panels either inside container or globally (id target)
      var panelMap = {};
      codeButtons.forEach(function (btn) {
        var targetId = btn.getAttribute("data-target");
        if (!targetId) return;
        var panel =
          container.querySelector("#" + CSS.escape(targetId)) ||
          document.getElementById(targetId);
        if (panel) {
          panelMap[targetId] = panel;
        }
      });

      // Helper to switch active panel
      function setActive(targetId) {
        // Hide all known panels & reset buttons
        Object.keys(panelMap).forEach(function (id) {
          hide(panelMap[id]);
        });
        codeButtons.forEach(function (b) {
          b.classList.remove("active");
          if (b.hasAttribute("aria-expanded")) {
            b.setAttribute("aria-expanded", "false");
          }
          if (b.hasAttribute("aria-selected")) {
            b.setAttribute("aria-selected", "false");
          }
        });

        // Show selected
        var activePanel = panelMap[targetId];
        if (activePanel) {
          show(activePanel);
        }

        // Mark triggering button active
        codeButtons.forEach(function (b) {
          if (b.getAttribute("data-target") === targetId) {
            b.classList.add("active");
            if (b.hasAttribute("aria-expanded")) {
              b.setAttribute("aria-expanded", "true");
            }
            if (b.hasAttribute("aria-selected")) {
              b.setAttribute("aria-selected", "true");
            }
          }
        });
      }

      // Wire up click handlers
      codeButtons.forEach(function (btn) {
        btn.addEventListener("click", function (e) {
          e.preventDefault();
          var targetId = btn.getAttribute("data-target");
          if (targetId && panelMap[targetId]) {
            setActive(targetId);
          }
        });
      });

      // Initialize: show the first available panel if any
      // Hide all panels first
      Object.keys(panelMap).forEach(function (id) {
        hide(panelMap[id]);
      });
      if (codeButtons.length) {
        var firstId = codeButtons[0].getAttribute("data-target");
        if (firstId && panelMap[firstId]) {
          setActive(firstId);
        }
      }
    });
  });
})();
</script>
<style>
section#cardStackBox {
    display: flex;
    max-width: 1300px;
    margin: 0 auto;
    padding: 106px 0 0 50px;
    gap: 80px;
}
#btnForward svg,
#btnBack svg {
    width: 25px;
    height: 21px;
}
article#cardMachine {
    display: flex;
    width: 517px;
    height: 415px;
    align-items: center;
}

section#cardHolder {
    position: relative;
    height: 437px;
    width: 476px;
    left: 45px;
}

.actualCard {
    box-sizing: content-box;
    position: absolute;
    height: 376px;
    width: 310px;
    padding: 16px;
    border: 1px solid #FFFFFF;
    border-radius: 20px 0;
    transition: transform 0.4s ease-in-out, z-index 0s 0.4s;
    box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25);
}

.imageHolder {
    width: 310px;
    height: 192px;
}

.cardImage {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    border-radius: 20px 0;
}

.cardNumber1 {
    transform: translate(0, -6.5px) rotate(0deg) scale(1);
}

.cardNumber2 {
    transform: translate(25px, -5px) rotate(2deg) scale(1);
}

.cardNumber3 {
    transform: translate(51px, 3px) rotate(1.65deg) scale(1);
}

.cardNumber4 {
    transform: translate(74px, 12px) rotate(2deg) scale(1);
}

.cardNumberHidden {
    transform: translate(74px, 12px) rotate(2deg) scale(1);
    opacity: 0;
    pointer-events: none;
}
aside.cardInfo {
    max-width: 600px;
    padding-top: 52px;
}
h2.cardInfo_headline {
    font: 700 48px / 56px Karla, sans-serif;
}
div.cardInfo_paragraph p {
    font: 500 16px / 20px Karla, sans-serif;
}
h3.cardHeadline {
    font: 700 24px / 24px Triront, serif;
    height: 42px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 8px 0;
    text-transform: uppercase;
    letter-spacing: normal;
}
p.cardTextArea {
    font: 500 18px / 19px Karla, sans-serif;
    width: 305px;
}
a#btnBack {
    position: relative;
    z-index: 999999;
}
#btnForward {
    position: relative;
    z-index: 99999;
}
/* Works for both <button> and <a> controls */
#btnForward.is-disabled,
#btnBack.is-disabled,
#btnForward[disabled],
#btnBack[disabled] {
  opacity: 0.4;
  pointer-events: none; /* stops mouse/touch on anchors */
  cursor: not-allowed;
}
.cm-fit {
  width: 345px;
  height: 292px;
  position: relative;
  top: 15px;
}
.cm-fit #cardMachine {
  width: 517px;
  height: 415px;
  transform-origin: top left;
  transform: scale(0.6673) !important;
}
</style>
View on GitHub

Course Catalog

Previously, grade levels had individual static course pages with no interactivity. The client wanted a single catalog with filters and search that editors could update easily. I built a custom options page for courses and a plugin to handle AJAX filtering, allowing users to refine results instantly without page reloads.  View the catalog here.

Course Catalog Code

// Function to extract and normalize grade level from custom taxonomies
if (!function_exists('extract_grade_level')) {
    function extract_grade_level($custom_taxonomies) {
        if (!$custom_taxonomies || !is_array($custom_taxonomies)) return [999, 'Unknown'];
        
        foreach ($custom_taxonomies as $taxonomy_group) {
            if (!is_array($taxonomy_group)) continue;
            
            $taxonomy_text = strtolower($taxonomy_group['taxonomy_text'] ?? '');
            
            // Look for grade-related taxonomy (flexible keywords)
            if (strpos($taxonomy_text, 'grade') !== false || strpos($taxonomy_text, 'level') !== false) {
                $term_name = $taxonomy_group['term_name'] ?? '';
                
                // Check sub-terms first (more specific grades)
                if (!empty($taxonomy_group['sub_terms']) && is_array($taxonomy_group['sub_terms'])) {
                    foreach ($taxonomy_group['sub_terms'] as $sub_term) {
                        if (!is_array($sub_term)) continue;
                        $sub_name = $sub_term['sub_term_name'] ?? '';
                        $sort_value = normalize_grade_for_sorting($sub_name);
                        if ($sort_value !== 999) {
                            return [$sort_value, $sub_name];
                        }
                    }
                }
                
                // If no specific sub-term, use the main term
                $sort_value = normalize_grade_for_sorting($term_name);
                return [$sort_value, $term_name];
            }
        }
        
        return [999, 'Unknown']; // Default for unknown
    }
}

// Function to normalize any grade string into a sortable number
if (!function_exists('normalize_grade_for_sorting')) {
    function normalize_grade_for_sorting($grade_string) {
        $grade_lower = strtolower(trim($grade_string));
        
        // Handle kindergarten variations
        if (strpos($grade_lower, 'k') !== false || strpos($grade_lower, 'kindergarten') !== false) {
            return 0;
        }
        
        // Extract numbers from grade strings (1st, 2nd, 3rd, etc.)
        if (preg_match('/(\d+)/', $grade_lower, $matches)) {
            return (int)$matches[1];
        }
        
        // Handle specific school level terms
        if (strpos($grade_lower, 'elementary school') !== false) return 50;
        if (strpos($grade_lower, 'middle school') !== false) return 100;
        if (strpos($grade_lower, 'high school') !== false) return 200;
        
        // If we can't determine, return high number so it sorts to end
        return 999;
    }
}

// Custom sorting function: Grade Level > Subject > Course Name
usort($courses, function($a, $b) {
    // 1. Sort by Grade Level (numeric)
    if ($a['grade_level_sort'] !== $b['grade_level_sort']) {
        return $a['grade_level_sort'] <=> $b['grade_level_sort'];
    }
    
    // 2. Sort by Subject with sub-terms (alphabetical)
    if ($a['subject'] !== $b['subject']) {
        return strcmp($a['subject'], $b['subject']);
    }
    
    // 3. Sort by Course Name (alphabetical)
    return strcmp($a['title'], $b['title']);
});
// Advanced Hierarchical Filtering with Smart AND/OR Logic
function filterAndPaginateCourses() {
    const filtersByTaxonomy = {};
    
    // Organize filters by taxonomy groups (grades, subjects, etc.)
    $('.course-catalog-sidebar input[type="checkbox"]:checked').each(function() {
        const $checkbox = $(this);
        const taxonomyGroup = $checkbox.attr('name');
        const isSubTerm = $checkbox.closest('.sub-term-list').length > 0;
        
        if (!filtersByTaxonomy[taxonomyGroup]) {
            filtersByTaxonomy[taxonomyGroup] = { mainTerms: [], subTerms: [] };
        }
        
        // Hierarchical term handling - sub-terms override main terms
        if (isSubTerm) {
            filtersByTaxonomy[taxonomyGroup].subTerms.push('subterm-' + sanitizeClass($checkbox.val()));
        } else {
            filtersByTaxonomy[taxonomyGroup].mainTerms.push('term-' + sanitizeClass($checkbox.val()));
        }
    });

    $('.course-card').each(function() {
        const $card = $(this);
        let matchesFilters = true;
        
        // AND logic between taxonomy groups, OR within each group
        for (const [taxonomyGroup, filters] of Object.entries(filtersByTaxonomy)) {
            let matchesThisTaxonomy = false;
            
            // Hierarchical priority: check sub-terms first, then main terms
            if (filters.subTerms.length > 0) {
                matchesThisTaxonomy = filters.subTerms.some(filterClass => $card.hasClass(filterClass));
            } else if (filters.mainTerms.length > 0) {
                matchesThisTaxonomy = filters.mainTerms.some(filterClass => $card.hasClass(filterClass));
            }
            
            if (!matchesThisTaxonomy) {
                matchesFilters = false;
                break;
            }
        }
        
        $card.toggleClass('visible-course', matchesFilters);
    });
    
    paginateVisibleCourses();
}
/* Mobile-First Responsive Sidebar with State Management */
.course-catalog-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: white;
    z-index: 1000;
    transform: translateX(-100%);
    display: flex;
    flex-direction: column;
}

.course-catalog-sidebar.active {
    transform: translateX(0);
    width: 268px;
    left: auto;
}

/* Prevent body scroll when sidebar is open */
body.sidebar-open {
    overflow: hidden;
    position: fixed;
    width: 100%;
}

/* Desktop: Convert to static sidebar */
@media (min-width: 1178px) {
    .course-catalog-sidebar {
        position: unset !important; 
        transform: none !important;
        width: 164px;
        background: transparent !important;
    }
}

/* Accordion with CSS-only animation */
.filter-group h3:after {
    content: '';
    width: 31px;
    height: 31px;
    background-image: url('data:image/svg+xml...');
    transition: transform 0.3s ease;
}

.filter-group.open h3:after {
    transform: rotate(180deg);
}
// COURSE CATALOG BLOCKS
array(
    'name'              => 'course-catalog-sidebar',
    'title'             => __('Course Catalog Sidebar'),
    'description'       => __('Filter sidebar for the course catalog'),
    'render_template'   => 'acf-blocks/course-catalog-sidebar.php',
    'category'          => 'laurel-springs-blocks',
    'icon'              => 'filter',
    'keywords'          => array('course', 'filter', 'sidebar'),
    'supports'          => array('align' => false ),
    'mode'              => 'edit',
),
array(
    'name'              => 'course-catalog-loop',
    'title'             => __('Course Catalog Loop'),
    'description'       => __('Displays filtered list of course posts'),
    'render_template'   => 'acf-blocks/course-catalog-loop.php',  // ← Direct path
    'category'          => 'laurel-springs-blocks',
    'icon'              => 'welcome-learn-more',
    'keywords'          => array( 'courses', 'catalog', 'loop' ),
    'mode'              => 'preview',
    'supports'          => array( 'anchor' => true ),
),

Deeper insights: Case Studies

A view of the desktop version of the Course Catalog

The Moody's Analytics' Course Catalog

At Moody’s, I led the redesign of their course catalog from discovery through design and implementation. I analyzed site analytics and user behavior to uncover pain points like high bounce rates, siloed catalogs, and poor mobile usability.

I then prototyped solutions that emphasized unified navigation, responsive filters, and a more intuitive browsing experience. Finally, I built the catalog in Sitecore—writing the necessary front-end and CMS code to support dynamic filtering and enabling content creators to manage courses with ease.

Over time, I produced several iterations of the catalog, continuously refining the design and functionality based on data and feedback.

View the Full Case Study

Copyright © 2026 · Genesis Child – DDC on Genesis Framework · WordPress · Log in