Here is my current code.

(ns spelling-bee.core
  (:require 
   [re-frame.core         :as rf]
   [reagent.core          :as ra       :refer [with-let]]
   [reagent.dom           :as rdom]
   [stylefy.core          :as stylefy  :refer [use-style]]
   [stylefy.reagent       :as stylefy-reagent]
   [spelling-bee.events   :as events]
   [spelling-bee.elements :as elements :refer [db-get]]))

(def debug?
  ^boolean goog.DEBUG)

(set! *warn-on-infer* false)


;---------- main page renderer ----------

(defn main-panel []
  
  #_{:clj-kondo/ignore [:unresolved-symbol]}
  (with-let [database        (rf/subscribe [::events/dbdb])
             name            (db-get :name)
             game-started    (db-get :game-started)
             words           (db-get :words)
             found-words     (db-get :found-words)
             common-letter   (db-get :common-letter)
             letters         (db-get :letters)
             display-letters (db-get :display-letters)
             current-input   (db-get :current-input)
             message         (db-get :message)
             score           (db-get :score)
             shake-message?  (db-get :shake-message?)
             shake-angry?    (db-get :shake-angry?)] 
     
      [:div
       [:div {:class "main-style"}
        [:h1
         "Hello, " @name]
       ;[:p "debug: "@database]
        [:h3 {:class (str ""(when @shake-message? "shake") (when @shake-angry? "-angry"))} @message]
        [elements/message-component]
        [elements/spawn-words-button]
        (when @game-started 
          [:div {:class "main-container-style"}
           [:div {:class "main-panel-style"}
            [:div (use-style {:text-align "center"})
             [elements/styled-text-input]
             [elements/submit-button @current-input]]
            [:div {:class "letter-buttons-container"}
             [elements/letter-buttons-panel @display-letters (first @common-letter)]]
            [:div (use-style {:text-align "center"})
             [elements/shuffle-order-button @display-letters]
             [elements/backspace-button]]
            [:h3 "Your score: " @score]]

           [:div {:class "side-panel-style"}
            [:h3
             "Found words:"]
            [:ul (for [word (sort @found-words)]     ; sort found words into an alphabetical list
                   [:li word])]]]) 
        ]]))


;---------- page load parameters ----------

(defn dev-setup []
  (when debug?
    (println "dev mode")))

(defn ^:dev/after-load mount-root []
  (rf/clear-subscription-cache!)
  (let [root-el (.getElementById js/document "app")]
    (rdom/unmount-component-at-node root-el)
    (rdom/render [main-panel] root-el)))

(defn install-global-key-listeners []
  (.addEventListener js/window "keydown" (fn [e] (rf/dispatch [::events/key-press (.-key e)]))))

#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn init []
  (install-global-key-listeners)               ; listen for keypress events
  (rf/dispatch-sync [::events/initialize-db])         ; get re-frame atom initialized
  (stylefy/init {:dom (stylefy-reagent/init)}) ; set up css
  (dev-setup)
  (mount-root))


/* some unsorted css style */

.main-style {
    padding: 20px;
    width: 100%;
    max-width: 600px;
    margin: 0 auto;
    font-family: ‘Open Sans’, sans-serif;
    border: 3px solid #ccc;
    background-color: #F0FFFF;
  }
  
  .button-style {
    background-color: #4CAF50;
    border: none;
    color: white;
    border-color: #4CAF50;
    padding: 15px 32px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 22px;
    margin-top: 8px;
    margin-bottom: 8px;
    cursor: pointer;
    clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
    transition: transform 0.2s ease-in-out;
  }

  .button-style:hover {
    transform: scale(1.5);
  }

  .garbage-can-button {
    background: none;
    border: none;
    cursor: pointer;
    font-size: 30px;
    color:#ffc400;
    padding: 10px;
    transition: transform 0.2s ease-in-out;
  }
  .garbage-can-button:hover {
    transform: scale(1.5);
  }

  
  .input-style {
    color: transparent;
    font-size: 18px;
    padding: 10px;
    border: 4px solid #cfc;
    border-radius: 3px;
    margin-bottom: 5px;
    font-size: 22px;
    font-family: inherit;
    position: relative;
    justify-content: left;
    pointer-events: none;
  }

  .input-container {
    position: relative;
  }
  
  .styled-letters {
    position: absolute;
    font-size: 22px;
    margin-top: 14px;
    margin-left: 14px;
    top: 0;
    left: 0;
    text-align: center;
    pointer-events: none;
  }
  
  .side-panel-style {
    flex: 1;
    padding: 10px;
    margin-left: 20px;
    border: 3px solid #ccc;
    border-radius: 4px;
    background-color: #FFFFFF;
    max-width: 200px;
  }
  
  .main-panel-style {
    max-width: 280px;
  }
  
  .main-container-style {
    display: flex;
    justify-content: space-around;
  }
  
  .body-background {
    background-image: url('images/tokimemobgscroll.gif');
    background-repeat: repeat;
  }

/* animation */

  @keyframes shake {
    0% { transform: translate(0px, 0px); }
    25% { transform: translate(-5px, 0px); }
    50% { transform: translate(5px, 0px); }
    75% { transform: translate(-5px, 0px); }
    100% { transform: translate(0px, 0px); }
  }

  @keyframes angry-shake {
    0%, 100% { transform: translateX(0); }
    10%, 30%, 50%, 70%, 90% { transform: translateX(-10px); }
    20%, 40%, 60%, 80% { transform: translateX(10px); }
  }

  .shake {
    animation: shake 0.5s;
  }
  .shake-angry {
    animation: angry-shake 0.5s;
  }

  @keyframes slide-in {
    from {
      transform: translateX(100%);
      opacity: 0;
    }
    to {
      transform: translateX(0);
      opacity: 1;
    }
  }

  @keyframes fade-out {
    from {
      transform: translateX(0);
      opacity: 1;
    }
    to {
      transform: translateX(0);
      opacity: 0;
    }
  }
  
  .message-animation {
    animation: slide-in 0.5s ease-out, fade-out 1s ease-in 2.5s forwards; /* hold the message for 2.5 seconds before fading out /
    will-change: transform, opacity;  / Optimization for smoother animations */
  }

/* cool button styles */


  .hex-button {
    width: 60px;
    height: 70px;
    background-color: #ffc400;
    border: 0px;
    text-align: center;
    font-size: 24px;
    margin: 0px;
    cursor: pointer;
    clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
    transition: transform 0.2s ease-in-out;
  }
  
  .hex-button:hover {
    transform: scale(1.2);
  }

  .letter-buttons-panel {
    text-align: center;
    padding-top: 10px;
    padding-bottom: 10px;
  }
  
  .hex-row {
    display: flex;
    justify-content: center;
  }
  
  .hex-button {
    margin: 4px;

  }

  .top-row,
.bottom-row {
  margin-top: -16px;
  margin-bottom: -16px;
}


(ns spelling-bee.elements
  (:require
   [clojure.string      :as str]
   [re-frame.core       :as rf]
   [stylefy.core        :as stylefy :refer [use-style]]
   [spelling-bee.events :as events]
   [spelling-bee.logic  :as logic]
   [spelling-bee.words  :as words]))


;---------- shorthand ----------
(defn db-get [key]
  (rf/subscribe [::events/get key]))

;---------- stylefy components ----------
; px vs rem, google
(defn letter-style [letter-validation-sequence]
  (case letter-validation-sequence
    :required {:color "#4CAF50"}
    :valid    {:color "#000000"}
    :invalid  {:color "#AAAAAA" :opacity "0.5"}))



;---------- button elements ----------

(defn spawn-words-button
  "Starts the game with a preset set of words."
  []
  (let [game-started (db-get :game-started)]
    (when-not @game-started
      [:button
       {:on-click #(rf/dispatch  [::events/set-words-and-letters words/word-collection])
        :class "button-style"}
       "Get Letters!"])))

(defn submit-button
  [word]
  (let [input-value (db-get :current-input)]
    [:button
     {:on-click #(when (seq word)
                   (println "click!")
                   (rf/dispatch [::events/submit-word @input-value]))
      :class "button-style"}
     "Submit"]))

(defn shuffle-order-button
  "Shuffles the order of the letters displayed."
  [display-letters]
  [:button {:on-click #(rf/dispatch  [::events/shuffle-letter-order display-letters])
            :class "button-style"}
   "Shuffle letters"])

(defn backspace-button []
  [:button {:class "garbage-can-button"
            :on-click #(rf/dispatch [::events/delete-last-letter])}
   [:i {:class "fa fa-trash"}]])

(defn hex-button
  ([letter & [is-common]]
   [:button.hex-button {:on-click #(rf/dispatch [::events/append-current-input letter])
                        :style (when is-common {:font-weight "bold" :color "#4CAF50"})}
    letter]))



;---------- input elements ----------

(defn styled-letter [letter valid-letters common-letter]
  (let [letter-validation (logic/validate-letter letter valid-letters common-letter)]
    [:span (use-style (letter-style letter-validation)) letter]))

(defn styled-text-input []
  (let [input-value   (db-get :current-input)
        valid-letters (db-get :letters)
        common-letter (db-get :common-letter)]
    [:div.input-container

     [:input.input-style
      {:type         "text"
       :placeholder  (when (str/blank? @input-value) "Type here!")
       :value        @input-value
       :on-change    #(rf/dispatch [::events/get :set-current-input (-> % .-target .-value)])}]
     [:div.styled-letters
      (map-indexed
       (fn [_ ltr]
         [styled-letter ltr @valid-letters @common-letter])
       @input-value)]]))

(defn letter-buttons-panel [valid-letters common-letter]
  (let [top-row (take 2 valid-letters)
        middle-row (concat (take 1 (drop 2 valid-letters)) [common-letter] (take 1 (drop 3 valid-letters)))
        bottom-row (take 2 (drop 4 valid-letters))]
    [:div.letter-buttons-panel
     [:div.hex-row.top-row (map hex-button top-row)]
     ;; if index is 1 (the middle one), it passes letter and a truthy value into the argument so it can be detected.
     [:div.hex-row.middle-row (doall (map-indexed (fn [idx letter] (hex-button letter (and (= idx 1) (= letter common-letter)))) middle-row))]
     [:div.hex-row.bottom-row (map hex-button bottom-row)]]))



;---------- pop-in etc elements ----------

(defn message-component []
  (let [message         (db-get :message)
        showing-message (db-get :message-show)]
    (when @showing-message
      [:div.message-animation {:class (str "" (when @showing-message "message-animation"))}
       @message])))



(ns spelling-bee.events
  (:require 
   [re-frame.core      :as rf]
   [spelling-bee.logic :as logic]))


;---------- our app state atom ----------

(def default-db
  {:name              "player"
   :game-started      false
   :words             #{}
   :common-letter     #{}
   :letters           #{}
   :display-letters   []
   :found-words       #{}
   :current-input     ""
   :message           "Welcome to the Spelling Bee!"
   :message-show      false
   :score             0
   :shake-message     false
   :shake-angry       false})

;---------- handlers ----------

(defn global-key-handler [key db]
  (cond
    (re-matches #"[a-zA-Z]" key) [::append-current-input (str key)]
    (= key "Enter")              [::submit-word (:current-input db)]
    (= key "Backspace")          [::delete-last-letter]
    :else nil))

;---------- subscriptions to data from app state ----------

(rf/reg-sub ::get
  (fn [db [_ key]]
    (get db key)))


(rf/reg-sub ::dbdb
  (fn [db]
    db))


;---------- events ----------

(rf/reg-event-db ::initialize-db
  (fn [_ _]
    default-db))

(rf/reg-event-db ::set-words-and-letters
  (fn [db [_ word-set]]
    (let [common-letter (logic/find-common-letter word-set)
          letter-coll   (logic/get-unique-letter-collection word-set)]
      (assoc db :words word-set
             :common-letter   common-letter
             :letters         letter-coll
             :display-letters (shuffle (vec (remove common-letter letter-coll)))
             :game-started    true))))

(rf/reg-event-fx ::key-press
  (fn [{:keys [db]} [_ key]]
    (let [result (global-key-handler key db)]
      (when result
        {:dispatch result}))))

(rf/reg-event-db ::set-current-input
  (fn [db [_ input-value]]
    (assoc db :current-input input-value)))

(rf/reg-event-fx ::set-message
  (fn [db [_ message]]
    (do
      (js/setTimeout #(rf/dispatch [::reset-message]) 4000)
      (assoc db :message message :showing-message true))))

(rf/reg-event-db ::reset-message
  (fn [db _]
    (assoc db :shake-message false :shake-angry false)))

(rf/reg-event-db ::append-current-input
  (fn [db [_ input-value]]
    (if (> 20 (count (seq (:current-input db))))
      (update db :current-input str input-value)
      (assoc  db :message "Less letters please!"))))

(rf/reg-event-db ::delete-last-letter
  (fn [db _]
    (update db :current-input #(subs % 0 (max 0 (dec (count %)))))))

(rf/reg-event-db ::shuffle-letter-order
  (fn [db [_ display-letters]]
    (assoc db :display-letters (shuffle display-letters))))

(rf/reg-event-db ::reset-shake-message
  (fn [db _]
    (assoc db :shake-message false :shake-angry false)))


(rf/reg-event-db ::submit-word
  (fn [db [_ word]]
    (let [letters       (:letters       db)
          common-letter (:common-letter db)
          words         (:words         db)
          point-val     (logic/calculate-points word letters)
          submit        (partial assoc db :current-input "" :message)]
      (js/setTimeout #(rf/dispatch [::reset-shake-message]) 500) ; preemptively set a timeout to kill any shaking 
      (case (logic/validate-word word words letters common-letter)
        :submit-ok   (if (contains? (:found-words db) word)
                       (submit "You've already found that word!")
                       (-> db
                           (update :found-words conj word)
                           (update :score + point-val)
                           (assoc  :current-input "" :message (str "Great job! You found " word ", worth a score of " point-val "!")))) ; add the valid word to found words
        :too-short   (submit "Only words with 4 letters or more count.")
        :not-in-list (submit (str "Sorry, " word " isn't in the word list today."))
        :no-common   (submit "Nice try, but the word needs to contain the common letter." :shake-message true)
        :invalid     (submit "All letters in the word must be from the given letter set." :shake-message true :shake-angry true)
        :other       (submit "Try again.")))))
; use reg-event-fx to dispatch further event to clear input



(ns spelling-bee.logic
  (:require
   [clojure.set         :as set]
   [clojure.string      :as str]))




;---------- various functions ----------

(defn get-unique-letter-collection [word-set]
  (-> word-set
      vec
      str/join
      seq
      set))

(defn find-common-letter [word-set]
  (reduce
   set/intersection
   (map set (seq word-set))))

(defn validate-word
  "Checks the given word against the current word list and letter set to see if it is valid. Gives the following keywords as a result.
   
   :submit-ok :too-short :invalid :no-common :not-in-list :other"
  [word word-list letters common-letter]
  (cond
    (contains? word-list word)                         :submit-ok       ; first check if the word is in the word collection
    (> 4 (count (seq word)))                           :too-short       ; check length, notify if less than 3 letters
    (not (every? letters (set word)))                  :invalid         ; check if every letter in the word is in letters set 
    (not (contains? (set word) (first common-letter))) :no-common       ; if it does not contain the common letter
    (contains? (set word) (first common-letter))       :not-in-list     ; then check if the word at least contains common letter 
    :else                                              :other))         ; generic if it somehow manages to not match one of the above

(defn validate-letter [letter letters common-letter]
  (cond
    (= letter (str (first common-letter))) :required
    (contains? (set letters) letter) :valid
    :else                            :invalid))

(defn calculate-points [word letters]
  (cond
    (= (get-unique-letter-collection word) (set letters)) (+ (count (seq word)) 7)
    (= (count (seq word)) 4) (int 1)
    :else (count (seq word))))


Currently, the pop-up message component doesn't appear.