diff --git a/src/pages/index.astro b/src/pages/index.astro
index 73e38718..ec5f33da 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -1,4 +1,6 @@
---
+import "bootstrap-icons/font/bootstrap-icons.css"
+
import BaseLayout from "../layouts/BaseLayout.astro"
import Hero from "../components/Hero.astro"
import CardList from "../components/CardList.astro"
@@ -46,3 +48,4 @@ const terms = await getCollection("terms")
+
diff --git a/src/scripts/hero-action.ts b/src/scripts/hero-action.ts
index abf8c029..7e7ef27c 100644
--- a/src/scripts/hero-action.ts
+++ b/src/scripts/hero-action.ts
@@ -1,5 +1,5 @@
class Hero extends HTMLElement {
-
+
constructor() {
super()
@@ -29,7 +29,12 @@ class Hero extends HTMLElement {
searchInput.value = ""
if (selectedCategory === "") {
- cards.forEach((card: HTMLElement) => (card.style.display = ""))
+ cards.forEach((card: HTMLElement) => {
+ card.style.display = ""
+ card.removeAttribute('data-hidden-by-filter')
+ })
+ // Trigger pagination refresh
+ window.dispatchEvent(new CustomEvent('cardsFiltered'))
return
}
for (let i = 0; i < cards.length; i++) {
@@ -37,10 +42,14 @@ class Hero extends HTMLElement {
cards[i]!.getAttribute("data-categories")!.split(", ")
if (cardCategories.includes(selectedCategory)) {
;(cards[i] as HTMLElement).style.display = ""
+ ;(cards[i] as HTMLElement).removeAttribute('data-hidden-by-filter')
} else {
;(cards[i] as HTMLElement).style.display = "none"
+ ;(cards[i] as HTMLElement).setAttribute('data-hidden-by-filter', 'true')
}
}
+ // Trigger pagination refresh
+ window.dispatchEvent(new CustomEvent('cardsFiltered'))
})
let currentFocus = -1 // Track the currently focused item in the autocomplete list
@@ -168,10 +177,14 @@ class Hero extends HTMLElement {
filterByCategory(selectedCategory, cardCategories)
) {
;(cards[i] as HTMLElement).style.display = ""
+ ;(cards[i] as HTMLElement).removeAttribute('data-hidden-by-filter')
} else {
;(cards[i] as HTMLElement).style.display = "none"
+ ;(cards[i] as HTMLElement).setAttribute('data-hidden-by-filter', 'true')
}
}
+ // Trigger pagination refresh
+ window.dispatchEvent(new CustomEvent('cardsFiltered'))
}
}
}
diff --git a/src/scripts/paginate.test.ts b/src/scripts/paginate.test.ts
new file mode 100644
index 00000000..62a3b140
--- /dev/null
+++ b/src/scripts/paginate.test.ts
@@ -0,0 +1,354 @@
+import { describe, test, expect, beforeEach, vi } from "vitest"
+import { JSDOM } from "jsdom"
+
+// Global DOM variable
+let dom: JSDOM
+
+// Mock DOM environment
+const setupDOM = (cardCount: number = 10) => {
+ dom = new JSDOM(`
+
+
+
+
+
+ ${Array.from({ length: cardCount }, (_, i) =>
+ `
Card ${i + 1}
`
+ ).join('')}
+
+
+
+ `, {
+ url: "http://localhost", // Provide a URL to avoid localStorage issues
+ })
+
+ global.document = dom.window.document
+ global.window = dom.window as any
+ global.Event = dom.window.Event
+ global.CustomEvent = dom.window.CustomEvent
+
+ // Mock localStorage
+ const localStorageMock = {
+ getItem: vi.fn(),
+ setItem: vi.fn(),
+ removeItem: vi.fn(),
+ clear: vi.fn(),
+ length: 0,
+ key: vi.fn()
+ }
+ Object.defineProperty(dom.window, 'localStorage', {
+ value: localStorageMock,
+ writable: true
+ })
+ global.localStorage = localStorageMock
+}// Simple PaginationManager class for testing (extracted from paginate.ts)
+class PaginationManager {
+ private allCards: HTMLElement[]
+ private currentPage: number = 1
+ private itemsPerPage: number = 6
+ private totalPages: number = 1
+
+ constructor() {
+ this.allCards = Array.from(document.querySelectorAll(".card"))
+ this.init()
+ }
+
+ private init() {
+ this.calculateTotalPages()
+ this.setupEventListeners()
+ this.showCurrentPage()
+ this.updateButtonStates()
+ }
+
+ private setupEventListeners() {
+ const paginateSelect = document.getElementById("pagination-select") as HTMLSelectElement
+ const prevButton = document.getElementById("prev-button")
+ const nextButton = document.getElementById("next-button")
+
+ // Listen for filter changes
+ window.addEventListener('cardsFiltered', () => {
+ setTimeout(() => this.refreshPagination(), 10)
+ })
+
+ paginateSelect?.addEventListener("change", (e) => {
+ const target = e.target as HTMLSelectElement
+ this.itemsPerPage = parseInt(target.value)
+ this.currentPage = 1
+ this.calculateTotalPages()
+ this.showCurrentPage()
+ this.updateButtonStates()
+ })
+
+ prevButton?.addEventListener("click", () => {
+ if (this.currentPage > 1) {
+ this.currentPage--
+ this.showCurrentPage()
+ this.updateButtonStates()
+ }
+ })
+
+ nextButton?.addEventListener("click", () => {
+ if (this.currentPage < this.totalPages) {
+ this.currentPage++
+ this.showCurrentPage()
+ this.updateButtonStates()
+ }
+ })
+ }
+
+ private refreshPagination() {
+ this.currentPage = 1
+ this.calculateTotalPages()
+ this.showCurrentPage()
+ this.updateButtonStates()
+ }
+
+ private calculateTotalPages() {
+ const filteredCards = this.getFilteredCards()
+ this.totalPages = Math.ceil(filteredCards.length / this.itemsPerPage)
+ if (this.totalPages === 0) this.totalPages = 1
+ }
+
+ private showCurrentPage() {
+ const filteredCards = this.getFilteredCards()
+ const startIndex = (this.currentPage - 1) * this.itemsPerPage
+ const endIndex = startIndex + this.itemsPerPage
+
+ this.allCards.forEach((card) => {
+ if (card.getAttribute('data-hidden-by-filter') === 'true') {
+ card.style.display = "none"
+ } else {
+ card.style.display = "none"
+ }
+ })
+
+ filteredCards.slice(startIndex, endIndex).forEach((card) => {
+ card.style.display = "block"
+ })
+ }
+
+ private updateButtonStates() {
+ const prevButton = document.getElementById("prev-button") as HTMLButtonElement
+ const nextButton = document.getElementById("next-button") as HTMLButtonElement
+
+ if (prevButton) {
+ prevButton.disabled = this.currentPage === 1
+ prevButton.style.opacity = this.currentPage === 1 ? "0.5" : "1"
+ }
+
+ if (nextButton) {
+ nextButton.disabled = this.currentPage === this.totalPages
+ nextButton.style.opacity = this.currentPage === this.totalPages ? "0.5" : "1"
+ }
+ }
+
+ // Public methods for testing
+ public getCurrentPage(): number {
+ return this.currentPage
+ }
+
+ public getTotalPages(): number {
+ return this.totalPages
+ }
+
+ public getItemsPerPage(): number {
+ return this.itemsPerPage
+ }
+
+ public getVisibleCards(): HTMLElement[] {
+ return this.allCards.filter(card => card.style.display !== "none")
+ }
+
+ public getFilteredCards(): HTMLElement[] {
+ return this.allCards.filter(card => {
+ return card.getAttribute('data-hidden-by-filter') !== 'true'
+ })
+ }
+}
+
+describe("PaginationManager", () => {
+ let paginationManager: PaginationManager
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ test("initializes with correct default values", () => {
+ setupDOM(10)
+ paginationManager = new PaginationManager()
+
+ expect(paginationManager.getCurrentPage()).toBe(1)
+ expect(paginationManager.getItemsPerPage()).toBe(6)
+ expect(paginationManager.getTotalPages()).toBe(2) // 10 cards / 6 per page = 2 pages
+ })
+
+ test("shows correct number of cards on first page", () => {
+ setupDOM(10)
+ paginationManager = new PaginationManager()
+
+ const visibleCards = paginationManager.getVisibleCards()
+ expect(visibleCards).toHaveLength(6)
+ })
+
+ test("navigates to next page correctly", () => {
+ setupDOM(10)
+ paginationManager = new PaginationManager()
+
+ const nextButton = document.getElementById("next-button") as HTMLButtonElement
+ nextButton.click()
+
+ expect(paginationManager.getCurrentPage()).toBe(2)
+ const visibleCards = paginationManager.getVisibleCards()
+ expect(visibleCards).toHaveLength(4) // Remaining 4 cards on page 2
+ })
+
+ test("navigates to previous page correctly", () => {
+ setupDOM(10)
+ paginationManager = new PaginationManager()
+
+ // Go to page 2 first
+ const nextButton = document.getElementById("next-button") as HTMLButtonElement
+ nextButton.click()
+
+ // Then go back to page 1
+ const prevButton = document.getElementById("prev-button") as HTMLButtonElement
+ prevButton.click()
+
+ expect(paginationManager.getCurrentPage()).toBe(1)
+ const visibleCards = paginationManager.getVisibleCards()
+ expect(visibleCards).toHaveLength(6)
+ })
+
+ test("changes items per page when dropdown changes", () => {
+ setupDOM(15)
+ paginationManager = new PaginationManager()
+
+ const select = document.getElementById("pagination-select") as HTMLSelectElement
+ select.value = "12"
+ select.dispatchEvent(new Event("change"))
+
+ expect(paginationManager.getItemsPerPage()).toBe(12)
+ expect(paginationManager.getTotalPages()).toBe(2) // 15 cards / 12 per page = 2 pages
+ expect(paginationManager.getCurrentPage()).toBe(1) // Should reset to page 1
+ })
+
+ test("disables previous button on first page", () => {
+ setupDOM(10)
+ paginationManager = new PaginationManager()
+
+ const prevButton = document.getElementById("prev-button") as HTMLButtonElement
+ expect(prevButton.disabled).toBe(true)
+ expect(prevButton.style.opacity).toBe("0.5")
+ })
+
+ test("disables next button on last page", () => {
+ setupDOM(10)
+ paginationManager = new PaginationManager()
+
+ // Navigate to last page
+ const nextButton = document.getElementById("next-button") as HTMLButtonElement
+ nextButton.click()
+
+ expect(nextButton.disabled).toBe(true)
+ expect(nextButton.style.opacity).toBe("0.5")
+ })
+
+ test("handles single page correctly", () => {
+ setupDOM(3) // Only 3 cards, so 1 page with 6 items per page
+ paginationManager = new PaginationManager()
+
+ expect(paginationManager.getTotalPages()).toBe(1)
+
+ const prevButton = document.getElementById("prev-button") as HTMLButtonElement
+ const nextButton = document.getElementById("next-button") as HTMLButtonElement
+
+ expect(prevButton.disabled).toBe(true)
+ expect(nextButton.disabled).toBe(true)
+ })
+
+ test("calculates total pages correctly with different page sizes", () => {
+ setupDOM(25)
+ paginationManager = new PaginationManager()
+
+ // Test with 6 items per page
+ expect(paginationManager.getTotalPages()).toBe(5) // 25 / 6 = 4.17 -> 5 pages
+
+ // Change to 12 items per page
+ const select = document.getElementById("pagination-select") as HTMLSelectElement
+ select.value = "12"
+ select.dispatchEvent(new Event("change"))
+ expect(paginationManager.getTotalPages()).toBe(3) // 25 / 12 = 2.08 -> 3 pages
+
+ // Change to 48 items per page
+ select.value = "48"
+ select.dispatchEvent(new Event("change"))
+ expect(paginationManager.getTotalPages()).toBe(1) // 25 / 48 = 0.52 -> 1 page
+ })
+
+ test("prevents navigation beyond boundaries", () => {
+ setupDOM(6) // Exactly 1 page
+ paginationManager = new PaginationManager()
+
+ const prevButton = document.getElementById("prev-button") as HTMLButtonElement
+ const nextButton = document.getElementById("next-button") as HTMLButtonElement
+
+ // Try to go to previous page (should stay at 1)
+ prevButton.click()
+ expect(paginationManager.getCurrentPage()).toBe(1)
+
+ // Try to go to next page (should stay at 1)
+ nextButton.click()
+ expect(paginationManager.getCurrentPage()).toBe(1)
+ })
+
+ test("works with filtered cards", () => {
+ setupDOM(10)
+ paginationManager = new PaginationManager()
+
+ // Simulate filtering by marking some cards as hidden by filter
+ const cards = Array.from(document.querySelectorAll('.card'))
+ if (cards.length >= 3) {
+ cards[0]!.setAttribute('data-hidden-by-filter', 'true')
+ cards[1]!.setAttribute('data-hidden-by-filter', 'true')
+ cards[2]!.setAttribute('data-hidden-by-filter', 'true')
+ }
+
+ // Trigger pagination refresh with custom event
+ window.dispatchEvent(new CustomEvent('cardsFiltered'))
+
+ // Should now work with 7 visible cards instead of 10
+ expect(paginationManager.getTotalPages()).toBe(2) // 7 cards / 6 per page = 2 pages
+
+ // First page should show 6 cards
+ const visibleCards = paginationManager.getVisibleCards()
+ expect(visibleCards).toHaveLength(6)
+ })
+
+ test("resets to page 1 when filters change", async () => {
+ setupDOM(15)
+ paginationManager = new PaginationManager()
+
+ // Go to page 2
+ const nextButton = document.getElementById("next-button") as HTMLButtonElement
+ nextButton.click()
+ expect(paginationManager.getCurrentPage()).toBe(2)
+
+ // Simulate filter change
+ window.dispatchEvent(new CustomEvent('cardsFiltered'))
+
+ // Wait for the setTimeout in the event handler
+ await new Promise(resolve => setTimeout(resolve, 20))
+
+ // Should reset to page 1
+ expect(paginationManager.getCurrentPage()).toBe(1)
+ })
+})
diff --git a/src/scripts/paginate.ts b/src/scripts/paginate.ts
new file mode 100644
index 00000000..6679e945
--- /dev/null
+++ b/src/scripts/paginate.ts
@@ -0,0 +1,136 @@
+ class PaginationManager {
+ private allCards: HTMLElement[]
+ private currentPage: number = 1
+ private itemsPerPage: number = 10
+ private totalPages: number = 1
+
+ constructor() {
+ this.allCards = Array.from(document.querySelectorAll(".card"))
+ this.init()
+ this.observeFilterChanges()
+ }
+
+ private init() {
+ this.calculateTotalPages()
+ this.setupEventListeners()
+ this.showCurrentPage()
+ this.updateButtonStates()
+ }
+
+ private observeFilterChanges() {
+ // Listen for the custom event dispatched by hero-action.ts
+ window.addEventListener('cardsFiltered', () => {
+ setTimeout(() => this.refreshPagination(), 10)
+ })
+ }
+
+ private getFilteredCards(): HTMLElement[] {
+ // Get cards that are not hidden by filtering
+ return this.allCards.filter(card => {
+ return card.getAttribute('data-hidden-by-filter') !== 'true'
+ })
+ }
+
+ private refreshPagination() {
+ this.currentPage = 1 // Reset to first page when filters change
+ this.calculateTotalPages()
+ this.showCurrentPage()
+ this.updateButtonStates()
+ }
+
+ private setupEventListeners() {
+ const paginateSelect = document.getElementById(
+ "pagination-select"
+ ) as HTMLSelectElement
+ const prevButton = document.getElementById("prev-button")
+ const nextButton = document.getElementById("next-button")
+
+ // Handle dropdown change
+ paginateSelect?.addEventListener("change", (e) => {
+ const target = e.target as HTMLSelectElement
+ this.itemsPerPage = parseInt(target.value)
+ this.currentPage = 1 // Reset to first page
+ this.calculateTotalPages()
+ this.showCurrentPage()
+ this.updateButtonStates()
+ })
+
+ // Handle previous button click
+ prevButton?.addEventListener("click", () => {
+ if (this.currentPage > 1) {
+ this.currentPage--
+ this.showCurrentPage()
+ this.updateButtonStates()
+ }
+ })
+
+ // Handle next button click
+ nextButton?.addEventListener("click", () => {
+ if (this.currentPage < this.totalPages) {
+ this.currentPage++
+ this.showCurrentPage()
+ this.updateButtonStates()
+ }
+ })
+ }
+
+ private calculateTotalPages() {
+ const filteredCards = this.getFilteredCards()
+ this.totalPages = Math.ceil(filteredCards.length / this.itemsPerPage)
+ if (this.totalPages === 0) this.totalPages = 1
+ }
+
+ private showCurrentPage() {
+ const filteredCards = this.getFilteredCards()
+ const startIndex = (this.currentPage - 1) * this.itemsPerPage
+ const endIndex = startIndex + this.itemsPerPage
+
+ // First, hide all cards
+ this.allCards.forEach((card) => {
+ // If card is hidden by filter, keep it hidden
+ if (card.getAttribute('data-hidden-by-filter') === 'true') {
+ card.style.display = "none"
+ } else {
+ // For filtered cards, hide by default (pagination will show the correct ones)
+ card.style.display = "none"
+ }
+ })
+
+ // Show only the cards for current page from filtered results
+ filteredCards.slice(startIndex, endIndex).forEach((card) => {
+ card.style.display = "block"
+ })
+ }
+
+ private updateButtonStates() {
+ const prevButton = document.getElementById(
+ "prev-button"
+ ) as HTMLButtonElement
+ const nextButton = document.getElementById(
+ "next-button"
+ ) as HTMLButtonElement
+
+ // Disable/enable previous button
+ if (prevButton) {
+ prevButton.disabled = this.currentPage === 1
+ prevButton.style.opacity = this.currentPage === 1 ? "0.5" : "1"
+ }
+
+ // Disable/enable next button
+ if (nextButton) {
+ nextButton.disabled = this.currentPage === this.totalPages
+ nextButton.style.opacity =
+ this.currentPage === this.totalPages ? "0.5" : "1"
+ }
+ }
+ }
+
+ // Also initialize immediately if DOM is already loaded
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", () => {
+ new PaginationManager()
+ })
+ } else {
+ // Initialize pagination when DOM is loaded
+ new PaginationManager()
+ }
\ No newline at end of file
diff --git a/src/styles/global.css b/src/styles/global.css
index 3816abda..618ae2c6 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -15,7 +15,7 @@
select {
/* A reset of styles, including removing the default dropdown arrow */
appearance: none;
- /* Additional resets for further consistency */
+ /* Additional resets for further consistency */
background-color: transparent;
border: none;
padding: 0 1em 0 0;
@@ -54,10 +54,10 @@ select,
@font-face {
font-display: swap;
- font-family: 'JetBrains Mono';
+ font-family: "JetBrains Mono";
font-style: normal;
font-weight: 400;
- src: url('/fonts/jetbrains-mono-v18-latin-regular.woff2') format('woff2');
+ src: url("/fonts/jetbrains-mono-v18-latin-regular.woff2") format("woff2");
}
img {
@@ -68,7 +68,7 @@ img {
body {
background-color: #fcfcfc;
color: #000000;
- font-family: 'JetBrains Mono', monospace;
+ font-family: "JetBrains Mono", monospace;
margin: 0;
padding: 0;
}
@@ -157,7 +157,7 @@ body {
#autocomplete-list {
max-height: 200px; /* Ensure there's a maximum height */
- overflow-y: auto; /* Enable vertical scrolling */
+ overflow-y: auto; /* Enable vertical scrolling */
scrollbar-width: thin; /* Firefox */
position: absolute;
}
@@ -200,7 +200,8 @@ body {
.hero-section {
text-align: center;
- padding: 50px 20px;
+ padding: 10px 20px;
+ padding-bottom: 0%;
}
.hero-title {
@@ -231,7 +232,7 @@ body {
border-color: black;
color: #000000;
padding: 15px 30px;
- font-family: 'JetBrains Mono', monospace;
+ font-family: "JetBrains Mono", monospace;
cursor: pointer;
text-align: center;
border-radius: 5px;
@@ -294,7 +295,9 @@ body.dark-mode .explain-button:hover {
box-shadow: 5px 5px rgb(237, 237, 237);
}
-body.dark-mode .search-bar input, body.dark-mode #category-select {
+body.dark-mode .search-bar input,
+body.dark-mode #category-select,
+body.dark-mode #pagination-select {
background-color: #444444;
color: #ffffff !important;
border-color: #d2d2d2;
@@ -304,11 +307,21 @@ body.dark-mode .search-bar i {
color: #cccccc;
}
+body.dark-mode #pagination-select {
+ background: url("data:image/svg+xml,
")
+ no-repeat;
+ background-position: calc(100% - 1.2rem) center !important;
+ -moz-appearance: none !important;
+ -webkit-appearance: none !important;
+ appearance: none !important;
+}
+
body.dark-mode #category-select {
- background: url("data:image/svg+xml,
") no-repeat;
+ background: url("data:image/svg+xml,
")
+ no-repeat;
background-position: calc(100% - 1.2rem) center !important;
- -moz-appearance:none !important;
- -webkit-appearance: none !important;
+ -moz-appearance: none !important;
+ -webkit-appearance: none !important;
appearance: none !important;
padding-right: 2rem !important;
}
@@ -340,17 +353,20 @@ body.dark-mode input[type="text"]::placeholder {
gap: 20px;
}
-.search-bar, .category-filter {
+.search-bar,
+.category-filter,
+.pagination-filter {
position: relative;
display: inline-block;
- font-family: 'JetBrains Mono', monospace;
+ font-family: "JetBrains Mono", monospace;
}
-.search-bar input, #category-select {
+.search-bar input,
+#category-select {
font-size: 1em;
border: 1px solid #000000;
border-radius: 35px;
- font-family: 'JetBrains Mono', monospace;
+ font-family: "JetBrains Mono", monospace;
}
.search-bar input {
@@ -372,10 +388,11 @@ body.dark-mode input[type="text"]::placeholder {
text-align: center;
color: #555;
- background: url("data:image/svg+xml,
") no-repeat;
+ background: url("data:image/svg+xml,
")
+ no-repeat;
background-position: calc(100% - 1.2rem) center !important;
- -moz-appearance:none !important;
- -webkit-appearance: none !important;
+ -moz-appearance: none !important;
+ -webkit-appearance: none !important;
appearance: none !important;
padding-right: 2rem !important;
}
@@ -422,12 +439,65 @@ body.dark-mode select option {
margin-bottom: 20px;
}
+.pagination-bar {
+ display: flex;
+ gap: 0.25rem;
+ justify-content: center;
+ justify-self: end;
+ align-items: center;
+ padding: 10px;
+ width: fit-content;
+}
+
+#pagination-select {
+ font-size: 1em;
+ border: 1px solid #000000;
+ font-family: "JetBrains Mono", monospace;
+ padding: 10px;
+ width: 75px;
+ border-radius: 35px;
+ text-align: left;
+ color: #555;
+ background: url("data:image/svg+xml,
")
+ no-repeat;
+ background-position: calc(100% - 1.2rem) center !important;
+ -moz-appearance: none !important;
+ -webkit-appearance: none !important;
+ appearance: none !important;
+}
+
+.navigation-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ width: 40px;
+ height: 40px;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.navigation-button i {
+ font-size: 2rem;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.navigation-button:hover i {
+ transform: scale(1.1);
+ transition: transform 0.2s ease;
+}
+
.explain-button {
background-color: #000;
color: #fff;
padding: 18px;
border-radius: 5px;
- font-family: 'JetBrains Mono', monospace;
+ font-family: "JetBrains Mono", monospace;
cursor: pointer;
width: 85%;
transition: all 0.3s linear;
@@ -462,7 +532,7 @@ body.dark-mode select option {
border-radius: 10px;
width: 80%;
max-width: 600px;
- font-family: 'JetBrains Mono', monospace;
+ font-family: "JetBrains Mono", monospace;
overflow-y: auto;
max-height: 70vh;
}
@@ -499,7 +569,7 @@ body.modal-open {
border-radius: 5px;
font-size: 15px;
text-align: center;
- font-family: 'JetBrains Mono', monospace;
+ font-family: "JetBrains Mono", monospace;
cursor: pointer;
}