mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
wip dashboard redesign (again lol)
This commit is contained in:
parent
7c06b27f60
commit
3ce3481a1a
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
198
app/views/main/components/_content_library.html.erb
Normal file
198
app/views/main/components/_content_library.html.erb
Normal 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>
|
||||
@ -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>
|
||||
|
||||
55
app/views/main/components/_quote_of_the_day.html.erb
Normal file
55
app/views/main/components/_quote_of_the_day.html.erb
Normal 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>
|
||||
@ -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>
|
||||
|
||||
@ -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 %>
|
||||
55
app/views/main/components/_whats_new.html.erb
Normal file
55
app/views/main/components/_whats_new.html.erb
Normal 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>
|
||||
@ -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' %>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user