Code Generator

Configure and copy your Circle.so code

Enable for homework/assignments (creates separate tracking)

Auto-generated values:

Action ID: Class-0001_attendance

Field Name: Class-0001_attended

<!-- Copy everything below this line into your Circle.so post HTML field -->
<div class="checkbox-wrapper" id="checkbox-wrapper-Class-0001_attendance">
  <div class="checkbox-container">
    <input type="checkbox" class="hidden-checkbox" id="task-Class-0001_attendance">
    <label class="custom-checkbox" for="task-Class-0001_attendance">
      <svg class="checkmark" width="13" height="11" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="M12.55.89a1.437 1.437 0 0 1 0 2.032L5.361 10.11a1.437 1.437 0 0 1-2.033 0L.454 7.235a1.438 1.438 0 0 1 2.033-2.033l1.859 1.86L10.516.89a1.437 1.437 0 0 1 2.033 0Z" fill="#fff"/>
      </svg>
    </label>
    <span class="label">Watched Today's Lesson! 👍</span>
  </div>
  <div class="PTS-badge">
    <svg class="star-icon" width="12" height="12" fill="currentColor">
      <path d="M11.963 4.563a.597.597 0 00-.325-.35c-.6-.263-2.175-.363-3.538-.413C7.675 2.563 6.988.737 6.412.175A.588.588 0 006 0a.64.64 0 00-.425.175C5.012.737 4.325 2.562 3.9 3.8c-1.362.05-2.938.15-3.538.413a.562.562 0 00-.325.35.585.585 0 00.038.5c.388.712 1.375 1.775 2.612 2.825-.337 1.374-.562 2.6-.624 3.462a.606.606 0 00.274.55.577.577 0 00.6.037c.213-.124.45-.237.688-.35.65-.325 1.438-.687 2.375-1.274.938.587 1.725.95 2.375 1.274.238.113.475.226.688.35a.681.681 0 00.274.063.62.62 0 00.325-.1.606.606 0 00.275-.55c-.062-.862-.287-2.088-.624-3.462 1.237-1.05 2.224-2.113 2.612-2.825.087-.15.1-.338.037-.5z"/>
    </svg>
    2 PTS
  </div>
</div>
<div id="toast-Class-0001_attendance" class="toast"></div>
<canvas id="confetti-canvas"></canvas>

<style>
/* Reset any inherited colors */
.checkbox-wrapper,
.checkbox-wrapper * {
  color: #6c8357;
}

.checkbox-wrapper {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: system-ui, -apple-system, sans-serif;
  margin-top: 16px;
}

.checkbox-container {
  position: relative;
  cursor: pointer;
  padding: 6px;
  margin-left: -6px;
  display: flex;
  align-items: center;
}

.hidden-checkbox {
  display: none;
}

.custom-checkbox {
  width: 24px;
  height: 24px;
  border: 2px solid #6c8357;
  border-radius: 9999px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  position: relative;
  overflow: hidden;
}

.checkmark {
  opacity: 0;
  transform: scale(0.8) rotate(-45deg);
  transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

.checkmark path {
  fill: #fff !important;
}

.hidden-checkbox:checked + .custom-checkbox {
  background-color: #6c8357;
  border-color: #6c8357;
  animation: pulse 0.5s;
}

.hidden-checkbox:checked + .custom-checkbox .checkmark {
  opacity: 1;
  transform: scale(1) rotate(0);
}

.hidden-checkbox:checked + .custom-checkbox::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  background: rgba(255, 255, 255, 0.7);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  animation: burst 0.5s;
}

@keyframes pulse {
  0% { transform: scale(1); }
  50% { transform: scale(1.1); }
  100% { transform: scale(1); }
}

@keyframes burst {
  0% { width: 0; height: 0; opacity: 0.5; }
  100% { width: 200%; height: 200%; opacity: 0; }
}

.label {
  font-weight: 600;
  font-size: 14px;
  color: #6c8357 !important;
  margin-left: 8px;
  transition: all 0.3s;
  text-decoration: none !important;
}

.checkbox-container:hover .label {
  color: #6c8357 !important;
}

.hidden-checkbox:checked ~ .label {
  animation: labelPop 0.3s forwards;
  color: #6c8357 !important;
}

@keyframes labelPop {
  0% { transform: scale(1); }
  50% { transform: scale(1.05); }
  100% { transform: scale(1); }
}

.PTS-badge {
  display: flex;
  align-items: center;
  background-color: #6c8357;
  color: white !important;
  font-size: 12px;
  font-weight: 600;
  padding: 4px 8px;
  border-radius: 12px;
  border: 1px solid #6c8357;
  margin-left: 8px;
  transition: all 0.3s;
}

.PTS-badge * {
  color: white !important;
}

.hidden-checkbox:checked ~ .PTS-badge {
  animation: badgePop 0.3s forwards;
}

@keyframes badgePop {
  0% { transform: scale(1); }
  50% { transform: scale(1.1); }
  100% { transform: scale(1); }
}

.star-icon {
  margin-right: 4px;
}

.toast {
  position: fixed;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
  padding: 12px 24px;
  border-radius: 8px;
  color: white !important;
  font-weight: 500;
  opacity: 0;
  transition: opacity 0.3s ease;
  z-index: 9999;
  font-size: 16px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  pointer-events: none;
}

.toast * {
  color: white !important;
}

.toast.success {
  background-color: #6c8357;
}

.toast.error {
  background-color: #EF4444;
}

.toast.show {
  opacity: 1;
  animation: toastPop 0.3s forwards;
}

@keyframes toastPop {
  0% { transform: translateX(-50%) scale(0.9); }
  100% { transform: translateX(-50%) scale(1); }
}

#confetti-canvas {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 9998;
}

/* Force color overrides */
.checkbox-wrapper a,
.checkbox-wrapper a:hover,
.checkbox-wrapper a:visited {
  color: #6c8357 !important;
  text-decoration: none !important;
}

.custom-checkbox:hover {
  border-color: #6c8357 !important;
}
</style>


<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js"></script>

<script>
(function() {
  // —-------------------- CONFIGURE THESE VALUES FOR EACH POST —----------
  const CLASS_ID = 'Class-0001'; // Add a class identifier here
  const ACTION_ID = 'Class-0001_attendance'; // Change this for each post (and in the HTML above!)
  const FIELD_NAME = 'Class-0001_attended'; // Change this for each post
  const POINTS = 2; // Points to award
  const WEBHOOK_URL = 'https://hook.us1.make.com/7e2upld7vea5xgb1f80q9en29wulvtjy';
  const SOUND_URL = 'https://assets-v2.circle.so/1ss93jmz4w3admahhsaufz39zglr';

  function showToast(message, type = 'success') {
    console.log('Showing toast:', message, type, 'for action:', ACTION_ID);
    const toast = document.getElementById(`toast-${ACTION_ID}`);
    toast.textContent = message;
    toast.className = `toast ${type} show`;
    setTimeout(() => {
      toast.className = 'toast';
    }, 3000);
  }

  function playCheckSound() {
    const audio = new Audio(SOUND_URL);
    audio.play().catch(error => {
        console.error('Error playing sound:', error);
    });
  }

  function triggerConfetti() {
    const duration = 3 * 1000;
    const animationEnd = Date.now() + duration;
    const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 };

    function randomInRange(min, max) {
      return Math.random() * (max - min) + min;
    }

    const interval = setInterval(function() {
      const timeLeft = animationEnd - Date.now();
      if (timeLeft <= 0) {
        return clearInterval(interval);
      }
      const particleCount = 50 * (timeLeft / duration);
      confetti(Object.assign({}, defaults, { 
        particleCount, 
        origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 } 
      }));
      confetti(Object.assign({}, defaults, { 
        particleCount, 
        origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 } 
      }));
    }, 250);
  }

  async function updatePoints(checked) {
    console.log('Updating points:', checked, 'for action:', ACTION_ID);
    try {
        const payload = {
            classId: CLASS_ID, // Include the class ID here
            email: circleUser.email,
            action: checked ? 'add' : 'subtract',
            points: POINTS,
            actionId: ACTION_ID,
            [FIELD_NAME]: checked ? 'checked' : 'unchecked'
        };
        
        console.log('Sending webhook payload:', payload);
        console.log('Circle User:', circleUser);

        const response = await fetch(WEBHOOK_URL, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(payload)
        });

        console.log('Webhook response status:', response.status);
        const responseData = await response.json().catch(e => console.log('No JSON response'));
        console.log('Webhook response data:', responseData);

        if (!response.ok) {
            throw new Error(`Failed to update points: ${response.status} ${response.statusText}`);
        }

        localStorage.setItem(`checkbox_state_${circleUser.email}_${ACTION_ID}`, checked);
        showToast(checked ? `+${POINTS} PTS Added!` : `-${POINTS} PTS Removed`, 'success');
        if (checked) {
            triggerConfetti();
        }
    } catch (error) {
        console.error('Error updating points:', error);
        console.error('Error details:', {
            email: circleUser?.email,
            ACTION_ID,
            FIELD_NAME,
            WEBHOOK_URL
        });
        const checkbox = document.getElementById(`task-${ACTION_ID}`);
        if (checkbox) checkbox.checked = !checked;
        showToast('Failed to update points. Please try again.', 'error');
    }
  }

  function loadSavedState() {
    console.log('Loading saved state for action:', ACTION_ID);
    const checkbox = document.getElementById(`task-${ACTION_ID}`);
    if (!checkbox) return;
    const savedState = localStorage.getItem(`checkbox_state_${circleUser.email}_${ACTION_ID}`);
    console.log('Saved state for action', ACTION_ID, ':', savedState);
    if (savedState !== null) {
      checkbox.checked = savedState === 'true';
    }
  }

  function initialize() {
    console.log('Initializing checkbox for action:', ACTION_ID);
    try {
      if (!window.circleUser) {
        throw new Error('Circle user data not found');
      }
      console.log('Circle User found:', circleUser);

      const checkbox = document.getElementById(`task-${ACTION_ID}`);
      if (!checkbox) {
        throw new Error('Checkbox element not found');
      }
      loadSavedState();
      checkbox.addEventListener('change', async (e) => {
        console.log('Checkbox changed:', e.target.checked, 'for action:', ACTION_ID);
        if (e.target.checked) {
          playCheckSound();
        }
        await updatePoints(e.target.checked);
      });
      console.log('Checkbox initialized successfully for action:', ACTION_ID);
    } catch (error) {
      console.error('Error initializing checkbox:', error);
      showToast('Failed to initialize checkbox', 'error');
    }
  }

  // Initialize when the DOM is loaded
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initialize);
  } else {
    initialize();
  }
})();
</script>
<!-- Copy everything above this line into your Circle.so post HTML field -->