dashboard polish

This commit is contained in:
Andrew Brown 2025-06-03 00:15:06 -07:00
parent a0f860d89e
commit 4e07ad8a9b
3 changed files with 277 additions and 14 deletions

View File

@ -184,6 +184,10 @@ class User < ApplicationRecord
Rails.application.config.content_types[:all].select { |c| can_create? c }
end
def words_written_today
word_count_updates.where(created_at: Time.current.beginning_of_day..Time.current.end_of_day).sum(:word_count)
end
# as_json creates a hash structure, which you then pass to ActiveSupport::json.encode to actually encode the object as a JSON string.
# This is different from to_json, which converts it straight to an escaped JSON string,
# which is undesireable in a case like this, when we want to modify it

View File

@ -2,10 +2,10 @@
<% (@activated_content_types + ["Timeline", "Document"]).each do |content_type| %>
<% klass = content_class_from_name(content_type) %>
<%= link_to send("new_#{content_type.downcase}_path") do %>
<li class="col-span-1 <%= klass.color %> text-white py-6 rounded-lg shadow hover:shadow-lg hover:opacity-80">
<div class="w-full text-center">
<i class="material-icons text-6xl"><%= klass.icon %></i>
<div>
<li class="fancy-button relative overflow-hidden col-span-1 <%= klass.color %> text-white py-6 rounded-lg shadow hover:shadow-lg hover:opacity-90 hover:-translate-y-1 transition-all duration-200 active:scale-95 active:shadow-sm cursor-pointer">
<div class="w-full text-center relative z-10">
<i class="material-icons text-6xl transition-transform duration-200"><%= klass.icon %></i>
<div class="transition-all duration-200">
<%= content_type %>
</div>
</div>
@ -14,13 +14,136 @@
<% end %>
<%= link_to customization_content_types_path do %>
<li class="col-span-1 bg-notebook-blue text-white py-6 rounded-lg shadow hover:shadow-lg">
<div class="w-full text-center">
<i class="material-icons text-6xl">add</i>
<div>
<li class="fancy-button relative overflow-hidden col-span-1 bg-notebook-blue text-white py-6 rounded-lg shadow hover:shadow-lg hover:opacity-90 hover:-translate-y-1 transition-all duration-200 active:scale-95 active:shadow-sm cursor-pointer">
<div class="w-full text-center relative z-10">
<i class="material-icons text-6xl transition-transform duration-200">add</i>
<div class="transition-all duration-200">
More...
</div>
</div>
</li>
<% end %>
</ul>
<style>
.fancy-button {
position: relative;
transform-style: preserve-3d;
}
.fancy-button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.6s ease, height 0.6s ease;
pointer-events: none;
z-index: 1;
}
.fancy-button:active::before {
width: 300px;
height: 300px;
}
.fancy-button:active i {
transform: scale(1.1) rotate(5deg);
}
.fancy-button:hover i {
transform: scale(1.05);
}
/* Enhanced glow effect on hover */
.fancy-button:hover {
filter: brightness(1.1);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.2), 0 10px 10px -5px rgba(0, 0, 0, 0.1);
}
/* Pulse animation on focus */
.fancy-button:focus {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(255, 255, 255, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
}
}
/* Ripple effect styles */
.ripple {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
transform: scale(0);
animation: rippleEffect 0.6s linear;
pointer-events: none;
z-index: 1;
}
@keyframes rippleEffect {
to {
transform: scale(4);
opacity: 0;
}
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const fancyButtons = document.querySelectorAll('.fancy-button');
fancyButtons.forEach(button => {
button.addEventListener('click', function(e) {
// Create ripple effect
const ripple = document.createElement('span');
const rect = this.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
const x = e.clientX - rect.left - size / 2;
const y = e.clientY - rect.top - size / 2;
ripple.style.width = ripple.style.height = size + 'px';
ripple.style.left = x + 'px';
ripple.style.top = y + 'px';
ripple.classList.add('ripple');
this.appendChild(ripple);
// Add click animation classes
this.style.transform = 'scale(0.95) translateY(-1px)';
setTimeout(() => {
this.style.transform = '';
ripple.remove();
}, 300);
// Add a brief glow effect
this.style.boxShadow = '0 0 20px rgba(255, 255, 255, 0.3)';
setTimeout(() => {
this.style.boxShadow = '';
}, 200);
});
// Add floating animation on hover
button.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-3px) scale(1.02)';
});
button.addEventListener('mouseleave', function() {
this.style.transform = '';
});
});
});
</script>

View File

@ -48,16 +48,16 @@
<!-- Action buttons -->
<div class="mt-6 lg:mt-0 flex flex-col sm:flex-row space-y-3 sm:space-y-0 sm:space-x-3">
<%= link_to new_character_path do %>
<button type="button" class="inline-flex items-center justify-center px-6 py-3 border border-gray-300 shadow-sm text-sm font-medium rounded-lg text-gray-700 bg-white hover:bg-gray-50 hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200">
<i class="material-icons mr-2 <%= Character.text_color %>"><%= Character.icon %></i>
Create Character
<button type="button" class="fancy-button relative overflow-hidden inline-flex items-center justify-center px-6 py-3 border border-gray-300 shadow-sm text-sm font-medium rounded-lg text-gray-700 bg-white hover:bg-gray-50 hover:border-gray-400 hover:shadow-lg hover:-translate-y-0.5 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200 active:scale-95 active:shadow-sm">
<i class="material-icons mr-2 <%= Character.text_color %> transition-transform duration-200"><%= Character.icon %></i>
<span class="transition-all duration-200">Create Character</span>
</button>
<% end %>
<% random_enabled_page_type = (current_user.createable_content_types - [Character]).sample %>
<%= link_to new_polymorphic_path(random_enabled_page_type) do %>
<button type="button" class="inline-flex items-center justify-center px-6 py-3 border border-transparent shadow-sm text-sm font-medium rounded-lg text-white <%= random_enabled_page_type.color %> focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200">
<i class="material-icons mr-2"><%= random_enabled_page_type.icon %></i>
<span>
<button type="button" class="fancy-button relative overflow-hidden inline-flex items-center justify-center px-6 py-3 border border-transparent shadow-sm text-sm font-medium rounded-lg text-white <%= random_enabled_page_type.color %> hover:shadow-lg hover:-translate-y-0.5 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200 active:scale-95 active:shadow-sm">
<i class="material-icons mr-2 transition-transform duration-200"><%= random_enabled_page_type.icon %></i>
<span class="transition-all duration-200">
Create <%= random_enabled_page_type.name %>
</span>
</button>
@ -111,4 +111,140 @@
</div>
</div>
<style>
.fancy-button {
position: relative;
transform-style: preserve-3d;
}
.fancy-button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.6s ease, height 0.6s ease;
pointer-events: none;
z-index: 1;
}
.fancy-button:active::before {
width: 300px;
height: 300px;
}
.fancy-button:active i {
transform: scale(1.1) rotate(5deg);
}
.fancy-button:hover i {
transform: scale(1.05);
}
/* Enhanced glow effect on hover */
.fancy-button:hover {
filter: brightness(1.05);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
/* Pulse animation on focus */
.fancy-button:focus {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
}
}
/* Subtle background animation */
.fancy-button {
background-size: 200% 200%;
animation: gradientShift 4s ease infinite;
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const fancyButtons = document.querySelectorAll('.fancy-button');
fancyButtons.forEach(button => {
button.addEventListener('click', function(e) {
// Create ripple effect
const ripple = document.createElement('span');
const rect = this.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
const x = e.clientX - rect.left - size / 2;
const y = e.clientY - rect.top - size / 2;
ripple.style.width = ripple.style.height = size + 'px';
ripple.style.left = x + 'px';
ripple.style.top = y + 'px';
ripple.classList.add('ripple');
this.appendChild(ripple);
// Add click animation classes
this.style.transform = 'scale(0.95)';
setTimeout(() => {
this.style.transform = '';
ripple.remove();
}, 300);
// Add a brief glow effect
this.style.boxShadow = '0 0 20px rgba(59, 130, 246, 0.5)';
setTimeout(() => {
this.style.boxShadow = '';
}, 200);
});
// Add floating animation on hover
button.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-2px) scale(1.02)';
});
button.addEventListener('mouseleave', function() {
this.style.transform = '';
});
});
});
</script>
<style>
.ripple {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
transform: scale(0);
animation: rippleEffect 0.6s linear;
pointer-events: none;
z-index: 1;
}
@keyframes rippleEffect {
to {
transform: scale(4);
opacity: 0;
}
}
</style>
<%= render partial: 'javascripts/content_linking' %>