wip dashboard redesign (again lol)

This commit is contained in:
Andrew Brown 2025-07-07 19:59:04 -07:00
parent 7c06b27f60
commit 3ce3481a1a
9 changed files with 737 additions and 723 deletions

View File

@ -79,6 +79,7 @@ class ApplicationController < ActionController::Base
cache_current_user_content
cache_notifications
cache_recently_edited_pages
cache_most_edited_pages
end
def cache_activated_content_types
@ -157,6 +158,34 @@ class ApplicationController < ActionController::Base
end
end
def cache_most_edited_pages(amount=50)
cache_current_user_content
@most_edited_pages ||= if user_signed_in?
# Get all user's content
all_content = @current_user_content.values.flatten
# Count edits for each content page using ContentChangeEvent
content_with_edit_counts = all_content.map do |content_page|
edit_count = ContentChangeEvent.where(
content_type: content_page.page_type,
content_id: content_page.id,
user_id: current_user.id
).count
[content_page, edit_count]
end
# Sort by edit count (descending) and take the top pages
# Keep both content page and edit count for the view
content_with_edit_counts
.sort_by { |content_page, edit_count| -edit_count }
.first(amount)
else
[]
end
end
def cache_forums_unread_counts
@unread_threads ||= if user_signed_in?
Thredded::Topic.unread_followed_by(current_user).count

View File

@ -1,19 +1,37 @@
<ul role="list" class="space-y-2 flex mb-8">
<div class="space-y-3">
<% discussions.each do |topic| %>
<%= link_to topic, class: 'w-full' do %>
<li class="text-sm text-white hover:bg-blue-600 bg-blue-500 shadow hover:shadow-md rounded p-2">
<i class="material-icons float-left mr-2">forum</i>
<div class="line-clamp-1"><%= topic.title %></div>
<div>
<small>
<%= number_with_delimiter (topic.posts.count - 1) %>
<%= 'reply'.pluralize(topic.posts.count - 1) %>
</small>
<small>
in <strong><%= topic.messageboard.name %></strong>
</small>
<%= link_to topic, class: 'block group' do %>
<div class="bg-white border border-gray-200 rounded-lg p-3 hover:border-orange-300 hover:shadow-sm transition-all duration-200">
<div class="flex items-start space-x-3">
<div class="flex-shrink-0 mt-1">
<i class="material-icons text-orange-500 text-lg">forum</i>
</div>
<div class="flex-1 min-w-0">
<h4 class="text-sm font-medium text-gray-900 line-clamp-2 group-hover:text-orange-600 transition-colors">
<%= topic.title %>
</h4>
<div class="flex items-center space-x-3 mt-1 text-xs text-gray-500">
<span>
<%= number_with_delimiter (topic.posts.count - 1) %>
<%= 'reply'.pluralize(topic.posts.count - 1) %>
</span>
<span>•</span>
<span class="line-clamp-1">
in <strong><%= topic.messageboard.name %></strong>
</span>
</div>
</div>
</div>
</li>
</div>
<% end %>
<% end %>
</ul>
<!-- View All Link -->
<div class="pt-3 border-t border-gray-100">
<%= link_to thredded_path, class: "block text-center text-sm text-orange-600 hover:text-orange-700 font-medium" do %>
Join the discussion →
<% end %>
</div>
</div>

View File

@ -0,0 +1,198 @@
<div class="dashboard-card">
<div class="dashboard-card-header">
<div class="flex items-center justify-between">
<h2 class="dashboard-card-title">
<i class="material-icons mr-2 text-gray-600">library_books</i>
Content Library
</h2>
<!-- Tab Navigation -->
<div class="flex space-x-1 bg-gray-100 rounded-lg p-1">
<button class="tab-button active" data-tab="recent">Recent</button>
<button class="tab-button" data-tab="favorites">Favorites</button>
<button class="tab-button" data-tab="all">All Content</button>
<button class="tab-button" data-tab="by-type">By Type</button>
</div>
</div>
</div>
<div class="dashboard-card-content">
<!-- Recent Tab -->
<div class="tab-content" data-tab="recent">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<% @current_user_content.except('Universe').values.flatten.sort_by(&:updated_at).reverse.first(15).each do |content_page| %>
<%= link_to send("edit_#{content_page.page_type.downcase}_path", content_page.id), class: 'block' do %>
<div class="bg-white rounded-lg shadow-sm border border-gray-200 hover:shadow-md hover:-translate-y-1 transition-all duration-300 overflow-hidden">
<div class="relative h-32 overflow-hidden">
<% if content_page.random_image_including_private.present? %>
<%= image_tag content_page.random_image_including_private, class: 'w-full h-full object-cover' %>
<% else %>
<div class="w-full h-full <%= content_page.color %> bg-opacity-20 flex items-center justify-center">
<i class="material-icons text-4xl text-white opacity-80"><%= content_page.icon %></i>
</div>
<% end %>
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent"></div>
<div class="absolute top-2 right-2 inline-flex items-center px-2 py-1 rounded-full text-xs font-medium text-white backdrop-blur-sm <%= content_page.color %>">
<i class="material-icons text-xs mr-1"><%= content_page.icon %></i>
<%= content_page.page_type %>
</div>
<h3 class="absolute bottom-2 left-2 right-2 text-white font-semibold text-sm line-clamp-2">
<%= content_page.name %>
</h3>
</div>
<div class="px-4 py-3 border-t border-gray-100 flex items-center justify-between text-xs text-gray-500">
<div class="flex items-center space-x-2">
<% if content_page.cached_word_count.present? %>
<span class="flex items-center space-x-1">
<i class="material-icons text-xs">description</i>
<span><%= number_with_delimiter content_page.cached_word_count %> words</span>
</span>
<% end %>
</div>
<span class="flex items-center space-x-1">
<i class="material-icons text-xs">schedule</i>
<span>edited <%= time_ago_in_words content_page.updated_at %> ago</span>
</span>
</div>
</div>
<% end %>
<% end %>
</div>
</div>
<!-- Favorites Tab -->
<div class="tab-content hidden" data-tab="favorites">
<% favorites = @current_user_content.except('Universe').values.flatten.select(&:favorite?) %>
<% if favorites.any? %>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<% favorites.sort_by { |p| p.name.downcase }.each do |content_page| %>
<%= link_to send("edit_#{content_page.page_type.downcase}_path", content_page.id), class: 'block' do %>
<div class="bg-white rounded-lg shadow-sm border border-gray-200 hover:shadow-md hover:-translate-y-1 transition-all duration-300 overflow-hidden">
<div class="relative h-32 overflow-hidden">
<% if content_page.random_image_including_private.present? %>
<%= image_tag content_page.random_image_including_private, class: 'w-full h-full object-cover' %>
<% else %>
<div class="w-full h-full <%= content_page.color %> bg-opacity-20 flex items-center justify-center">
<i class="material-icons text-4xl text-white opacity-80"><%= content_page.icon %></i>
</div>
<% end %>
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent"></div>
<div class="absolute top-2 left-2">
<i class="material-icons text-yellow-400">star</i>
</div>
<div class="absolute top-2 right-2 inline-flex items-center px-2 py-1 rounded-full text-xs font-medium text-white backdrop-blur-sm <%= content_page.color %>">
<i class="material-icons text-xs mr-1"><%= content_page.icon %></i>
<%= content_page.page_type %>
</div>
<h3 class="absolute bottom-2 left-2 right-2 text-white font-semibold text-sm line-clamp-2">
<%= content_page.name %>
</h3>
</div>
<div class="px-4 py-3 border-t border-gray-100 flex items-center justify-between text-xs text-gray-500">
<div class="flex items-center space-x-2">
<% if content_page.cached_word_count.present? %>
<span class="flex items-center space-x-1">
<i class="material-icons text-xs">description</i>
<span><%= number_with_delimiter content_page.cached_word_count %> words</span>
</span>
<% end %>
</div>
<span class="flex items-center space-x-1">
<i class="material-icons text-xs">schedule</i>
<span>edited <%= time_ago_in_words content_page.updated_at %> ago</span>
</span>
</div>
</div>
<% end %>
<% end %>
</div>
<% else %>
<div class="text-center py-8">
<i class="material-icons text-6xl text-gray-300 mb-4">star_border</i>
<h3 class="text-lg font-medium text-gray-900 mb-2">No favorites yet</h3>
<p class="text-gray-500">Star your favorite content to see it here</p>
</div>
<% end %>
</div>
<!-- All Content Tab -->
<div class="tab-content hidden" data-tab="all">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<% @current_user_content.except('Universe').values.flatten.sort_by { |p| p.name.downcase }.each do |content_page| %>
<%= link_to send("edit_#{content_page.page_type.downcase}_path", content_page.id), class: 'block' do %>
<div class="bg-white rounded-lg shadow-sm border border-gray-200 hover:shadow-md hover:-translate-y-1 transition-all duration-300 overflow-hidden">
<div class="relative h-32 overflow-hidden">
<% if content_page.random_image_including_private.present? %>
<%= image_tag content_page.random_image_including_private, class: 'w-full h-full object-cover' %>
<% else %>
<div class="w-full h-full <%= content_page.color %> bg-opacity-20 flex items-center justify-center">
<i class="material-icons text-3xl text-white opacity-80"><%= content_page.icon %></i>
</div>
<% end %>
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent"></div>
<div class="absolute top-2 right-2 inline-flex items-center px-2 py-1 rounded-full text-xs font-medium text-white backdrop-blur-sm <%= content_page.color %>">
<i class="material-icons text-xs mr-1"><%= content_page.icon %></i>
<%= content_page.page_type %>
</div>
<h3 class="absolute bottom-2 left-2 right-2 text-white font-semibold text-sm line-clamp-2">
<%= content_page.name %>
</h3>
</div>
</div>
<% end %>
<% end %>
</div>
</div>
<!-- By Type Tab -->
<div class="tab-content hidden" data-tab="by-type">
<div class="space-y-6">
<% @current_user_content.except('Universe').each do |content_type, pages| %>
<% next if pages.empty? %>
<div>
<h3 class="text-lg font-semibold text-gray-900 mb-3 flex items-center">
<% klass = content_class_from_name(content_type) %>
<i class="material-icons mr-2 <%= klass.text_color %>"><%= klass.icon %></i>
<%= content_type.pluralize %>
<span class="text-sm font-normal text-gray-500 ml-2">(<%= pages.length %>)</span>
</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
<% pages.sort_by { |p| p.name.downcase }.each do |content_page| %>
<%= link_to send("edit_#{content_page.page_type.downcase}_path", content_page.id), class: 'block' do %>
<div class="bg-white border border-gray-200 rounded-lg p-3 hover:border-blue-300 hover:shadow-sm transition-all duration-200">
<div class="flex items-center space-x-2">
<% if content_page.favorite? %>
<i class="material-icons text-yellow-400 text-sm">star</i>
<% end %>
<h4 class="font-medium text-gray-900 line-clamp-1 flex-1">
<%= content_page.name %>
</h4>
</div>
<div class="mt-1 text-xs text-gray-500">
edited <%= time_ago_in_words content_page.updated_at %> ago
</div>
</div>
<% end %>
<% end %>
</div>
</div>
<% end %>
</div>
</div>
</div>
</div>

View File

@ -1,149 +1,30 @@
<ul role="list" class="grid grid-cols-3 gap-2">
<!-- Full Grid of Content Types -->
<div class="grid grid-cols-12 gap-2 p-2 max-w-7xl mx-auto">
<% (@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="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>
<%= link_to send("new_#{content_type.downcase}_path"), class: "block" do %>
<div class="<%= klass.color %> text-white rounded-lg p-4 hover:opacity-90 hover:shadow-md transition-all duration-200 text-center group">
<div class="relative">
<i class="material-icons text-3xl mb-1 transition-all duration-200 opacity-100 group-hover:opacity-0"><%= klass.icon %></i>
<i class="material-icons text-3xl mb-1 transition-all duration-200 absolute inset-0 opacity-0 group-hover:opacity-100">add</i>
</div>
</li>
<div class="text-xs font-medium">
<%= content_type %>
</div>
</div>
<% end %>
<% end %>
<%= link_to customization_content_types_path do %>
<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>
<%= link_to customization_content_types_path, class: "block" do %>
<div class="bg-gray-500 text-white rounded-lg p-4 hover:bg-gray-600 hover:shadow-md transition-all duration-200 text-center group">
<div class="relative">
<i class="material-icons text-3xl mb-1 transition-all duration-200 opacity-100 group-hover:opacity-0">add</i>
<i class="material-icons text-3xl mb-1 transition-all duration-200 absolute inset-0 opacity-0 group-hover:opacity-100">arrow_forward</i>
</div>
</li>
<div class="text-xs font-medium">
More...
</div>
</div>
<% end %>
</ul>
</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.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

@ -0,0 +1,55 @@
<div class="dashboard-card">
<div class="dashboard-card-content">
<div class="quote-card text-gray-600 mt-1">
<%
quotes = [
{ text: "The secret to getting ahead is getting started.", author: "Mark Twain" },
{ text: "A story is a character who wants something and overcomes conflict to get it.", author: "Donald Maass" },
{ text: "You can't use up creativity. The more you use, the more you have.", author: "Maya Angelou" },
{ text: "The first draft of anything is shit.", author: "Ernest Hemingway" },
{ text: "Write what you need to know, not what you already know.", author: "Donald Barthelme" },
{ text: "I can shake off everything as I write; my sorrows disappear, my courage is reborn.", author: "Anne Frank" },
{ text: "The role of a writer is not to say what we all can say, but what we are unable to say.", author: "Anaïs Nin" },
{ text: "We write to taste life twice, in the moment and in retrospect.", author: "Anaïs Nin" },
{ text: "There is nothing to writing. All you do is sit down at a typewriter and bleed.", author: "Ernest Hemingway" },
{ text: "The scariest moment is always just before you start.", author: "Stephen King" },
{ text: "Don't tell me the moon is shining; show me the glint of light on broken glass.", author: "Anton Chekhov" },
{ text: "If you want to be a writer, you must do two things above all others: read a lot and write a lot.", author: "Stephen King" },
{ text: "The art of writing is the art of discovering what you believe.", author: "Gustave Flaubert" },
{ text: "You don't start out writing good stuff. You start out writing crap and thinking it's good stuff, and then gradually you get better at it.", author: "Octavia Butler" },
{ text: "A good story should make you laugh, and a moment later break your heart.", author: "Chuck Palahniuk" },
{ text: "Writing is an exploration. You start from nothing and learn as you go.", author: "E.L. Doctorow" },
{ text: "There is no greater agony than bearing an untold story inside you.", author: "Maya Angelou" },
{ text: "A writer is someone for whom writing is more difficult than it is for other people.", author: "Thomas Mann" },
{ text: "The only way to write a novel is to assume that you will never publish it.", author: "John Irving" },
{ text: "The best time for planning a book is while you're doing the dishes.", author: "Agatha Christie" },
{ text: "You can make anything by writing.", author: "C.S. Lewis" },
{ text: "Writing is the painting of the voice.", author: "Voltaire" },
{ text: "A writer is a world trapped in a person.", author: "Victor Hugo" },
{ text: "The writer's job is to tell the truth, even when it hurts.", author: "George Orwell" },
{ text: "The best stories are those that are true, even if they never happened.", author: "Unknown" },
{ text: "Writing is the only way I have to explain my own life to myself.", author: "Pat Conroy" },
{ text: "A writer is a person who can make a story out of anything.", author: "Neil Gaiman" },
{ text: "The only way to write is to write a lot.", author: "Ray Bradbury" },
{ text: "Writing is the only profession where no one considers you ridiculous if you earn no money.", author: "Jules Renard" },
{ text: "A writer is someone who has something to say and knows how to say it.", author: "William Zinsser" },
{ text: "The best way to predict the future is to invent it.", author: "Alan Kay" },
{ text: "Writing is a socially acceptable form of schizophrenia.", author: "E.L. Doctorow" },
{ text: "The only thing worse than starting something and failing is not starting something.", author: "Seth Godin" }
]
# Use day of year to ensure same quote per day
day_of_year = Time.current.yday
today_quote = quotes[day_of_year % quotes.length]
%>
<blockquote class="quote-text">
"<%= today_quote[:text] %>"
</blockquote>
<div class="quote-author text-xs">
— <%= today_quote[:author] %>
</div>
</div>
</div>
</div>

View File

@ -1,169 +1,51 @@
<div class="space-y-4">
<% @recently_edited_pages.first(9).each do |page| %>
<div class="space-y-3">
<% @recently_edited_pages.first(6).each do |page| %>
<%# Timelines and Documents use edit_polymorphic_path, while ContentPages uses edit_path -- gross %>
<%= link_to page.try(:view_path) || edit_polymorphic_path(page), class: 'block w-full group' do %>
<div class="fancy-card relative overflow-hidden rounded-xl shadow-lg hover:shadow-xl border border-gray-200/50 transition-all duration-300 hover:-translate-y-1">
<!-- Image container with fixed height -->
<div class="relative h-32 overflow-hidden rounded-t-xl">
<%= image_tag page.random_image_including_private, class: 'absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-110' %>
<!-- Gradient overlay for title readability -->
<div class="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent"></div>
<!-- Content type badge -->
<div class="absolute top-3 right-3 z-20">
<div class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium <%= page.color %> backdrop-blur-sm text-white">
<i class="material-icons text-sm mr-1"><%= page.icon %></i>
<%= page.page_type %>
<div class="bg-white border border-gray-200 rounded-lg p-4 hover:border-blue-300 hover:shadow-sm transition-all duration-200">
<div class="flex items-center space-x-3">
<!-- Content Type Icon -->
<div class="flex-shrink-0">
<div class="w-10 h-10 <%= page.color %> rounded-lg flex items-center justify-center">
<i class="material-icons text-white text-lg"><%= page.icon %></i>
</div>
</div>
<!-- Title overlay at bottom of image -->
<div class="absolute bottom-0 left-0 right-0 p-4 z-10">
<h3 class="text-lg font-bold text-white line-clamp-2 group-hover:text-blue-300 transition-colors duration-200">
<!-- Content Info -->
<div class="flex-1 min-w-0">
<h4 class="font-medium text-gray-900 line-clamp-1 group-hover:text-blue-600 transition-colors">
<%= page.name %>
</h3>
</div>
</div>
<!-- Footer below image -->
<div class="bg-white rounded-b-xl">
<!-- Stats and metadata -->
<div class="flex items-center justify-between border-t border-gray-200 px-4 py-3">
<div class="flex items-center space-x-3 text-xs text-gray-500">
</h4>
<div class="flex items-center space-x-3 mt-1 text-sm text-gray-500">
<span class="flex items-center space-x-1">
<i class="material-icons text-xs">schedule</i>
<span>edited <%= time_ago_in_words page.updated_at %> ago</span>
</span>
<% if page.cached_word_count.present? %>
<div class="flex items-center space-x-1">
<i class="material-icons text-sm text-blue-500">description</i>
<span class="font-medium text-gray-700">
<%= number_with_delimiter page.cached_word_count %>
<%= 'word'.pluralize(page.cached_word_count) %>
</span>
</div>
<span class="flex items-center space-x-1">
<i class="material-icons text-xs">description</i>
<span><%= number_with_delimiter page.cached_word_count %> words</span>
</span>
<% end %>
<% if page.respond_to?(:favorite?) && page.favorite? %>
<i class="material-icons text-xs text-yellow-500">star</i>
<% end %>
</div>
<div class="flex items-center space-x-1 text-xs text-gray-500">
<i class="material-icons text-sm text-green-500">schedule</i>
<span>edited <%= time_ago_in_words page.updated_at %> ago</span>
</div>
</div>
<!-- Quick Edit Arrow -->
<div class="flex-shrink-0">
<i class="material-icons text-gray-400 group-hover:text-blue-500 transition-colors">arrow_forward</i>
</div>
</div>
</div>
<% end %>
<% end %>
</div>
<style>
.fancy-card {
position: relative;
transform-style: preserve-3d;
}
.fancy-card:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(59, 130, 246, 0.2);
border-color: rgba(59, 130, 246, 0.3);
}
.fancy-card:active {
transform: translateY(-2px) scale(0.98);
transition-duration: 0.1s;
}
/* Enhanced text effects */
.fancy-card h3 {
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.fancy-card:hover h3 {
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.8);
}
/* Shimmer effect on hover */
.fancy-card::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s ease;
border-radius: 0.75rem;
pointer-events: none;
z-index: 5;
}
.fancy-card:hover::after {
left: 100%;
}
/* Ripple effect for cards */
.card-ripple {
position: absolute;
border-radius: 50%;
background: rgba(59, 130, 246, 0.4);
transform: scale(0);
animation: cardRippleEffect 0.6s linear;
pointer-events: none;
z-index: 15;
}
@keyframes cardRippleEffect {
to {
transform: scale(4);
opacity: 0;
}
}
/* Line clamp utilities */
.line-clamp-1 {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const fancyCards = document.querySelectorAll('.fancy-card');
fancyCards.forEach(card => {
card.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('card-ripple');
this.appendChild(ripple);
setTimeout(() => {
ripple.remove();
}, 600);
});
// Add subtle floating animation
card.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-6px) scale(1.03)';
});
card.addEventListener('mouseleave', function() {
this.style.transform = '';
});
});
});
</script>
<!-- View All Link -->
<div class="pt-3 border-t border-gray-100">
<%= link_to recent_content_path, class: "block text-center text-sm text-blue-600 hover:text-blue-700 font-medium" do %>
View all recent updates →
<% end %>
</div>
</div>

View File

@ -1,21 +1,54 @@
<% if @content %>
<div class="bg-white shadow-md rounded-md mb-8">
<div class="bg-notebook-blue rounded-t-lg">
<%= image_tag @content.random_image_including_private(format: :original), class: 'w-full object-cover object-center max-h-48 rounded-t-md' %>
</div>
<div class="p-4 lg:flex lg:flex-col lg:justify-between ">
<div>
<%=
if @attribute_field_to_question
render partial: 'cards/serendipitous/tailwind_content_question', locals: {
content: @content,
field: @attribute_field_to_question,
expand_by_default: true,
include_quick_reference: false
}
end
%>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<!-- Hero Image -->
<div class="relative h-48 overflow-hidden">
<%= image_tag @content.random_image_including_private(format: :original), class: 'w-full h-full object-cover' %>
<div class="absolute inset-0 bg-gradient-to-t from-black/50 via-transparent to-transparent"></div>
<!-- Content Type Badge -->
<div class="absolute top-4 right-4">
<div class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium <%= @content.color %> text-white backdrop-blur-sm">
<i class="material-icons text-sm mr-1"><%= @content.icon %></i>
<%= @content.page_type %>
</div>
</div>
<!-- Content Title -->
<div class="absolute bottom-4 left-4 right-4">
<h3 class="text-xl font-bold text-white mb-1">
<%= link_to @content.name, send("#{@content.page_type.downcase}_path", @content.id), class: "hover:text-blue-200 transition-colors" %>
</h3>
<p class="text-sm text-gray-200">
<% if @content.respond_to?(:universe) && @content.universe.present? %>
from <%= @content.universe.name %>
<% end %>
</p>
</div>
</div>
<!-- Question Content -->
<div class="p-6">
<% if @attribute_field_to_question %>
<%= render partial: 'cards/serendipitous/tailwind_content_question', locals: {
content: @content,
field: @attribute_field_to_question,
expand_by_default: true,
include_quick_reference: false
} %>
<!-- Action Buttons -->
<div class="mt-6 flex flex-col sm:flex-row gap-3">
<%= link_to send("edit_#{@content.page_type.downcase}_path", @content.id), class: "inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-lg text-white #{@content.color} hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200" do %>
<i class="material-icons mr-2 text-sm">edit</i>
Work on this
<% end %>
<button onclick="location.reload()" class="inline-flex items-center justify-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-lg text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200">
<i class="material-icons mr-2 text-sm">refresh</i>
Ask me something else
</button>
</div>
<% end %>
</div>
</div>
<% end %>

View File

@ -0,0 +1,55 @@
<div class="dashboard-card">
<div class="dashboard-card-header">
<h2 class="dashboard-card-title">
<i class="material-icons mr-2 text-emerald-600">new_releases</i>
What's New
</h2>
</div>
<div class="dashboard-card-content">
<div class="whats-new-card">
<div class="whats-new-badge">
<i class="material-icons text-xs mr-1">fiber_new</i>
Latest Update
</div>
<h3 class="font-semibold text-gray-900 mb-2">Search & Discovery Improvements</h3>
<p class="text-sm text-gray-600 mb-4">
We've enhanced the search experience with instant autocomplete and multi-word search capabilities. Find your content faster than ever!
</p>
<div class="flex items-center justify-between">
<span class="text-xs text-gray-500">December 2024</span>
<button class="text-xs text-emerald-600 hover:text-emerald-700 font-medium">
Learn more →
</button>
</div>
</div>
<!-- Additional smaller updates -->
<div class="mt-4 space-y-2">
<div class="flex items-center justify-between py-2 border-b border-gray-100 last:border-b-0">
<div class="flex items-center space-x-2">
<div class="w-2 h-2 bg-blue-400 rounded-full"></div>
<span class="text-sm text-gray-600">Template editor consolidation</span>
</div>
<span class="text-xs text-gray-400">Dec 7</span>
</div>
<div class="flex items-center justify-between py-2 border-b border-gray-100 last:border-b-0">
<div class="flex items-center space-x-2">
<div class="w-2 h-2 bg-purple-400 rounded-full"></div>
<span class="text-sm text-gray-600">Universe organization guide</span>
</div>
<span class="text-xs text-gray-400">Dec 7</span>
</div>
<div class="flex items-center justify-between py-2">
<div class="flex items-center space-x-2">
<div class="w-2 h-2 bg-green-400 rounded-full"></div>
<span class="text-sm text-gray-600">Dashboard redesign</span>
</div>
<span class="text-xs text-gray-400">Dec 7</span>
</div>
</div>
</div>
</div>

View File

@ -1,302 +1,201 @@
<div class="min-h-full">
<div class="flex flex-col flex-1">
<main class="flex-1 pb-8">
<!-- Modern full-width header -->
<div class="bg-gradient-to-r from-slate-50 to-white border-b border-gray-200">
<div class="w-full px-4 sm:px-6 lg:px-8">
<div class="py-4 sm:py-6 lg:py-8">
<div class="flex flex-col space-y-4 lg:flex-row lg:items-center lg:justify-between lg:space-y-0">
<!-- User info section -->
<div class="flex flex-col sm:flex-row sm:items-center sm:space-x-4 space-y-4 sm:space-y-0">
<%= link_to current_user, class: "group relative self-center sm:self-auto" do %>
<div class="relative">
<%= image_tag current_user.image_url, class: "h-16 w-16 sm:h-20 sm:w-20 rounded-full #{User.color} ring-4 ring-white shadow-lg group-hover:ring-blue-100 transition-all duration-200" %>
<div class="absolute -bottom-1 -right-1 h-4 w-4 sm:h-6 sm:w-6 bg-green-400 rounded-full ring-4 ring-white"></div>
</div>
<% end %>
<div class="flex flex-col text-center sm:text-left">
<div class="flex flex-col sm:flex-row sm:items-center sm:space-x-2 space-y-1 sm:space-y-0">
<h1 class="text-xl sm:text-2xl lg:text-3xl font-bold text-gray-900 tracking-tight">
Welcome back, <%= link_to current_user.display_name, current_user, class: "#{User.text_color} hover:opacity-80 transition-opacity" %>
</h1>
<%= render partial: 'thredded/users/badge', locals: { user: current_user } %>
</div>
<p class="text-base sm:text-lg text-gray-600 mt-1">
<em>Ready to create something amazing today?</em>
</p>
<!--
<div class="flex items-center space-x-6 mt-3">
<div class="flex items-center space-x-2 text-sm text-gray-500">
<svg class="h-4 w-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
</svg>
<span class="font-medium">Active projects</span>
</div>
<div class="flex items-center space-x-2 text-sm text-gray-500">
<svg class="h-4 w-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
<span class="font-medium">Recent activity</span>
</div>
</div>
-->
</div>
</div>
<!-- Action buttons -->
<div class="flex flex-col sm:flex-row space-y-3 sm:space-y-0 sm:space-x-3 w-full sm:w-auto">
<%= link_to new_character_path do %>
<button type="button" class="fancy-button relative overflow-hidden inline-flex items-center justify-center px-4 sm: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="fancy-button relative overflow-hidden inline-flex items-center justify-center px-4 sm: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>
<% end %>
</div>
<!-- Main Dashboard Container -->
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50">
<!-- Hero Section -->
<div class="bg-white shadow-sm border-b border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between space-y-4 lg:space-y-0">
<!-- User Welcome -->
<div class="flex items-center space-x-4">
<%= link_to current_user, class: "group relative" do %>
<div class="relative">
<%= image_tag current_user.image_url, class: "h-14 w-14 rounded-full #{User.color} ring-3 ring-white shadow-md group-hover:ring-blue-100 transition-all duration-200" %>
<div class="absolute -bottom-1 -right-1 h-4 w-4 bg-green-400 rounded-full ring-2 ring-white"></div>
</div>
<% end %>
<div>
<h1 class="text-2xl font-bold text-gray-900">
Welcome back, <%= link_to current_user.display_name, current_user, class: "#{User.text_color} hover:opacity-80 transition-opacity" %>
</h1>
<!-- Quote of the Day -->
<%= render partial: 'main/components/quote_of_the_day', locals: { } %>
</div>
</div>
</div>
<%# render partial: 'main/components/activity_bar', locals: { } %>
<div class="max-w-7xl mx-auto my-4 px-4 sm:px-6 lg:px-8">
<!-- Mobile-first: stack vertically, then side-by-side on larger screens -->
<div class="grid grid-cols-1 xl:grid-cols-8 gap-4 xl:gap-6">
<!-- Main content area - full width on mobile, 5/8 on xl -->
<div class="xl:col-span-5 space-y-6">
<% if @content %>
<div class="text-center">
<h2 class="text-sm leading-6 font-medium text-gray-500 mb-2">
Expand
<% if @universe_scope.present? %>
<%= link_to @universe_scope.name, @universe_scope, class: Universe.text_color %>
<% else %>
your worlds
<% end %>
</h2>
<%= render partial: 'main/components/serendipitous_question', locals: { } %>
</div>
<% end %>
<div class="text-center">
<h2 class="text-sm leading-6 font-medium text-gray-500 mb-2">
Create a new page
</h2>
<%= render partial: 'main/components/create_new_page_list', locals: { } %>
</div>
</div>
<!-- Primary Action Buttons -->
<div class="flex space-x-3">
<%= link_to new_character_path, class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-lg text-gray-700 bg-white hover:bg-gray-50 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200" do %>
<i class="material-icons mr-2 text-sm <%= Character.text_color %>"><%= Character.icon %></i>
Create Character
<% end %>
<!-- Sidebar content - full width on mobile, 3/8 on xl -->
<div class="xl:col-span-3 space-y-6">
<% if @most_recent_threads.any? %>
<div class="text-center">
<h2 class="text-sm leading-6 font-medium text-gray-500 hover:text-gray-800 mb-2">
<%= link_to 'Active discussions', thredded_path %>
</h2>
<%= render partial: 'main/components/active_discussions', locals: { discussions: @most_recent_threads } %>
</div>
<% end %>
<% if @recently_edited_pages %>
<div class="text-center">
<h2 class="text-sm leading-6 font-medium text-gray-500 hover:text-gray-800 mb-2">
<%= link_to 'Your recent updates', recent_content_path %>
</h2>
<%= render partial: 'main/components/recently_edited_pages', locals: { } %>
</div>
<% end %>
</div>
<!-- Content cards section - redesigned for Tailwind from MaterializeCSS -->
<% if @current_user_content && @current_user_content.except('Universe').values.flatten.any? %>
<div class="text-center mt-8 xl:col-span-8">
<h2 class="text-sm leading-6 font-medium text-gray-500 mb-4">Your Content</h2>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
<% @current_user_content.except('Universe').values.flatten.sort_by { |p| p.name.downcase }.each do |content_page| %>
<%= link_to send("edit_#{content_page.page_type.downcase}_path", content_page.id), class: 'block w-full group' do %>
<div class="fancy-card relative overflow-hidden rounded-xl shadow-lg hover:shadow-xl border border-gray-200/50 transition-all duration-300 hover:-translate-y-1">
<!-- Image container with fixed height -->
<div class="relative h-32 overflow-hidden rounded-t-xl">
<% if content_page.random_image_including_private.present? %>
<%= image_tag content_page.random_image_including_private, class: 'absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-110' %>
<% else %>
<!-- Fallback when no image is available -->
<div class="absolute inset-0 w-full h-full <%= content_page.color %> bg-opacity-20"></div>
<% end %>
<!-- Gradient overlay for title readability -->
<div class="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent"></div>
<!-- Content type badge -->
<div class="absolute top-3 right-3 z-20">
<div class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium <%= content_page.color %> backdrop-blur-sm text-white">
<i class="material-icons text-sm mr-1"><%= content_page.icon %></i>
<%= content_page.page_type %>
</div>
</div>
<!-- Title overlay at bottom of image -->
<div class="absolute bottom-0 left-0 right-0 p-4 z-10">
<h3 class="text-lg font-bold text-white line-clamp-2 group-hover:text-blue-300 transition-colors duration-200">
<%= content_page.name %>
</h3>
</div>
</div>
<!-- Footer below image -->
<div class="bg-white rounded-b-xl">
<!-- Stats and metadata -->
<div class="flex items-center justify-between border-t border-gray-200 px-4 py-3">
<div class="flex items-center space-x-3 text-xs text-gray-500">
<% if content_page.cached_word_count.present? %>
<div class="flex items-center space-x-1">
<i class="material-icons text-sm text-blue-500">description</i>
<span class="font-medium text-gray-700">
<%= number_with_delimiter content_page.cached_word_count %>
<%= 'word'.pluralize(content_page.cached_word_count) %>
</span>
</div>
<% end %>
</div>
<div class="flex items-center space-x-1 text-xs text-gray-500">
<i class="material-icons text-sm text-green-500">schedule</i>
<span>edited <%= time_ago_in_words content_page.updated_at %> ago</span>
</div>
</div>
</div>
</div>
<% end %>
<% end %>
</div>
</div>
<% random_enabled_page_type = (current_user.createable_content_types - [Character]).sample %>
<%= link_to new_polymorphic_path(random_enabled_page_type), class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-lg text-white #{random_enabled_page_type.color} hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200" do %>
<i class="material-icons mr-2 text-sm"><%= random_enabled_page_type.icon %></i>
Create <%= random_enabled_page_type.name %>
<% end %>
</div>
</div>
</main>
</div>
</div>
<div>
<%= render partial: 'main/components/create_new_page_list', locals: { } %>
</div>
<!-- Two-Column Hero Section: Expand Your Worlds -->
<% if @content %>
<div class="bg-white shadow-sm border-b border-gray-200 mb-12">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Two-Column Layout: 2/3 and 1/3 split -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Left Column: Serendipitous Question (2/3) -->
<div class="lg:col-span-2">
<%= render partial: 'main/components/serendipitous_question', locals: { } %>
</div>
<!-- Right Column: Recently Edited Pages (1/3) -->
<% if @recently_edited_pages %>
<div class="lg:col-span-1">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900 flex items-center">
<i class="material-icons mr-2 text-blue-600">history</i>
Recently Edited
</h3>
<%= link_to recent_content_path, class: "text-xs text-blue-600 hover:text-blue-700 font-medium" do %>
View all →
<% end %>
</div>
<!-- Recently Edited Pages Component -->
<div class="dashboard-card">
<div class="dashboard-card-content">
<%= render partial: 'main/components/recently_edited_pages', locals: { } %>
</div>
</div>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
<% if @most_edited_pages && @most_edited_pages.any? %>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-8">
<div class="flex items-center justify-between mb-8">
<h2 class="text-2xl font-bold text-gray-900 flex items-center">
<i class="material-icons mr-3 text-gray-600 text-3xl">library_books</i>
Most-Edited Pages
</h2>
<%= link_to recent_content_path, class: "text-sm text-blue-600 hover:text-blue-700 font-medium" do %>
View more →
<% end %>
</div>
<!-- Horizontal Scrolling Content Grid -->
<div class="overflow-x-auto pb-4">
<div class="flex space-x-4 min-w-max">
<% @most_edited_pages.first(20).each do |content_page, edit_count| %>
<%= link_to send("edit_#{content_page.page_type.downcase}_path", content_page.id), class: 'block flex-shrink-0' do %>
<div class="w-64 bg-white rounded-lg shadow-sm border border-gray-200 hover:shadow-md hover:-translate-y-1 transition-all duration-300 overflow-hidden">
<div class="relative h-32 overflow-hidden">
<% if content_page.random_image_including_private.present? %>
<%= image_tag content_page.random_image_including_private, class: 'w-full h-full object-cover' %>
<% else %>
<div class="w-full h-full <%= content_page.color %> bg-opacity-20 flex items-center justify-center">
<i class="material-icons text-4xl text-white opacity-80"><%= content_page.icon %></i>
</div>
<% end %>
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent"></div>
<div class="absolute top-2 right-2 inline-flex items-center px-2 py-1 rounded-full text-xs font-medium text-white backdrop-blur-sm <%= content_page.color %>">
<i class="material-icons text-xs mr-1"><%= content_page.icon %></i>
<%= content_page.page_type %>
</div>
<% if content_page.respond_to?(:favorite?) && content_page.favorite? %>
<div class="absolute top-2 left-2">
<i class="material-icons text-yellow-400 text-sm">star</i>
</div>
<% end %>
<h3 class="absolute bottom-2 left-2 right-2 text-white font-semibold text-sm line-clamp-2">
<%= content_page.name %>
</h3>
</div>
<div class="px-4 py-3 border-t border-gray-100">
<div class="flex items-center justify-between text-xs text-gray-500">
<span class="flex items-center space-x-1">
<i class="material-icons text-xs">edit</i>
<span><%= number_with_delimiter edit_count %> <%= 'edit'.pluralize(edit_count) %></span>
</span>
<% if content_page.cached_word_count.present? %>
<span class="flex items-center space-x-1">
<i class="material-icons text-xs">description</i>
<span><%= number_with_delimiter content_page.cached_word_count %> <%= 'word'.pluralize(content_page.cached_word_count) %></span>
</span>
<% end %>
</div>
</div>
</div>
<% end %>
<% end %>
</div>
</div>
</div>
</div>
<% end %>
<!-- Main Content Area -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Single Column Layout for remaining content -->
<div class="max-w-2xl mx-auto space-y-10">
<!-- What's New -->
<%= render partial: 'main/components/whats_new', locals: { } %>
<!-- Forum Activity -->
<% if @most_recent_threads.any? %>
<section>
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900 flex items-center">
<i class="material-icons mr-2 text-orange-600">forum</i>
<%= link_to 'Forum Activity', thredded_path, class: "hover:text-orange-600 transition-colors" %>
</h3>
</div>
<div class="dashboard-card">
<div class="dashboard-card-content">
<%= render partial: 'main/components/active_discussions', locals: { discussions: @most_recent_threads } %>
</div>
</div>
</section>
<% end %>
</div>
</div>
</div>
<style>
/* Button styles */
.fancy-button {
position: relative;
transform-style: preserve-3d;
background-size: 200% 200%;
animation: gradientShift 4s ease infinite;
/* Dashboard Card Styles */
.dashboard-card {
@apply bg-white rounded-2xl shadow-sm border border-gray-200 hover:shadow-lg transition-all duration-300;
}
.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);
}
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Card styles */
.fancy-card {
position: relative;
transform-style: preserve-3d;
}
.fancy-card:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(59, 130, 246, 0.2);
border-color: rgba(59, 130, 246, 0.3);
}
.fancy-card:active {
transform: translateY(-2px) scale(0.98);
transition-duration: 0.1s;
}
/* Enhanced text effects */
.fancy-card h3 {
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.fancy-card:hover h3 {
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.8);
}
/* Shimmer effect on hover */
.fancy-card::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s ease;
border-radius: 0.75rem;
pointer-events: none;
z-index: 5;
}
.fancy-card:hover::after {
left: 100%;
.dashboard-card-content {
@apply p-8;
}
/* Line clamp utilities */
@ -313,126 +212,90 @@
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Responsive adjustments */
@media (max-width: 1024px) {
.dashboard-card-content {
@apply p-6;
}
}
@media (max-width: 640px) {
.dashboard-card-content {
@apply p-4;
}
}
/* Smooth scrollbar for horizontal content */
.overflow-x-auto::-webkit-scrollbar {
height: 6px;
}
.overflow-x-auto::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 3px;
}
.overflow-x-auto::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
.overflow-x-auto::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Button effects
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 = '';
// Animate sections on scroll
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}
});
}, {
threshold: 0.1,
rootMargin: '50px'
});
// Card effects
const fancyCards = document.querySelectorAll('.fancy-card');
fancyCards.forEach(card => {
card.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('card-ripple');
this.appendChild(ripple);
setTimeout(() => {
ripple.remove();
}, 600);
});
// Add subtle floating animation
card.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-6px) scale(1.03)';
});
card.addEventListener('mouseleave', function() {
this.style.transform = '';
});
// Observe dashboard sections
document.querySelectorAll('section, .dashboard-card').forEach(element => {
element.style.opacity = '0';
element.style.transform = 'translateY(30px)';
element.style.transition = 'opacity 0.8s ease, transform 0.8s ease';
observer.observe(element);
});
// Smooth horizontal scrolling for content library
const contentLibrary = document.querySelector('.overflow-x-auto');
if (contentLibrary) {
let isScrolling = false;
contentLibrary.addEventListener('wheel', function(e) {
if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) return;
e.preventDefault();
if (!isScrolling) {
requestAnimationFrame(function() {
contentLibrary.scrollLeft += e.deltaY;
isScrolling = false;
});
isScrolling = true;
}
});
}
});
</script>
<style>
/* Button ripple effect */
.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;
}
}
/* Card ripple effect */
.card-ripple {
position: absolute;
border-radius: 50%;
background: rgba(59, 130, 246, 0.4);
transform: scale(0);
animation: cardRippleEffect 0.6s linear;
pointer-events: none;
z-index: 15;
}
@keyframes cardRippleEffect {
to {
transform: scale(4);
opacity: 0;
}
}
</style>
<%= render partial: 'javascripts/content_linking' %>