Spaces:
Sleeping
Sleeping
Query length warning
Browse files- app.py +98 -30
- model_params.cfg +1 -1
- style.css +29 -10
app.py
CHANGED
|
@@ -626,7 +626,7 @@ with gr.Blocks(title="Audit Q&A", css= "style.css", theme=theme,elem_id = "main-
|
|
| 626 |
pending_query = gr.State(None)
|
| 627 |
|
| 628 |
# Warning UI is implemented as a hidden row (to keep it simple)
|
| 629 |
-
with gr.Row(visible=False, elem_id="warning-row") as warning_row:
|
| 630 |
with gr.Column():
|
| 631 |
gr.Markdown("<span class='warning-icon'>⚠️</span> **No report filter selected. Are you sure you want to proceed?**")
|
| 632 |
with gr.Row(elem_classes="warning-buttons"):
|
|
@@ -642,29 +642,41 @@ with gr.Blocks(title="Audit Q&A", css= "style.css", theme=theme,elem_id = "main-
|
|
| 642 |
return gr.update(visible=False)
|
| 643 |
|
| 644 |
# Logic needs to be changed to accomodate default filter values (currently I have them all set to None)
|
| 645 |
-
def check_filters(textbox_value, sources, reports, subtype, year):
|
| 646 |
"""Check if any filters are selected"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 647 |
no_filters = (not reports) and (not sources) and (not subtype) and (not year)
|
| 648 |
if no_filters:
|
| 649 |
-
# If no filters, show warning and
|
| 650 |
return (
|
|
|
|
| 651 |
True, # warning state
|
| 652 |
gr.update(visible=True), # warning row visibility
|
| 653 |
-
|
| 654 |
textbox_value # store the query
|
| 655 |
)
|
| 656 |
# If filters exist, proceed normally
|
| 657 |
return (
|
| 658 |
-
|
| 659 |
-
|
|
|
|
| 660 |
textbox_value, # keep the original value
|
| 661 |
None # no need to store query
|
| 662 |
)
|
| 663 |
|
| 664 |
-
async def handle_chat_flow(warning_active, query, chatbot, sources, reports, subtype, year, client_ip, session_id):
|
| 665 |
-
"""Handle chat flow with
|
| 666 |
-
if
|
| 667 |
-
|
| 668 |
yield (
|
| 669 |
chatbot, # unchanged chatbot
|
| 670 |
"", # empty sources
|
|
@@ -680,16 +692,56 @@ with gr.Blocks(title="Audit Q&A", css= "style.css", theme=theme,elem_id = "main-
|
|
| 680 |
# Proceed with chat and yield each update
|
| 681 |
async for update in chat(query, history, sources, reports, subtype, year, client_ip, session_id):
|
| 682 |
yield update
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 683 |
|
| 684 |
#-------------------- Gradio Handlers -------------------------
|
| 685 |
|
| 686 |
# Hanlders: Text input from Textbox
|
| 687 |
(textbox
|
| 688 |
.submit(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
check_filters,
|
| 690 |
-
[textbox, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year],
|
| 691 |
-
[warning_state, warning_row, textbox, pending_query],
|
| 692 |
-
api_name="submit_textbox"
|
|
|
|
| 693 |
)
|
| 694 |
.then(
|
| 695 |
get_client_ip_handler,
|
|
@@ -700,7 +752,7 @@ with gr.Blocks(title="Audit Q&A", css= "style.css", theme=theme,elem_id = "main-
|
|
| 700 |
)
|
| 701 |
.then(
|
| 702 |
handle_chat_flow,
|
| 703 |
-
[warning_state, textbox, chatbot, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year, client_ip, session_id],
|
| 704 |
[chatbot, sources_textbox, feedback_state, session_id],
|
| 705 |
queue=True,
|
| 706 |
api_name="handle_chat_flow_textbox"
|
|
@@ -720,15 +772,21 @@ with gr.Blocks(title="Audit Q&A", css= "style.css", theme=theme,elem_id = "main-
|
|
| 720 |
|
| 721 |
# Hanlders: Text input from Examples (same chain as textbox)
|
| 722 |
examples_hidden.change(
|
| 723 |
-
lambda x: x,
|
| 724 |
inputs=examples_hidden,
|
| 725 |
outputs=textbox,
|
| 726 |
api_name="submit_examples"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
).then(
|
| 728 |
check_filters,
|
| 729 |
-
[textbox, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year],
|
| 730 |
-
[warning_state, warning_row, textbox, pending_query],
|
| 731 |
-
api_name="check_filters_examples"
|
|
|
|
| 732 |
).then(
|
| 733 |
get_client_ip_handler,
|
| 734 |
[textbox],
|
|
@@ -737,7 +795,7 @@ with gr.Blocks(title="Audit Q&A", css= "style.css", theme=theme,elem_id = "main-
|
|
| 737 |
api_name="get_client_ip_examples"
|
| 738 |
).then(
|
| 739 |
handle_chat_flow,
|
| 740 |
-
[warning_state, textbox, chatbot, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year, client_ip, session_id],
|
| 741 |
[chatbot, sources_textbox, feedback_state, session_id],
|
| 742 |
queue=True,
|
| 743 |
api_name="handle_chat_flow_examples"
|
|
@@ -757,24 +815,21 @@ with gr.Blocks(title="Audit Q&A", css= "style.css", theme=theme,elem_id = "main-
|
|
| 757 |
# Handlers for the warning buttons
|
| 758 |
proceed_btn.click(
|
| 759 |
lambda query: (
|
|
|
|
| 760 |
False, # warning state
|
| 761 |
gr.update(visible=False), # warning row
|
| 762 |
gr.update(value=query if query else "", interactive=True), # restore query
|
| 763 |
None # clear pending query
|
| 764 |
),
|
| 765 |
pending_query,
|
| 766 |
-
[warning_state, warning_row, textbox, pending_query]
|
| 767 |
-
).then(
|
| 768 |
-
lambda: False,
|
| 769 |
-
None,
|
| 770 |
-
warning_state
|
| 771 |
).then(
|
| 772 |
get_client_ip_handler,
|
| 773 |
[textbox],
|
| 774 |
[client_ip]
|
| 775 |
).then(
|
| 776 |
handle_chat_flow,
|
| 777 |
-
[warning_state, textbox, chatbot, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year, client_ip, session_id],
|
| 778 |
[chatbot, sources_textbox, feedback_state, session_id],
|
| 779 |
queue=True
|
| 780 |
).then(
|
|
@@ -789,15 +844,28 @@ with gr.Blocks(title="Audit Q&A", css= "style.css", theme=theme,elem_id = "main-
|
|
| 789 |
|
| 790 |
cancel_btn.click(
|
| 791 |
lambda: (
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
gr.update(
|
| 795 |
-
|
|
|
|
| 796 |
),
|
| 797 |
None,
|
| 798 |
-
[warning_state, warning_row, textbox, pending_query]
|
| 799 |
)
|
| 800 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 801 |
|
| 802 |
demo.queue()
|
| 803 |
|
|
|
|
| 626 |
pending_query = gr.State(None)
|
| 627 |
|
| 628 |
# Warning UI is implemented as a hidden row (to keep it simple)
|
| 629 |
+
with gr.Row(visible=False, elem_id="warning-row", elem_classes="warning-message") as warning_row:
|
| 630 |
with gr.Column():
|
| 631 |
gr.Markdown("<span class='warning-icon'>⚠️</span> **No report filter selected. Are you sure you want to proceed?**")
|
| 632 |
with gr.Row(elem_classes="warning-buttons"):
|
|
|
|
| 642 |
return gr.update(visible=False)
|
| 643 |
|
| 644 |
# Logic needs to be changed to accomodate default filter values (currently I have them all set to None)
|
| 645 |
+
def check_filters(check_status, textbox_value, sources, reports, subtype, year):
|
| 646 |
"""Check if any filters are selected"""
|
| 647 |
+
# If a previous check failed, don't continue with this check
|
| 648 |
+
if check_status is not None:
|
| 649 |
+
return (
|
| 650 |
+
check_status, # keep current check status
|
| 651 |
+
False, # keep warning state unchanged
|
| 652 |
+
gr.update(visible=False), # keep warning row visibility unchanged
|
| 653 |
+
textbox_value, # keep the textbox value
|
| 654 |
+
None # no need to store query
|
| 655 |
+
)
|
| 656 |
+
|
| 657 |
no_filters = (not reports) and (not sources) and (not subtype) and (not year)
|
| 658 |
if no_filters:
|
| 659 |
+
# If no filters, show warning and set status
|
| 660 |
return (
|
| 661 |
+
"filter", # check status - no filters selected
|
| 662 |
True, # warning state
|
| 663 |
gr.update(visible=True), # warning row visibility
|
| 664 |
+
gr.update(value=""), # clear textbox
|
| 665 |
textbox_value # store the query
|
| 666 |
)
|
| 667 |
# If filters exist, proceed normally
|
| 668 |
return (
|
| 669 |
+
None, # no check failed
|
| 670 |
+
False, # normal state
|
| 671 |
+
gr.update(visible=False), # hide warning
|
| 672 |
textbox_value, # keep the original value
|
| 673 |
None # no need to store query
|
| 674 |
)
|
| 675 |
|
| 676 |
+
async def handle_chat_flow(check_status, warning_active, short_query_warning_active, query, chatbot, sources, reports, subtype, year, client_ip, session_id):
|
| 677 |
+
"""Handle chat flow with explicit check for status"""
|
| 678 |
+
# Don't proceed if any check failed or query is None
|
| 679 |
+
if check_status is not None or warning_active or short_query_warning_active or query is None or query == "":
|
| 680 |
yield (
|
| 681 |
chatbot, # unchanged chatbot
|
| 682 |
"", # empty sources
|
|
|
|
| 692 |
# Proceed with chat and yield each update
|
| 693 |
async for update in chat(query, history, sources, reports, subtype, year, client_ip, session_id):
|
| 694 |
yield update
|
| 695 |
+
|
| 696 |
+
#-------------------- Short Query Warning -------------------------
|
| 697 |
+
# Warn users when query is too short (less than 4 words)
|
| 698 |
+
short_query_warning_state = gr.State(False)
|
| 699 |
+
check_status = gr.State(None)
|
| 700 |
+
|
| 701 |
+
# Short query warning UI - using exact same structure as filters warning
|
| 702 |
+
with gr.Row(visible=False, elem_id="warning-row", elem_classes="warning-message") as short_query_warning_row:
|
| 703 |
+
with gr.Column():
|
| 704 |
+
gr.Markdown("<span class='warning-icon'>⚠️</span> **Your query is too short. Please lengthen your query to ensure the app has adequate context.**")
|
| 705 |
+
with gr.Row(elem_classes="warning-buttons"):
|
| 706 |
+
short_query_proceed_btn = gr.Button("OK", elem_classes="proceed")
|
| 707 |
+
|
| 708 |
+
def check_query_length(textbox_value):
|
| 709 |
+
"""Check if query has at least 4 words"""
|
| 710 |
+
if textbox_value and len(textbox_value.split()) < 4:
|
| 711 |
+
# If query is too short, show warning and set status
|
| 712 |
+
return (
|
| 713 |
+
"short", # check status - this query is too short
|
| 714 |
+
True, # short query warning state
|
| 715 |
+
gr.update(visible=True), # short query warning row visibility
|
| 716 |
+
gr.update(value=""), # clear textbox
|
| 717 |
+
textbox_value # store the query
|
| 718 |
+
)
|
| 719 |
+
# If query is long enough, proceed normally
|
| 720 |
+
return (
|
| 721 |
+
None, # no check failed
|
| 722 |
+
False, # normal state
|
| 723 |
+
gr.update(visible=False), # hide warning
|
| 724 |
+
gr.update(value=textbox_value), # keep the textbox value
|
| 725 |
+
None # no need to store query
|
| 726 |
+
)
|
| 727 |
+
|
| 728 |
|
| 729 |
#-------------------- Gradio Handlers -------------------------
|
| 730 |
|
| 731 |
# Hanlders: Text input from Textbox
|
| 732 |
(textbox
|
| 733 |
.submit(
|
| 734 |
+
check_query_length,
|
| 735 |
+
[textbox],
|
| 736 |
+
[check_status, short_query_warning_state, short_query_warning_row, textbox, pending_query],
|
| 737 |
+
api_name="check_query_length_textbox"
|
| 738 |
+
)
|
| 739 |
+
.then(
|
| 740 |
check_filters,
|
| 741 |
+
[check_status, textbox, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year],
|
| 742 |
+
[check_status, warning_state, warning_row, textbox, pending_query],
|
| 743 |
+
api_name="submit_textbox",
|
| 744 |
+
show_progress=False
|
| 745 |
)
|
| 746 |
.then(
|
| 747 |
get_client_ip_handler,
|
|
|
|
| 752 |
)
|
| 753 |
.then(
|
| 754 |
handle_chat_flow,
|
| 755 |
+
[check_status, warning_state, short_query_warning_state, textbox, chatbot, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year, client_ip, session_id],
|
| 756 |
[chatbot, sources_textbox, feedback_state, session_id],
|
| 757 |
queue=True,
|
| 758 |
api_name="handle_chat_flow_textbox"
|
|
|
|
| 772 |
|
| 773 |
# Hanlders: Text input from Examples (same chain as textbox)
|
| 774 |
examples_hidden.change(
|
| 775 |
+
lambda x: x,
|
| 776 |
inputs=examples_hidden,
|
| 777 |
outputs=textbox,
|
| 778 |
api_name="submit_examples"
|
| 779 |
+
).then(
|
| 780 |
+
check_query_length,
|
| 781 |
+
[textbox],
|
| 782 |
+
[check_status, short_query_warning_state, short_query_warning_row, textbox, pending_query],
|
| 783 |
+
api_name="check_query_length_examples"
|
| 784 |
).then(
|
| 785 |
check_filters,
|
| 786 |
+
[check_status, textbox, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year],
|
| 787 |
+
[check_status, warning_state, warning_row, textbox, pending_query],
|
| 788 |
+
api_name="check_filters_examples",
|
| 789 |
+
show_progress=False
|
| 790 |
).then(
|
| 791 |
get_client_ip_handler,
|
| 792 |
[textbox],
|
|
|
|
| 795 |
api_name="get_client_ip_examples"
|
| 796 |
).then(
|
| 797 |
handle_chat_flow,
|
| 798 |
+
[check_status, warning_state, short_query_warning_state, textbox, chatbot, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year, client_ip, session_id],
|
| 799 |
[chatbot, sources_textbox, feedback_state, session_id],
|
| 800 |
queue=True,
|
| 801 |
api_name="handle_chat_flow_examples"
|
|
|
|
| 815 |
# Handlers for the warning buttons
|
| 816 |
proceed_btn.click(
|
| 817 |
lambda query: (
|
| 818 |
+
None, # reset check status
|
| 819 |
False, # warning state
|
| 820 |
gr.update(visible=False), # warning row
|
| 821 |
gr.update(value=query if query else "", interactive=True), # restore query
|
| 822 |
None # clear pending query
|
| 823 |
),
|
| 824 |
pending_query,
|
| 825 |
+
[check_status, warning_state, warning_row, textbox, pending_query]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 826 |
).then(
|
| 827 |
get_client_ip_handler,
|
| 828 |
[textbox],
|
| 829 |
[client_ip]
|
| 830 |
).then(
|
| 831 |
handle_chat_flow,
|
| 832 |
+
[check_status, warning_state, short_query_warning_state, textbox, chatbot, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year, client_ip, session_id],
|
| 833 |
[chatbot, sources_textbox, feedback_state, session_id],
|
| 834 |
queue=True
|
| 835 |
).then(
|
|
|
|
| 844 |
|
| 845 |
cancel_btn.click(
|
| 846 |
lambda: (
|
| 847 |
+
None, # reset check status
|
| 848 |
+
False, # warning state
|
| 849 |
+
gr.update(visible=False), # warning row
|
| 850 |
+
gr.update(value="", interactive=True), # clear textbox
|
| 851 |
+
None # clear pending query
|
| 852 |
),
|
| 853 |
None,
|
| 854 |
+
[check_status, warning_state, warning_row, textbox, pending_query]
|
| 855 |
)
|
| 856 |
|
| 857 |
+
# short query warning OK button
|
| 858 |
+
short_query_proceed_btn.click(
|
| 859 |
+
lambda query: (
|
| 860 |
+
None, # reset check status
|
| 861 |
+
False, # short query warning state
|
| 862 |
+
gr.update(visible=False), # short query warning row
|
| 863 |
+
gr.update(value=query if query else "", interactive=True), # restore query
|
| 864 |
+
None # clear pending query
|
| 865 |
+
),
|
| 866 |
+
pending_query,
|
| 867 |
+
[check_status, short_query_warning_state, short_query_warning_row, textbox, pending_query]
|
| 868 |
+
)
|
| 869 |
|
| 870 |
demo.queue()
|
| 871 |
|
model_params.cfg
CHANGED
|
@@ -11,6 +11,6 @@ DEDICATED_MODEL = meta-llama/Llama-3.1-8B-Instruct
|
|
| 11 |
DEDICATED_ENDPOINT = https://qu2d8m6dmsollhly.us-east-1.aws.endpoints.huggingface.cloud
|
| 12 |
NVIDIA_MODEL = meta-llama/Llama-3.1-8B-Instruct
|
| 13 |
NVIDIA_ENDPOINT = https://huggingface.co/api/integrations/dgx/v1
|
| 14 |
-
MAX_TOKENS =
|
| 15 |
[app]
|
| 16 |
repo_id = mtyrrell/spaces_log
|
|
|
|
| 11 |
DEDICATED_ENDPOINT = https://qu2d8m6dmsollhly.us-east-1.aws.endpoints.huggingface.cloud
|
| 12 |
NVIDIA_MODEL = meta-llama/Llama-3.1-8B-Instruct
|
| 13 |
NVIDIA_ENDPOINT = https://huggingface.co/api/integrations/dgx/v1
|
| 14 |
+
MAX_TOKENS = 64
|
| 15 |
[app]
|
| 16 |
repo_id = mtyrrell/spaces_log
|
style.css
CHANGED
|
@@ -443,22 +443,22 @@ span.chatbot > p > img{
|
|
| 443 |
margin: 0 5px;
|
| 444 |
}
|
| 445 |
|
| 446 |
-
/* Warning popup styling */
|
| 447 |
#warning-row {
|
| 448 |
background-color: #fff3cd;
|
| 449 |
border: 1px solid #ffeeba;
|
| 450 |
border-radius: 4px;
|
| 451 |
padding: 15px 20px;
|
| 452 |
-
margin: 10px 0;
|
| 453 |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
| 454 |
-
position:
|
| 455 |
z-index: 1000;
|
| 456 |
font-size: 14px;
|
| 457 |
color: #856404;
|
| 458 |
-
width:
|
|
|
|
| 459 |
}
|
| 460 |
|
| 461 |
-
/* Only apply the animation when the element is visible */
|
| 462 |
#warning-row.visible {
|
| 463 |
animation: fadeIn 0.3s ease-in-out;
|
| 464 |
}
|
|
@@ -470,10 +470,24 @@ span.chatbot > p > img{
|
|
| 470 |
text-align: center;
|
| 471 |
}
|
| 472 |
|
| 473 |
-
#warning-row .warning-icon
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 477 |
}
|
| 478 |
|
| 479 |
#warning-row .warning-buttons {
|
|
@@ -481,11 +495,12 @@ span.chatbot > p > img{
|
|
| 481 |
justify-content: center;
|
| 482 |
gap: 15px;
|
| 483 |
margin-top: 10px;
|
| 484 |
-
width: 100%;
|
| 485 |
}
|
| 486 |
|
| 487 |
#warning-row button {
|
| 488 |
min-width: 100px;
|
|
|
|
|
|
|
| 489 |
border-radius: 4px;
|
| 490 |
transition: background-color 0.3s;
|
| 491 |
}
|
|
@@ -532,3 +547,7 @@ body.dark #warning-row button.cancel {
|
|
| 532 |
from { opacity: 0; transform: translateY(-10px); }
|
| 533 |
to { opacity: 1; transform: translateY(0); }
|
| 534 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
margin: 0 5px;
|
| 444 |
}
|
| 445 |
|
| 446 |
+
/* #_______ Warning "popup" styling _______ */
|
| 447 |
#warning-row {
|
| 448 |
background-color: #fff3cd;
|
| 449 |
border: 1px solid #ffeeba;
|
| 450 |
border-radius: 4px;
|
| 451 |
padding: 15px 20px;
|
| 452 |
+
margin: 10px 0; /* Reset margin */
|
| 453 |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
| 454 |
+
position: left; /* Reset to normal positioning */
|
| 455 |
z-index: 1000;
|
| 456 |
font-size: 14px;
|
| 457 |
color: #856404;
|
| 458 |
+
width: 65%; /* Approximatation of chatbox width */
|
| 459 |
+
box-sizing: border-box;
|
| 460 |
}
|
| 461 |
|
|
|
|
| 462 |
#warning-row.visible {
|
| 463 |
animation: fadeIn 0.3s ease-in-out;
|
| 464 |
}
|
|
|
|
| 470 |
text-align: center;
|
| 471 |
}
|
| 472 |
|
| 473 |
+
#warning-row.warning-message span.warning-icon,
|
| 474 |
+
#warning-row.warning-message .prose span.warning-icon {
|
| 475 |
+
font-size: 1.7em !important;
|
| 476 |
+
color: #856404 !important;
|
| 477 |
+
margin-right: 10px !important;
|
| 478 |
+
display: inline-block !important;
|
| 479 |
+
vertical-align: middle !important;
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
#warning-row.warning-message .prose p,
|
| 483 |
+
#warning-row.warning-message .prose strong {
|
| 484 |
+
font-size: 14px !important;
|
| 485 |
+
line-height: 1.5 !important;
|
| 486 |
+
font-weight: bold;
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
#warning-row.warning-message {
|
| 490 |
+
padding: 20px 25px !important;
|
| 491 |
}
|
| 492 |
|
| 493 |
#warning-row .warning-buttons {
|
|
|
|
| 495 |
justify-content: center;
|
| 496 |
gap: 15px;
|
| 497 |
margin-top: 10px;
|
|
|
|
| 498 |
}
|
| 499 |
|
| 500 |
#warning-row button {
|
| 501 |
min-width: 100px;
|
| 502 |
+
max-width: 150px;
|
| 503 |
+
width: auto;
|
| 504 |
border-radius: 4px;
|
| 505 |
transition: background-color 0.3s;
|
| 506 |
}
|
|
|
|
| 547 |
from { opacity: 0; transform: translateY(-10px); }
|
| 548 |
to { opacity: 1; transform: translateY(0); }
|
| 549 |
}
|
| 550 |
+
|
| 551 |
+
|
| 552 |
+
|
| 553 |
+
|