JSFiddle - React, Tailwind, and code Playground

HTML

<table>
    <tr>
        <td>
            <textarea class='comment-box' rows="4" cols="60"></textarea>
        </td>
        <td valign="top">
            <button class='add-comment'>Add Comment</button>
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <div class="comment-status"></div>
        </td>
    </tr>
    <tr class="popup-container">
        <td colspan="2">
            <div class="popup"><h1>a</h1><div class="close">Click this box to close the message.</div></div>
        </td>
    </tr>
</table>

<div id="comments">
</div>

CSS

body {
    font-family: Helvetica, Arial;
    font-size: 13px;
}
button {
    font-size: 0.9em;
    margin: 0 10px;
    padding: 5px;
    background-color: #CCC;
    border: 1px solid #AAA;
}
.comment-status {
    margin: 3px 0;
    color: #999;
}
.mild {
    color: #999;
    color: #9B764F;
}
.spicy {
    color: #CF7721;
}
.hot {
   color: #FE7A15
}
.sizzling {
    color: red;
}
.popup {
    color: white;
    padding: 5px;
    background-color: #FE7A15;
}
.popup h1 {
    font-size: 1.3em;
}
.popup div.close {
    margin: 3px 0;
    font-size: 0.9em;
}
.popup-container {
    cursor: pointer;
    display: none;
}
.comment {
    color: #666;
    margin: 10px 0;
}

JavaScript

/**
 * Stores the timestamp when the last comment was submitted
 */
var lastCommentTimestamp = null;

/**
 * Handle comment change.
 * Update character count. 
 * Indicate progress
 */
function handleCommentUpdate(comment) {
    var status = $('.comment-status');

    status.text(getStatusText(comment));
    status.removeClass('mild spicy hot sizzling');
    status.addClass(getStatusClass(comment));
}

/**
 * Is the comment valid for submission
 */
function commentSubmittable(comment) {
    var notTooSoon = !isTooSoon();
    var notEmpty = !isEmpty(comment);
    var hasEnoughCharacters = !isTooShort(comment);

    return notTooSoon && notEmpty && hasEnoughCharacters;
}
    
/**
 * Was last comment submitted less than 15 seconds ago?
 */
function isTooSoon() {
    if(lastCommentTimestamp == null) {
        return false;
    }
    var minDelay = 15 * 1000; // milliseconds
    return (Date.now() - lastCommentTimestamp) < minDelay;
}

/**
 * Seriously? Comment for this?
 */
function isEmpty(comment) {
    return comment == '';
}

/**
 * Shouldn't be less than 15 characters
 */
function isTooShort(comment) {
    return comment.replace(/\s+/g, " ").length < 15;    
}

/**
 * Map comment size to class names
 */
function getStatusClass(comment) {
    var max = 600;
    var remaining = max - comment.length;
    if(remaining < 0) return 'sizzling';
    if(remaining < 120) return 'hot';
    if(remaining < 250) return 'spicy';
    if(remaining < 350) return 'mild';
    return '';
}

/**
 * Comment progress message
 */
function getStatusText(comment) {
    if(comment == '') {
        return 'enter at least 15 characters';
    }

    if(comment.length < 15) {
        var remaining = 15 - comment.length;
        return remaining + ' more to go..';
    }

    var remaining = 585 - comment.length;
    return remaining + ' characters left';
}

function popup(message) {
    $('.popup-container').fadeIn();
    $('.popup h1').text(message);
}

// update comment progress...