mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
479 lines
24 KiB
Plaintext
479 lines
24 KiB
Plaintext
<%
|
|
page_title = "#{@content.name} Gallery"
|
|
page_description = "Gallery of images for #{@content.name}, a fictional #{@content.class.name.downcase} on Notebook.ai"
|
|
|
|
set_meta_tags title: page_title,
|
|
description: page_description,
|
|
image_src: @content.first_public_image,
|
|
og: { type: 'website' },
|
|
twitter: { card: 'photo', image: @content.first_public_image }
|
|
%>
|
|
|
|
<div
|
|
x-data="{ showEditMode: <%= params.fetch('editing', false) %> }"
|
|
>
|
|
<nav class="bg-white h-16 border-b border-gray-200 flex sticky top-14 z-40" aria-label="Breadcrumb">
|
|
<ol role="list" class="flex-1 mx-auto px-4 flex space-x-4 sm:px-6 lg:px-8">
|
|
<li class="flex">
|
|
<div class="flex items-center">
|
|
<%= link_to root_path, class: 'text-gray-400 hover:text-gray-500' do %>
|
|
<i class="material-icons flex-shrink-0 text-2xl relative top-1">dashboard</i>
|
|
<span class="sr-only">Home</span>
|
|
<% end %>
|
|
</div>
|
|
</li>
|
|
|
|
<li class="flex">
|
|
<div class="flex items-center">
|
|
<svg class="flex-shrink-0 w-6 h-full text-gray-200" viewBox="0 0 24 44" preserveAspectRatio="none" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
<path d="M.293 0l22 22-22 22h1.414l22-22-22-22H.293z" />
|
|
</svg>
|
|
<%= link_to @serialized_content.class_name.pluralize, send("#{@serialized_content.class_name.downcase.pluralize}_path"), class: 'ml-4 text-sm font-medium text-gray-500 hover:text-gray-700' %>
|
|
</div>
|
|
</li>
|
|
|
|
<li class="flex">
|
|
<div class="flex items-center">
|
|
<svg class="flex-shrink-0 w-6 h-full text-gray-200" viewBox="0 0 24 44" preserveAspectRatio="none" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
<path d="M.293 0l22 22-22 22h1.414l22-22-22-22H.293z" />
|
|
</svg>
|
|
<%= link_to @serialized_content.name, @serialized_content.raw_model, class: 'ml-4 text-sm font-medium text-gray-500 hover:text-gray-700', aria: { current: 'page' } %>
|
|
</div>
|
|
</li>
|
|
</ol>
|
|
|
|
<div class="flex flex-row pt-3">
|
|
<% if @content.updatable_by?(current_user) %>
|
|
<div class="mr-3" x-show="showEditMode">
|
|
<button type="button" class="w-full bg-notebook-blue border border-notebook-blue rounded-md shadow-sm px-4 py-2 inline-flex items-center text-sm font-medium text-gray-200 hover:bg-blue-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
@click="showEditMode = false"
|
|
>
|
|
<i class="material-icons text-2xl leading-3 mr-3">edit</i>
|
|
Read Mode
|
|
</button>
|
|
</div>
|
|
<div class="mr-3" x-show="!showEditMode">
|
|
<button type="button" class="w-full bg-notebook-blue border border-notebook-blue rounded-md shadow-sm px-4 py-2 inline-flex items-center text-sm font-medium text-gray-200 hover:bg-blue-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
@click="showEditMode = true"
|
|
>
|
|
<i class="material-icons text-2xl leading-3 mr-3">edit</i>
|
|
Edit Mode
|
|
</button>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="relative min-h-full flex flex-col">
|
|
<!-- 3 column wrapper -->
|
|
<div class="flex-grow w-full lg:flex min-h-screen">
|
|
<!-- Left sidebar & main wrapper -->
|
|
<div class="flex-1 min-w-0 xl:flex">
|
|
<% if @serialized_content.class_name == Universe.name %>
|
|
<%= render partial: 'content/tailwind_components/universe_context_sidenav', locals: { content: @serialized_content } %>
|
|
<% else %>
|
|
<%= render partial: 'content/tailwind_components/content_context_sidenav', locals: { content: @serialized_content } %>
|
|
<% end %>
|
|
|
|
<div class="bg-white lg:min-w-0 lg:flex-1 border-b border-gray-200 shadow">
|
|
<div class="pl-4 pr-6 pt-4 pb-4 border-b border-t border-gray-200 sm:pl-6 lg:pl-8 xl:pl-6 xl:pt-6 xl:border-t-0">
|
|
<%= render partial: 'content/tailwind_components/content_header', locals: { content: @serialized_content } %>
|
|
</div>
|
|
|
|
<!-- Gallery content -->
|
|
<% if @images.empty? && @basil_images.empty? %>
|
|
<!-- Empty state -->
|
|
<div class="flex flex-col items-center justify-center py-20">
|
|
<div class="bg-gray-100 rounded-full p-6 mb-4">
|
|
<i class="material-icons text-gray-400 text-5xl"><%= @content.class.icon %></i>
|
|
</div>
|
|
<h2 class="text-2xl font-medium text-gray-700 mb-2">No images yet</h2>
|
|
<p class="text-gray-500 mb-6">This <%= @content.class.name.downcase %> doesn't have any images in its gallery.</p>
|
|
<% if @content.updatable_by?(current_user) %>
|
|
<%= link_to send("edit_#{@content.class.name.downcase}_path", @content, anchor: "uploads"),
|
|
class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-notebook-blue hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
|
<i class="material-icons text-sm mr-2">add_photo_alternate</i>
|
|
Add Images
|
|
<% end %>
|
|
<% end %>
|
|
</div>
|
|
<% else %>
|
|
<%
|
|
# Use the helper to combine and sort images consistently by position
|
|
# This keeps the order consistent with the MaterializeCSS version
|
|
combined_images = combine_and_sort_gallery_images(@images, @basil_images)
|
|
%>
|
|
|
|
<!-- PRIMARY image if exists -->
|
|
<% if @primary_image %>
|
|
<div class="px-5 py-2 mx-auto pt-6">
|
|
<div class="flex flex-wrap -m-1 md:-m-2">
|
|
<div class="flex flex-wrap w-2/3">
|
|
<div class="w-full p-1 md:p-2">
|
|
<%= link_to @primary_image.src.url do %>
|
|
<%= image_tag @primary_image.src.url, class: 'block object-cover object-center w-full h-full rounded hover:opacity-90', alt: "Primary image for #{@content.name}" %>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-wrap w-1/3 p-12">
|
|
<div>
|
|
<h2 class="font-bold w-full"><%= @content.name %></h2>
|
|
<div class="text-sm text-gray-400">
|
|
A <%= @content.class.name.downcase %>
|
|
by <%= link_to @content.user.name, @content.user, class: User.text_color %>
|
|
</div>
|
|
</div>
|
|
|
|
<div x-show="showEditMode" class="mt-4">
|
|
<%=
|
|
button_to 'Delete this image', image_deletion_path(@primary_image.id),
|
|
class: 'js-remove-image text-xs bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-lg',
|
|
method: 'delete',
|
|
remote: true,
|
|
data: { confirm: "Are you sure? This can't be undone." }
|
|
%>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
|
|
<!-- All other images in a grid -->
|
|
<div class="p-5">
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<% combined_images.each_with_index do |image_item, index| %>
|
|
<div class="gallery-item relative overflow-hidden rounded-lg shadow-md">
|
|
<% if image_item[:type] == 'image_upload' %>
|
|
<%= link_to image_item[:data].src(:original), target: "_blank", class: "block" do %>
|
|
<%= image_tag image_item[:data].src(:medium),
|
|
class: "w-full h-60 object-cover transform hover:scale-105 transition duration-300",
|
|
alt: "Image of #{@content.name}" %>
|
|
<% end %>
|
|
|
|
<% if @content.updatable_by?(current_user) %>
|
|
<div x-show="showEditMode" class="absolute top-2 right-2">
|
|
<%=
|
|
button_to 'Delete', image_deletion_path(image_item[:data].id),
|
|
class: 'bg-red-500 hover:bg-red-700 text-white text-xs py-1 px-2 rounded',
|
|
method: :delete,
|
|
remote: true,
|
|
data: { confirm: "Are you sure? This can't be undone." }
|
|
%>
|
|
</div>
|
|
<% end %>
|
|
|
|
<div class="p-3 bg-white dark:bg-gray-800 flex items-center text-sm text-gray-500">
|
|
<i class="material-icons text-xs mr-1">schedule</i>
|
|
<%= time_ago_in_words(image_item[:data].created_at) %> ago
|
|
</div>
|
|
<% elsif image_item[:type] == 'basil_commission' %>
|
|
<%
|
|
# For BasilCommission, access the image through rails_blob_path
|
|
if image_item[:data].image.attached?
|
|
basil_url = rails_blob_path(image_item[:data].image, disposition: "attachment")
|
|
else
|
|
basil_url = asset_path("missing-image.jpg")
|
|
end
|
|
%>
|
|
<%= link_to basil_url, target: "_blank", class: "block" do %>
|
|
<%= image_tag basil_url,
|
|
class: "w-full h-60 object-cover transform hover:scale-105 transition duration-300",
|
|
alt: "Generated image of #{@content.name}" %>
|
|
<% end %>
|
|
|
|
<% if @content.updatable_by?(current_user) %>
|
|
<div x-show="showEditMode" class="absolute top-2 right-2">
|
|
<%=
|
|
button_to 'Delete', basil_delete_path(image_item[:data]),
|
|
class: 'bg-red-500 hover:bg-red-700 text-white text-xs py-1 px-2 rounded',
|
|
method: :delete,
|
|
remote: true,
|
|
data: { confirm: "Are you sure? This can't be undone." }
|
|
%>
|
|
</div>
|
|
<% end %>
|
|
|
|
<div class="p-3 bg-white dark:bg-gray-800 flex items-center text-sm text-gray-500">
|
|
<i class="material-icons text-xs mr-1">schedule</i>
|
|
<% if image_item[:data].respond_to?(:saved_at) %>
|
|
<%= time_ago_in_words(image_item[:data].saved_at) %> ago
|
|
<% else %>
|
|
<%= time_ago_in_words(Time.current) %> ago
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
|
|
<% if @content.updatable_by?(current_user) %>
|
|
<div x-show="showEditMode" class="p-5">
|
|
<h3 class="text-lg font-medium text-gray-700 mb-4">Upload more images</h3>
|
|
<%= form_tag upload_image_path(content_type: @content.class.name, content_id: @content.id),
|
|
multipart: true, class: "flex items-center space-x-2" do %>
|
|
<%= file_field_tag 'image_uploads[]', multiple: true, accept: 'image/*',
|
|
class: "border border-gray-300 rounded-md py-2 px-3 text-sm" %>
|
|
<%= submit_tag 'Upload', class: "bg-notebook-blue text-white px-4 py-2 rounded hover:bg-blue-600 transition" %>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right pane -->
|
|
<div class="bg-gray-50 pr-4 sm:pr-6 lg:pr-8 lg:flex-shrink-0 lg:border-l lg:border-gray-200 xl:pr-0">
|
|
<div class="pl-6 lg:w-80 px-6 pt-6 space-y-8" x-show="showEditMode">
|
|
<div class="lg:flex lg:items-center lg:justify-between">
|
|
<div class="flex-1 min-w-0">
|
|
<h2 class="font-bold leading-tight text-gray-900">Gallery customization</h2>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center mb-4">
|
|
<!-- Enabled: "bg-indigo-600", Not Enabled: "bg-gray-200" -->
|
|
<button type="button" class="bg-gray-200 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-notebook-blue" role="switch" aria-checked="false" aria-labelledby="annual-billing-label">
|
|
<!-- Enabled: "translate-x-5", Not Enabled: "translate-x-0" -->
|
|
<span aria-hidden="true" class="translate-x-0 pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"></span>
|
|
</button>
|
|
<span class="ml-3" id="annual-billing-label">
|
|
<span class="text-sm font-medium text-gray-900">Enable Gallery</span>
|
|
<span class="text-sm text-gray-500">to others</span>
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center mb-4">
|
|
<!-- Enabled: "bg-indigo-600", Not Enabled: "bg-gray-200" -->
|
|
<button type="button" class="bg-gray-200 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-notebook-blue" role="switch" aria-checked="false" aria-labelledby="annual-billing-label">
|
|
<!-- Enabled: "translate-x-5", Not Enabled: "translate-x-0" -->
|
|
<span aria-hidden="true" class="translate-x-0 pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"></span>
|
|
</button>
|
|
<span class="ml-3" id="annual-billing-label">
|
|
<span class="text-sm font-medium text-gray-900">Enable Comments</span>
|
|
<span class="text-sm text-gray-500">from followers</span>
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center mb-4">
|
|
<!-- Enabled: "bg-indigo-600", Not Enabled: "bg-gray-200" -->
|
|
<button type="button" class="bg-gray-200 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-notebook-blue" role="switch" aria-checked="false" aria-labelledby="annual-billing-label">
|
|
<!-- Enabled: "translate-x-5", Not Enabled: "translate-x-0" -->
|
|
<span aria-hidden="true" class="translate-x-0 pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"></span>
|
|
</button>
|
|
<span class="ml-3" id="annual-billing-label">
|
|
<span class="text-sm font-medium text-gray-900">Enable Comments</span>
|
|
<span class="text-sm text-gray-500">from anyone</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h2 class="text-sm font-semibold">Upload storage</h2>
|
|
<div class="w-full bg-gray-200 rounded-full my-1 dark:bg-gray-700">
|
|
<div class="bg-notebook-blue h-4 rounded-full text-center leading-tight text-xs text-white" style="width: 65%">65%</div>
|
|
</div>
|
|
<p class="text-xs">
|
|
You've used <strong>0GB</strong> (X%) of your 0GB storage.
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<h2 class="text-sm font-semibold">Generate new images</h2>
|
|
<p class="text-xs">
|
|
Our visualizer can also generate new images of <%= @content.name %> for you from your
|
|
existing descriptions.
|
|
<% unless current_user.on_premium_plan? %>
|
|
The first <%= BasilService::FREE_IMAGE_LIMIT %> images are free!
|
|
<% end %>
|
|
<%= link_to basil_content_path(@content.page_type, @content.id), class: 'inline-block bg-pink-500 hover:bg-pink-400 my-2 px-2 flex items-center text-white rounded-lg' do %>
|
|
<i class="material-icons text-sm mr-2">palette</i>
|
|
<span class="flex-1">See available styles</span>
|
|
<i class="material-icons text-sm">chevron_right</i>
|
|
<% end %>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- About section -->
|
|
<div class="bg-gray-50 py-16">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="text-center">
|
|
<h2 class="text-3xl font-extrabold tracking-tight text-gray-900 sm:text-4xl">About <%= @content.name %></h2>
|
|
<div class="mt-3 flex justify-center">
|
|
<div class="inline-flex items-center px-3 py-0.5 rounded-full text-sm font-medium bg-<%= @content.class.color %>-100 text-<%= @content.class.color %>-800">
|
|
<i class="material-icons mr-1 text-sm"><%= @content.class.icon %></i>
|
|
<%= @content.class.name %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-12 max-w-3xl mx-auto">
|
|
<%
|
|
# Find the overview category if it exists
|
|
overview_category = @serialized_content&.data&.fetch(:categories, [])&.find { |cat|
|
|
cat[:name].downcase == 'overview' || cat[:name].downcase == 'appearance'
|
|
} if @serialized_content.present?
|
|
%>
|
|
|
|
<% if overview_category.present? && overview_category[:fields].present? %>
|
|
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
|
<% overview_category[:fields].reject { |field| field[:hidden] }.each do |field| %>
|
|
<%
|
|
# Skip empty fields
|
|
next if field[:value].blank?
|
|
|
|
# Skip private fields
|
|
next if field[:label].to_s.start_with?("Private") && !(user_signed_in? && @content.user == current_user)
|
|
%>
|
|
|
|
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
|
<h3 class="text-xs font-medium tracking-wider uppercase text-gray-500 mb-1">
|
|
<%= field[:label] %>
|
|
</h3>
|
|
<div class="text-sm text-gray-900">
|
|
<% if field[:value].is_a?(String) %>
|
|
<%= ContentFormatterService.show(text: field[:value], viewing_user: current_user) %>
|
|
<% elsif field[:value].is_a?(Array) %>
|
|
<%= field[:value].join(", ") %>
|
|
<% else %>
|
|
<%= field[:value].to_s %>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% else %>
|
|
<div class="bg-white shadow overflow-hidden sm:rounded-lg p-8 text-center text-gray-500">
|
|
<p>No overview information available for this <%= @content.class.name.downcase %>.</p>
|
|
</div>
|
|
<% end %>
|
|
|
|
<div class="mt-8 text-center">
|
|
<%= link_to @content, class: "inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-notebook-blue hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
|
<i class="material-icons mr-2">visibility</i>
|
|
View full <%= @content.class.name.downcase %> page
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Related content section (when available) -->
|
|
<% if @other_content.any? %>
|
|
<div class="bg-white py-16">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<h2 class="text-2xl font-extrabold tracking-tight text-gray-900 flex items-center">
|
|
<i class="material-icons mr-2 text-<%= @content.class.color %>-600"><%= @content.class.icon %></i>
|
|
More <%= @content.class.name.pluralize %> in this Universe
|
|
</h2>
|
|
|
|
<div class="mt-8 grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-4 xl:gap-x-8">
|
|
<% @other_content.each do |related| %>
|
|
<%= link_to send("gallery_#{related.class.name.downcase}_path", related), class: "group" do %>
|
|
<div class="w-full aspect-w-1 aspect-h-1 bg-gray-200 rounded-lg overflow-hidden xl:aspect-w-7 xl:aspect-h-8">
|
|
<%
|
|
related_image = ImageUpload.where(content_type: related.class.name, content_id: related.id).first
|
|
related_basil = BasilCommission.where(entity: related).where.not(saved_at: nil).first
|
|
|
|
if related_image
|
|
image_url = related_image.src(:medium)
|
|
elsif related_basil
|
|
image_url = related_basil.image
|
|
else
|
|
image_url = asset_path("card-headers/#{related.class.name.downcase.pluralize}.jpg")
|
|
end
|
|
%>
|
|
<div class="w-full h-60 bg-cover bg-center group-hover:opacity-75 transition duration-300"
|
|
style="background-image: url('<%= image_url %>');">
|
|
<div class="w-full h-full bg-gradient-to-t from-black/70 to-transparent flex items-end">
|
|
<h3 class="p-4 text-lg font-medium text-white"><%= related.name %></h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
|
|
<!-- Lightbox for images -->
|
|
<div id="gallery-lightbox" class="fixed inset-0 z-50 hidden bg-black/90 flex items-center justify-center">
|
|
<button id="close-lightbox" class="absolute top-4 right-4 text-white text-4xl">×</button>
|
|
<button id="prev-image" class="absolute left-4 text-white text-4xl"><</button>
|
|
<button id="next-image" class="absolute right-4 text-white text-4xl">></button>
|
|
<img id="lightbox-image" class="max-h-[90vh] max-w-[90vw] object-contain" src="" alt="Image in lightbox">
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Set up lightbox functionality
|
|
const lightbox = document.getElementById('gallery-lightbox');
|
|
const lightboxImg = document.getElementById('lightbox-image');
|
|
const closeBtn = document.getElementById('close-lightbox');
|
|
const prevBtn = document.getElementById('prev-image');
|
|
const nextBtn = document.getElementById('next-image');
|
|
|
|
const galleryImages = document.querySelectorAll('.gallery-item img');
|
|
let currentIndex = 0;
|
|
const imageUrls = [];
|
|
|
|
// Collect all image URLs
|
|
galleryImages.forEach(img => {
|
|
const url = img.parentElement.href;
|
|
imageUrls.push(url);
|
|
|
|
img.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
currentIndex = imageUrls.indexOf(img.parentElement.href);
|
|
openLightbox();
|
|
});
|
|
});
|
|
|
|
function openLightbox() {
|
|
lightboxImg.src = imageUrls[currentIndex];
|
|
lightbox.classList.remove('hidden');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
|
|
function closeLightbox() {
|
|
lightbox.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
}
|
|
|
|
function showPrevImage() {
|
|
currentIndex = (currentIndex - 1 + imageUrls.length) % imageUrls.length;
|
|
lightboxImg.src = imageUrls[currentIndex];
|
|
}
|
|
|
|
function showNextImage() {
|
|
currentIndex = (currentIndex + 1) % imageUrls.length;
|
|
lightboxImg.src = imageUrls[currentIndex];
|
|
}
|
|
|
|
closeBtn.addEventListener('click', closeLightbox);
|
|
prevBtn.addEventListener('click', showPrevImage);
|
|
nextBtn.addEventListener('click', showNextImage);
|
|
|
|
// Close on overlay click
|
|
lightbox.addEventListener('click', function(e) {
|
|
if (e.target === lightbox) {
|
|
closeLightbox();
|
|
}
|
|
});
|
|
|
|
// Keyboard navigation
|
|
document.addEventListener('keydown', function(e) {
|
|
if (!lightbox.classList.contains('hidden')) {
|
|
if (e.key === 'Escape') closeLightbox();
|
|
if (e.key === 'ArrowLeft') showPrevImage();
|
|
if (e.key === 'ArrowRight') showNextImage();
|
|
}
|
|
});
|
|
});
|
|
</script> |