Tailwind CSS Integration

Integrate SpamBlock with Tailwind CSS forms and create beautiful status messages with utility classes

Overview

This guide demonstrates how to integrate SpamBlock with Tailwind CSS forms, including styled success/error messages, loading states, and smooth animations. Tailwind's utility classes make it easy to create professional form feedback.

Prerequisites

  • Tailwind CSS configured in your project
  • Basic knowledge of Tailwind utility classes
  • SpamBlock pixel script included

Setup

1. Include SpamBlock Pixel

Add the SpamBlock script to your page:

<script src="https://api.spamblock.io/sdk/pixel/v1.js defer"></script>

2. Basic Tailwind Form

Here's a contact form with Tailwind styling:

<form 
  data-block-spam 
  action="/api/contact" 
  method="post" 
  id="contact-form"
  class="max-w-2xl mx-auto space-y-6">
  
  <div>
    <label for="email" class="block text-sm font-medium text-slate-700 mb-2">
      Email Address
    </label>
    <input 
      type="email" 
      id="email" 
      name="email" 
      required
      placeholder="[email protected]"
      class="w-full px-4 py-2 border border-slate-300 rounded-lg shadow-sm 
             focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
             transition-colors">
  </div>

  <div>
    <label for="message" class="block text-sm font-medium text-slate-700 mb-2">
      Message
    </label>
    <textarea 
      id="message" 
      name="message" 
      rows="5" 
      required
      placeholder="Your message here..."
      class="w-full px-4 py-2 border border-slate-300 rounded-lg shadow-sm 
             focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
             transition-colors resize-none"></textarea>
  </div>

  <button 
    type="submit" 
    id="submit-btn"
    class="w-full bg-blue-600 text-white py-3 px-6 rounded-lg 
           hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 
           focus:ring-offset-2 font-medium transition-colors disabled:opacity-50 
           disabled:cursor-not-allowed flex items-center justify-center gap-2">
    <svg id="spinner" class="hidden animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
      <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
      <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
    </svg>
    <span id="btn-text">Send Message</span>
  </button>

  <!-- Status messages (hidden by default) -->
  <div id="success-message" class="hidden rounded-lg border border-green-300 bg-green-50 px-4 py-3 text-sm text-green-700">
    <div class="flex items-center gap-2">
      <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
        <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
      </svg>
      <span id="success-text"></span>
    </div>
  </div>

  <div id="error-message" class="hidden rounded-lg border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700">
    <div class="flex items-center gap-2">
      <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
        <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
      </svg>
      <span id="error-text"></span>
    </div>
  </div>
</form>

3. Event Handling with Tailwind Styling

Add this JavaScript to handle SpamBlock events and show Tailwind-styled feedback:

document.addEventListener('DOMContentLoaded', () => {
  const form = document.getElementById('contact-form');
  const submitBtn = document.getElementById('submit-btn');
  const btnText = document.getElementById('btn-text');
  const spinner = document.getElementById('spinner');
  const successAlert = document.getElementById('success-message');
  const successText = document.getElementById('success-text');
  const errorAlert = document.getElementById('error-message');
  const errorText = document.getElementById('error-text');

  // Hide all alerts
  const hideAlerts = () => {
    successAlert.classList.add('hidden');
    errorAlert.classList.add('hidden');
  };

  // Show/hide loading state
  const setLoading = (loading) => {
    if (loading) {
      submitBtn.disabled = true;
      spinner.classList.remove('hidden');
      btnText.textContent = 'Sending...';
    } else {
      submitBtn.disabled = false;
      spinner.classList.add('hidden');
      btnText.textContent = 'Send Message';
    }
  };

  // Handle successful submission
  form.addEventListener('spamblock:allowed', (event) => {
    setLoading(false);
    hideAlerts();
    
    const response = event.detail.response;
    
    // Show success message if submission was allowed
    if (response && response.allow === true && response.score < 60) {
      successText.textContent = 'Thank you! Your message has been sent successfully.';
      successAlert.classList.remove('hidden');
      
      // Add fade-in animation
      successAlert.classList.add('animate-fade-in');
      
      // Reset form after successful submission
      form.reset();
      
      // Hide success message after 5 seconds
      setTimeout(() => {
        successAlert.classList.add('hidden');
        successAlert.classList.remove('animate-fade-in');
      }, 5000);
    }
  });

  // Handle blocked submission
  form.addEventListener('spamblock:blocked', (event) => {
    setLoading(false);
    hideAlerts();
    
    const response = event.detail.response;
    const reasons = response?.reasons || [];
    
    // Build error message from reasons
    let errorMsg = 'Your submission was blocked. ';
    
    if (reasons.includes('disposable_domain')) {
      errorMsg += 'Please use a valid email address (disposable domains are not allowed).';
    } else if (reasons.includes('profanity_detected')) {
      errorMsg += 'Please remove inappropriate language from your message.';
    } else if (reasons.includes('honeypot_filled')) {
      errorMsg += 'Automated submission detected.';
    } else {
      errorMsg += `Spam detected (score: ${response?.score || 0}/60).`;
    }
    
    errorText.textContent = errorMsg;
    errorAlert.classList.remove('hidden');
    errorAlert.classList.add('animate-fade-in');
  });

  // Show loading state when form is submitted
  form.addEventListener('submit', () => {
    hideAlerts();
    setLoading(true);
  });
});

Advanced Examples

With Tailwind Animations

Add custom animations to your tailwind.config.js:

module.exports = {
  theme: {
    extend: {
      keyframes: {
        'fade-in': {
          '0%': { opacity: '0', transform: 'translateY(-10px)' },
          '100%': { opacity: '1', transform: 'translateY(0)' },
        },
        'slide-in': {
          '0%': { transform: 'translateX(-100%)' },
          '100%': { transform: 'translateX(0)' },
        },
      },
      animation: {
        'fade-in': 'fade-in 0.3s ease-out',
        'slide-in': 'slide-in 0.3s ease-out',
      },
    },
  },
}

Then use them in your HTML:

<div id="success-message" class="hidden rounded-lg border border-green-300 bg-green-50 px-4 py-3 text-sm text-green-700 animate-fade-in">
  <!-- ... -->
</div>

Toast-Style Notifications

Create toast notifications that slide in from the top:

<!-- Toast Container (fixed position) -->
<div id="toast-container" class="fixed top-4 right-4 z-50 space-y-2">
  <!-- Toasts will be inserted here -->
</div>
const showToast = (message, type = 'success') => {
  const container = document.getElementById('toast-container');
  const toastId = `toast-${Date.now()}`;
  
  const bgColor = type === 'success' ? 'bg-green-500' : 'bg-red-500';
  const icon = type === 'success' 
    ? '<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>'
    : '<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>';
  
  const toast = document.createElement('div');
  toast.id = toastId;
  toast.className = `${bgColor} text-white px-6 py-4 rounded-lg shadow-lg flex items-center gap-3 animate-slide-in min-w-[300px]`;
  toast.innerHTML = `
    <svg class="h-5 w-5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
      ${icon}
    </svg>
    <span class="flex-1">${message}</span>
    <button onclick="document.getElementById('${toastId}').remove()" class="text-white hover:text-gray-200">
      <svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
        <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
      </svg>
    </button>
  `;
  
  container.appendChild(toast);
  
  // Auto-remove after 5 seconds
  setTimeout(() => {
    toast.classList.add('animate-fade-out');
    setTimeout(() => toast.remove(), 300);
  }, 5000);
};

// Use in event handlers
form.addEventListener('spamblock:allowed', (event) => {
  setLoading(false);
  showToast('✅ Message sent successfully!', 'success');
  form.reset();
});

form.addEventListener('spamblock:blocked', (event) => {
  setLoading(false);
  const response = event.detail.response;
  showToast(`❌ Submission blocked: ${response?.reasons?.join(', ') || 'Spam detected'}`, 'error');
});

JavaScript-Based Submission

For SPAs or when you need custom submission handling, intercept the spamblock:allowed event to handle submission via fetch():

<form data-block-spam id="contact-form" class="max-w-2xl mx-auto space-y-6">
  <!-- Form fields -->
  <button type="submit" id="submit-btn" class="w-full bg-blue-600 text-white py-3 px-6 rounded-lg">
    <svg id="spinner" class="hidden animate-spin h-5 w-5 text-white inline mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
      <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
      <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
    </svg>
    <span id="btn-text">Send Message</span>
  </button>
  <div id="success-message" class="hidden rounded-lg border border-green-300 bg-green-50 px-4 py-3 text-sm text-green-700"></div>
  <div id="error-message" class="hidden rounded-lg border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700"></div>
</form>

<script>
document.addEventListener('DOMContentLoaded', () => {
  const form = document.getElementById('contact-form');
  const submitBtn = document.getElementById('submit-btn');
  const btnText = document.getElementById('btn-text');
  const spinner = document.getElementById('spinner');
  const successAlert = document.getElementById('success-message');
  const errorAlert = document.getElementById('error-message');

  const setLoading = (loading) => {
    if (loading) {
      submitBtn.disabled = true;
      spinner.classList.remove('hidden');
      btnText.textContent = 'Sending...';
    } else {
      submitBtn.disabled = false;
      spinner.classList.add('hidden');
      btnText.textContent = 'Send Message';
    }
  };

  // Prevent native submission by intercepting submit event in capture phase
  form.addEventListener('submit', (event) => {
    // This runs before SpamBlock's handler, preventing native submission
    event.preventDefault();
  }, { capture: true });

  // Intercept allowed event to handle submission manually
  form.addEventListener('spamblock:allowed', async (event) => {
    setLoading(true);
    errorAlert.classList.add('hidden');
    
    try {
      // Submit form data via fetch
      const formData = new FormData(form);
      const response = await fetch('/api/contact', {
        method: 'POST',
        body: formData
      });
      
      if (response.ok) {
        successAlert.textContent = '✅ Thank you! Your message has been sent successfully.';
        successAlert.classList.remove('hidden');
        form.reset();
        setTimeout(() => successAlert.classList.add('hidden'), 5000);
      } else {
        throw new Error('Submission failed');
      }
    } catch (error) {
      errorAlert.textContent = '❌ Error sending message. Please try again.';
      errorAlert.classList.remove('hidden');
    } finally {
      setLoading(false);
    }
  });

  // Handle blocked submissions
  form.addEventListener('spamblock:blocked', (event) => {
    setLoading(false);
    const response = event.detail.response;
    const reasons = response?.reasons || [];
    
    let errorMsg = '❌ Your submission was blocked. ';
    if (reasons.includes('disposable_domain')) {
      errorMsg += 'Please use a valid email address.';
    } else if (reasons.includes('profanity_detected')) {
      errorMsg += 'Please remove inappropriate language.';
    } else {
      errorMsg += `Spam detected (score: ${response?.score || 0}).`;
    }
    
    errorAlert.textContent = errorMsg;
    errorAlert.classList.remove('hidden');
  });

  // Show loading state when form is submitted
  form.addEventListener('submit', () => {
    setLoading(true);
  });
});
</script>

Key points:

  • Intercept the submit event in the capture phase to prevent native submission
  • Use FormData to collect form data
  • Handle success/error states with Tailwind-styled alerts
  • Form stays on the same page (no navigation)

Field-Level Validation Feedback

Show Tailwind-styled validation errors on specific fields:

form.addEventListener('spamblock:blocked', (event) => {
  const response = event.detail.response;
  const reasons = response?.reasons || [];
  
  // Highlight email field if disposable domain detected
  if (reasons.includes('disposable_domain')) {
    const emailInput = form.querySelector('[name="email"]');
    emailInput.classList.remove('border-slate-300', 'focus:border-blue-500');
    emailInput.classList.add('border-red-500', 'focus:border-red-500', 'focus:ring-red-500');
    
    // Show inline error message
    let errorMsg = emailInput.parentElement.querySelector('.error-message');
    if (!errorMsg) {
      errorMsg = document.createElement('p');
      errorMsg.className = 'mt-1 text-sm text-red-600 error-message';
      emailInput.parentElement.appendChild(errorMsg);
    }
    errorMsg.textContent = 'Please use a valid, non-disposable email address.';
  }
  
  // Highlight message field if profanity detected
  if (reasons.includes('profanity_detected')) {
    const messageInput = form.querySelector('[name="message"]');
    messageInput.classList.remove('border-slate-300', 'focus:border-blue-500');
    messageInput.classList.add('border-red-500', 'focus:border-red-500', 'focus:ring-red-500');
    
    let errorMsg = messageInput.parentElement.querySelector('.error-message');
    if (!errorMsg) {
      errorMsg = document.createElement('p');
      errorMsg.className = 'mt-1 text-sm text-red-600 error-message';
      messageInput.parentElement.appendChild(errorMsg);
    }
    errorMsg.textContent = 'Please remove inappropriate language from your message.';
  }
});

Gradient Submit Button with Loading State

<button 
  type="submit" 
  id="submit-btn"
  class="w-full bg-gradient-to-r from-blue-600 to-indigo-600 text-white py-3 px-6 rounded-lg 
         hover:from-blue-700 hover:to-indigo-700 focus:outline-none focus:ring-2 
         focus:ring-blue-500 focus:ring-offset-2 font-medium transition-all duration-200 
         disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2
         shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 disabled:transform-none">
  <svg id="spinner" class="hidden animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
    <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
    <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
  </svg>
  <span id="btn-text">Send Message</span>
</button>

Complete Example

Here's a complete, production-ready Tailwind form with SpamBlock:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Contact Form - SpamBlock + Tailwind</title>
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-50 min-h-screen py-12">
  <div class="max-w-2xl mx-auto px-4">
    <div class="bg-white rounded-xl shadow-lg p-8">
      <h1 class="text-3xl font-bold text-slate-900 mb-2">Contact Us</h1>
      <p class="text-slate-600 mb-8">Send us a message and we'll get back to you soon.</p>
      
      <form 
        data-block-spam 
        action="/api/contact" 
        method="post" 
        id="contact-form"
        class="space-y-6">
        
        <div>
          <label for="name" class="block text-sm font-medium text-slate-700 mb-2">
            Name
          </label>
          <input 
            type="text" 
            id="name" 
            name="name" 
            required
            class="w-full px-4 py-2 border border-slate-300 rounded-lg shadow-sm 
                   focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
                   transition-colors">
        </div>

        <div>
          <label for="email" class="block text-sm font-medium text-slate-700 mb-2">
            Email Address
          </label>
          <input 
            type="email" 
            id="email" 
            name="email" 
            required
            placeholder="[email protected]"
            class="w-full px-4 py-2 border border-slate-300 rounded-lg shadow-sm 
                   focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
                   transition-colors">
        </div>

        <div>
          <label for="message" class="block text-sm font-medium text-slate-700 mb-2">
            Message
          </label>
          <textarea 
            id="message" 
            name="message" 
            rows="5" 
            required
            placeholder="Your message here..."
            class="w-full px-4 py-2 border border-slate-300 rounded-lg shadow-sm 
                   focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
                   transition-colors resize-none"></textarea>
        </div>

        <button 
          type="submit" 
          id="submit-btn"
          class="w-full bg-blue-600 text-white py-3 px-6 rounded-lg 
                 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 
                 focus:ring-offset-2 font-medium transition-colors disabled:opacity-50 
                 disabled:cursor-not-allowed flex items-center justify-center gap-2">
          <svg id="spinner" class="hidden animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
            <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
            <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
          </svg>
          <span id="btn-text">Send Message</span>
        </button>

        <div id="success-message" class="hidden rounded-lg border border-green-300 bg-green-50 px-4 py-3 text-sm text-green-700">
          <div class="flex items-center gap-2">
            <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
              <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
            </svg>
            <span id="success-text"></span>
          </div>
        </div>

        <div id="error-message" class="hidden rounded-lg border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700">
          <div class="flex items-center gap-2">
            <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
              <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
            </svg>
            <span id="error-text"></span>
          </div>
        </div>
      </form>
    </div>
  </div>

  <script src="https://api.spamblock.io/sdk/pixel/v1.js defer"></script>
  <script>
    // Event handling code from above
    document.addEventListener('DOMContentLoaded', () => {
      const form = document.getElementById('contact-form');
      const submitBtn = document.getElementById('submit-btn');
      const btnText = document.getElementById('btn-text');
      const spinner = document.getElementById('spinner');
      const successAlert = document.getElementById('success-message');
      const successText = document.getElementById('success-text');
      const errorAlert = document.getElementById('error-message');
      const errorText = document.getElementById('error-text');

      const hideAlerts = () => {
        successAlert.classList.add('hidden');
        errorAlert.classList.add('hidden');
      };

      const setLoading = (loading) => {
        if (loading) {
          submitBtn.disabled = true;
          spinner.classList.remove('hidden');
          btnText.textContent = 'Sending...';
        } else {
          submitBtn.disabled = false;
          spinner.classList.add('hidden');
          btnText.textContent = 'Send Message';
        }
      };

      form.addEventListener('spamblock:allowed', (event) => {
        setLoading(false);
        hideAlerts();
        const response = event.detail.response;
        if (response && response.allow === true) {
          successText.textContent = 'Thank you! Your message has been sent successfully.';
          successAlert.classList.remove('hidden');
          form.reset();
          setTimeout(() => successAlert.classList.add('hidden'), 5000);
        }
      });

      form.addEventListener('spamblock:blocked', (event) => {
        setLoading(false);
        hideAlerts();
        const response = event.detail.response;
        const reasons = response?.reasons || [];
        let errorMsg = 'Your submission was blocked. ';
        if (reasons.includes('disposable_domain')) {
          errorMsg += 'Please use a valid email address.';
        } else if (reasons.includes('profanity_detected')) {
          errorMsg += 'Please remove inappropriate language.';
        } else {
          errorMsg += `Spam detected (score: ${response?.score || 0}).`;
        }
        errorText.textContent = errorMsg;
        errorAlert.classList.remove('hidden');
      });

      form.addEventListener('submit', () => {
        hideAlerts();
        setLoading(true);
      });
    });
  </script>
</body>
</html>

Best Practices

  1. Use Tailwind's state variants: Leverage hover:, focus:, disabled:, and group-* for interactive states
  2. Consistent spacing: Use Tailwind's spacing scale (space-y-6, gap-2, etc.) for consistent layouts
  3. Smooth transitions: Add transition-colors or transition-all for smooth state changes
  4. Accessible colors: Ensure sufficient contrast ratios for text on colored backgrounds
  5. Responsive design: Use Tailwind's responsive prefixes (sm:, md:, lg:) for mobile-first design

Troubleshooting

Issue Solution
Styles not applying Ensure Tailwind CSS is properly configured and compiled
Animations not working Add custom animations to tailwind.config.js or use Tailwind's built-in animations
Hidden classes not working Check that hidden class isn't being overridden by other styles
Loading spinner not visible Ensure SVG has proper dimensions and animate-spin class is applied