From 1a016b4b950860d4c84102639458e07bfacdf397 Mon Sep 17 00:00:00 2001 From: MusicalBean Date: Sat, 26 Aug 2023 21:33:17 -0700 Subject: [PATCH] Add toggle to hide non-open traders --- .../ContentScript/element-factories/toggle.ts | 152 +++++ .../page-elements/profile-link.ts | 55 ++ .../page-elements/trade-finder-row.ts | 8 + .../page-enhancers/trade-finder-enhancer.ts | 51 +- .../trade-finder-theirs-enhancer.ts | 51 +- .../page-enhancers/who-owns-enhancer.ts | 44 +- .../page-enhancers/who-wants-enhancer.ts | 57 +- source/ContentScript/settings.ts | 13 + source/assets/css/extension.css | 607 ++++++++++++++++++ 9 files changed, 1023 insertions(+), 15 deletions(-) create mode 100644 source/ContentScript/element-factories/toggle.ts create mode 100644 source/ContentScript/settings.ts diff --git a/source/ContentScript/element-factories/toggle.ts b/source/ContentScript/element-factories/toggle.ts new file mode 100644 index 0000000..06e29a0 --- /dev/null +++ b/source/ContentScript/element-factories/toggle.ts @@ -0,0 +1,152 @@ +export default class Toggle { + on: boolean = false; + + constructor(public element: HTMLDivElement) { + this.setupListeners(); + } + + setupListeners() { + this.element + .querySelector("button")! + .addEventListener("click", () => this.toggle()); + } + + toggle() { + this.on = !this.on; + + if (this.on) { + this.switchOn(); + } else { + this.switchOff(); + } + } + + onToggle(callback: (on: boolean) => void) { + this.element + .querySelector("button")! + .addEventListener("click", () => + setTimeout(() => callback(this.on), 0) + ); + } + + switchOn() { + this.element + .querySelector("button")! + .setAttribute("aria-checked", "true"); + this.element + .querySelector("button span")! + .classList.remove("translate-x-0"); + this.element + .querySelector("button span")! + .classList.add("translate-x-5"); + + this.element.querySelector("button")!.classList.remove("bg-slate-200"); + this.element.querySelector("button")!.classList.add("bg-green-600"); + } + + switchOff() { + this.element + .querySelector("button")! + .setAttribute("aria-checked", "false"); + this.element + .querySelector("button span")! + .classList.remove("translate-x-5"); + this.element + .querySelector("button span")! + .classList.add("translate-x-0"); + + this.element.querySelector("button")!.classList.remove("bg-green-600"); + this.element.querySelector("button")!.classList.add("bg-slate-200"); + } + + static create(): Toggle { + let div = document.createElement("div"); + div.classList.add("flex", "items-center"); + + let button = document.createElement("button"); + button.type = "button"; + button.classList.add( + "relative", + "inline-flex", + "h-6", + "w-11", + "flex-shrink-0", + "cursor-pointer", + "rounded-full", + "border-2", + "border-transparent", + "bg-slate-200", + "transition-colors", + "duration-200", + "ease-in-out", + "focus:outline-none", + "focus:ring-2", + "focus:ring-indigo-600", + "focus:ring-offset-2" + ); + button.setAttribute("role", "switch"); + button.setAttribute("aria-checked", "false"); + button.setAttribute("aria-labelledby", "toggle-label"); + + let handle = document.createElement("span"); + handle.setAttribute("aria-hidden", "true"); + handle.classList.add( + "pointer-events-none", + "inline-block", + "h-5", + "w-5", + "translate-x-0", + "transform", + "rounded-full", + "bg-white", + "shadow", + "ring-0", + "transition", + "duration-200", + "ease-in-out" + ); + button.appendChild(handle); + + let span = document.createElement("span"); + span.classList.add("ml-[0.75rem]", "text-sm"); + span.id = "toggle-label"; + + let spanChild1 = document.createElement("span"); + spanChild1.classList.add("font-medium", "text-slate-900"); + spanChild1.innerText = "Only show"; + + let spanChild2 = document.createElement("span"); + spanChild2.classList.add( + "relative", + "ml-[14px]", + "mr-1.5", + "inline-block" + ); + + let spanGrandchild1 = document.createElement("span"); + spanGrandchild1.classList.add( + "absolute", + "-bottom-[3px]", + "-left-1.5", + "-right-1.5", + "-top-0.5", + "rounded-md", + "bg-green-100" + ); + + let spanGrandchild2 = document.createElement("span"); + spanGrandchild2.classList.add("relative", "text-green-950"); + spanGrandchild2.innerText = "open to trades"; + + spanChild2.appendChild(spanGrandchild1); + spanChild2.appendChild(spanGrandchild2); + + span.appendChild(spanChild1); + span.appendChild(spanChild2); + + div.appendChild(button); + div.appendChild(span); + + return new Toggle(div); + } +} diff --git a/source/ContentScript/page-elements/profile-link.ts b/source/ContentScript/page-elements/profile-link.ts index 587215c..e274f69 100644 --- a/source/ContentScript/page-elements/profile-link.ts +++ b/source/ContentScript/page-elements/profile-link.ts @@ -28,6 +28,16 @@ export default class ProfileLink { : this.element; } + get open(): boolean { + if (this.element.nextElementSibling?.tagName !== "SPAN") { + return false; + } + + return (( + this.element.nextElementSibling + )).innerText.includes("Open to trades"); + } + addOwnsBadge() { let trader = this.trader; @@ -76,4 +86,49 @@ export default class ProfileLink { ) ); } + + hide() { + this.element.style.display = "none"; + + // If the next element is text, hide it too. + if (this.element.nextElementSibling?.nodeType === Node.TEXT_NODE) { + (this.element.nextElementSibling).style.display = + "none"; + } + + let nextElement = this.element.nextElementSibling; + + if (nextElement?.tagName === "SPAN") { + (nextElement).style.display = "none"; + nextElement = nextElement.nextElementSibling; + } + + if (nextElement?.tagName === "A") { + (nextElement).style.display = "none"; + nextElement = nextElement.nextElementSibling; + } + + if (nextElement?.tagName === "BR") { + (nextElement).style.display = "none"; + } + } + + show() { + this.element.style.display = ""; + let nextElement = this.element.nextElementSibling; + + if (nextElement?.tagName === "SPAN") { + (nextElement).style.display = ""; + nextElement = nextElement.nextElementSibling; + } + + if (nextElement?.tagName === "A") { + (nextElement).style.display = ""; + nextElement = nextElement.nextElementSibling; + } + + if (nextElement?.tagName === "BR") { + (nextElement).style.display = ""; + } + } } diff --git a/source/ContentScript/page-elements/trade-finder-row.ts b/source/ContentScript/page-elements/trade-finder-row.ts index 34972e2..6ea5c20 100644 --- a/source/ContentScript/page-elements/trade-finder-row.ts +++ b/source/ContentScript/page-elements/trade-finder-row.ts @@ -85,4 +85,12 @@ export default class TradeFinderRow { this.row.appendChild(td); } + + hide() { + this.row.style.display = "none"; + } + + show() { + this.row.style.display = ""; + } } diff --git a/source/ContentScript/page-enhancers/trade-finder-enhancer.ts b/source/ContentScript/page-enhancers/trade-finder-enhancer.ts index 43a9ecf..46a01f8 100644 --- a/source/ContentScript/page-enhancers/trade-finder-enhancer.ts +++ b/source/ContentScript/page-enhancers/trade-finder-enhancer.ts @@ -1,4 +1,6 @@ +import Toggle from "../element-factories/toggle"; import TradeFinderRow from "../page-elements/trade-finder-row"; +import Settings from "../settings"; import TraderRepository from "../trader-repository"; export default class TradeFinderEnhancer { @@ -11,20 +13,61 @@ export default class TradeFinderEnhancer { page.querySelector("table thead tr")?.appendChild(th); } + static async addVisibilityToggle(page: Document, rows: TradeFinderRow[]) { + let h2 = page.querySelector("h2")!; + + let div = document.createElement("div"); + div.style.display = "flex"; + div.style.justifyContent = "space-between"; + + let toggle = Toggle.create(); + + h2.parentNode!.insertBefore(div, h2); + div.appendChild(h2); + div.appendChild(toggle.element); + + toggle.onToggle((on: boolean) => { + on === true + ? this.hideNonOpenRows(rows) + : this.showNonOpenRows(rows); + }); + + if ((await Settings.get("hideNonOpenRows")) === true) { + toggle.switchOn(); + this.hideNonOpenRows(rows); + } + } + static async enhance(page: Document) { let repository = await TraderRepository.load(); this.addHeader(page); let tbody = page.querySelector("table tbody")!; - let rows = tbody.querySelectorAll("tr"); - Array.from(rows) + let rows = Array.from(tbody.querySelectorAll("tr")) .map((row) => TradeFinderRow.parse(row as HTMLTableRowElement, repository) ) .filter((row) => row !== null) - .filter((row) => row!.trader !== null) - .forEach((row) => row!.addWantsColumn()); + .filter((row) => row!.trader !== null) as TradeFinderRow[]; + + rows.forEach((row) => row!.addWantsColumn()); + + this.addVisibilityToggle(page, rows); + } + + static hideNonOpenRows(rows: TradeFinderRow[] = []) { + Settings.set("hideNonOpenRows", true); + + rows.filter((row) => row!.trader!.open === false).forEach((row) => + row!.hide() + ); + } + + static showNonOpenRows(rows: TradeFinderRow[] = []) { + Settings.set("hideNonOpenRows", false); + + rows.forEach((row) => row!.show()); } } diff --git a/source/ContentScript/page-enhancers/trade-finder-theirs-enhancer.ts b/source/ContentScript/page-enhancers/trade-finder-theirs-enhancer.ts index b2a4e74..1331c24 100644 --- a/source/ContentScript/page-enhancers/trade-finder-theirs-enhancer.ts +++ b/source/ContentScript/page-enhancers/trade-finder-theirs-enhancer.ts @@ -1,4 +1,6 @@ +import Toggle from "../element-factories/toggle"; import TradeFinderRow from "../page-elements/trade-finder-row"; +import Settings from "../settings"; import TraderRepository from "../trader-repository"; export default class TradeFinderTheirsEnhancer { @@ -11,20 +13,61 @@ export default class TradeFinderTheirsEnhancer { page.querySelector("table thead tr")?.appendChild(th); } + static async addVisibilityToggle(page: Document, rows: TradeFinderRow[]) { + let h2 = page.querySelector("h2")!; + + let div = document.createElement("div"); + div.style.display = "flex"; + div.style.justifyContent = "space-between"; + + let toggle = Toggle.create(); + + h2.parentNode!.insertBefore(div, h2); + div.appendChild(h2); + div.appendChild(toggle.element); + + toggle.onToggle((on: boolean) => { + on === true + ? this.hideNonOpenRows(rows) + : this.showNonOpenRows(rows); + }); + + if ((await Settings.get("hideNonOpenRows")) === true) { + toggle.switchOn(); + this.hideNonOpenRows(rows); + } + } + static async enhance(page: Document) { let repository = await TraderRepository.load(); this.addHeader(page); let tbody = page.querySelector("table tbody")!; - let rows = tbody.querySelectorAll("tr"); - Array.from(rows) + let rows = Array.from(tbody.querySelectorAll("tr")) .map((row) => TradeFinderRow.parse(row as HTMLTableRowElement, repository) ) .filter((row) => row !== null) - .filter((row) => row!.trader !== null) - .forEach((row) => row!.addOwnsColumn()); + .filter((row) => row!.trader !== null) as TradeFinderRow[]; + + rows.forEach((row) => row!.addWantsColumn()); + + this.addVisibilityToggle(page, rows); + } + + static hideNonOpenRows(rows: TradeFinderRow[] = []) { + Settings.set("hideNonOpenRows", true); + + rows.filter((row) => row!.trader!.open === false).forEach((row) => + row!.hide() + ); + } + + static showNonOpenRows(rows: TradeFinderRow[] = []) { + Settings.set("hideNonOpenRows", false); + + rows.forEach((row) => row!.show()); } } diff --git a/source/ContentScript/page-enhancers/who-owns-enhancer.ts b/source/ContentScript/page-enhancers/who-owns-enhancer.ts index a2e4632..ae850b6 100644 --- a/source/ContentScript/page-enhancers/who-owns-enhancer.ts +++ b/source/ContentScript/page-enhancers/who-owns-enhancer.ts @@ -1,11 +1,37 @@ +import Toggle from "../element-factories/toggle"; import ProfileLink from "../page-elements/profile-link"; +import Settings from "../settings"; import TraderRepository from "../trader-repository"; export default class WhoOwnsEnhancer { + static async addVisibilityToggle(page: Document, links: ProfileLink[]) { + let bs = page.querySelectorAll("b"); + + let b = Array.from(bs).find((b) => b.innerText.trim() === "OWNERS:")!; + + let toggle = Toggle.create(); + toggle.element.style.marginBottom = "1.5em"; + + b.parentNode!.insertBefore(toggle.element, b); + + toggle.onToggle((on: boolean) => { + on === true + ? this.hideNonOpenLinks(links) + : this.showNonOpenLinks(links); + }); + + if ((await Settings.get("hideNonOpenRows")) === true) { + toggle.switchOn(); + this.hideNonOpenLinks(links); + } + } + static async enhance(page: Document) { let repository = await TraderRepository.load(); - ProfileLink.getAll(page, repository) + let links = ProfileLink.getAll(page, repository); + + links .filter((profileLink) => profileLink.trader !== null) .filter((profileLink) => profileLink.trader!.wantsCount !== null) .filter((profileLink) => profileLink.trader!.wantsCount! > 0) @@ -18,5 +44,21 @@ export default class WhoOwnsEnhancer { .forEach((profileLink) => { profileLink.addStar(); }); + + this.addVisibilityToggle(page, links); + } + + static hideNonOpenLinks(links: ProfileLink[] = []) { + Settings.set("hideNonOpenRows", true); + + links + .filter((link) => link!.trader!.open === false) + .forEach((link) => link!.hide()); + } + + static showNonOpenLinks(links: ProfileLink[] = []) { + Settings.set("hideNonOpenRows", false); + + links.forEach((link) => link!.show()); } } diff --git a/source/ContentScript/page-enhancers/who-wants-enhancer.ts b/source/ContentScript/page-enhancers/who-wants-enhancer.ts index d4964a7..205821f 100644 --- a/source/ContentScript/page-enhancers/who-wants-enhancer.ts +++ b/source/ContentScript/page-enhancers/who-wants-enhancer.ts @@ -1,22 +1,67 @@ +import Toggle from "../element-factories/toggle"; import ProfileLink from "../page-elements/profile-link"; +import Settings from "../settings"; import TraderRepository from "../trader-repository"; export default class WhoWantsEnhancer { + static async addVisibilityToggle(page: Document, links: ProfileLink[]) { + let bs = page.querySelectorAll("b"); + + let b = Array.from(bs).find((b) => b.innerText.trim() === "WANTERS:")!; + + console.log(bs); + + let toggle = Toggle.create(); + toggle.element.style.marginBottom = "1.5em"; + + b.parentNode!.insertBefore(toggle.element, b); + + toggle.onToggle((on: boolean) => { + on === true + ? this.hideNonOpenLinks(links) + : this.showNonOpenLinks(links); + }); + + if ((await Settings.get("hideNonOpenRows")) === true) { + toggle.switchOn(); + this.hideNonOpenLinks(links); + } + } + static async enhance(page: Document) { let repository = await TraderRepository.load(); - ProfileLink.getAll(page, repository) + let links = ProfileLink.getAll(page, repository); + + let filteredLinks = links .filter((profileLink) => profileLink.trader !== null) .filter((profileLink) => profileLink.trader!.ownsCount !== null) - .filter((profileLink) => profileLink.trader!.ownsCount! > 0) - .map((profileLink) => { - profileLink.addOwnsBadge(); + .filter((profileLink) => profileLink.trader!.ownsCount! > 0); - return profileLink; - }) + filteredLinks.forEach((profileLink) => { + profileLink.addOwnsBadge(); + + return profileLink; + }); + + filteredLinks .filter((profileLink) => profileLink.trader!.open) .forEach((profileLink) => { profileLink.addStar(); }); + + this.addVisibilityToggle(page, links); + } + + static hideNonOpenLinks(links: ProfileLink[] = []) { + Settings.set("hideNonOpenRows", true); + + links.filter((link) => !link.open).forEach((link) => link!.hide()); + } + + static showNonOpenLinks(links: ProfileLink[] = []) { + Settings.set("hideNonOpenRows", false); + + links.forEach((link) => link!.show()); } } diff --git a/source/ContentScript/settings.ts b/source/ContentScript/settings.ts new file mode 100644 index 0000000..003491a --- /dev/null +++ b/source/ContentScript/settings.ts @@ -0,0 +1,13 @@ +import { browser } from "webextension-polyfill-ts"; + +export default class Settings { + static async get(key: string): Promise { + let storage = await browser.storage.local.get(key); + + return storage[key] ?? null; + } + + static async set(key: string, value: any) { + await browser.storage.local.set({ [key]: value }); + } +} diff --git a/source/assets/css/extension.css b/source/assets/css/extension.css index 8a32850..8ffbdd9 100644 --- a/source/assets/css/extension.css +++ b/source/assets/css/extension.css @@ -1,3 +1,109 @@ +[hidden] { + display: none; +} + +*, +::before, +::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + .et-badge { background-color: #e2e8f0; color: #334155; @@ -12,3 +118,504 @@ color: #020617; text-decoration: none; } + +.pointer-events-none { + pointer-events: none; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.-bottom-\[3px\] { + bottom: -3px; +} + +.-left-1 { + left: -0.25rem; +} + +.-left-1\.5 { + left: -0.375rem; +} + +.-right-1 { + right: -0.25rem; +} + +.-right-1\.5 { + right: -0.375rem; +} + +.-top-0 { + top: -0px; +} + +.-top-0\.5 { + top: -0.125rem; +} + +.ml-\[14px\] { + margin-left: 14px; +} + +.ml-\[1rem\] { + margin-left: 1rem; +} + +.mr-1 { + margin-right: 0.25rem; +} + +.mr-1\.5 { + margin-right: 0.375rem; +} + +.inline-block { + display: inline-block; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.h-5 { + height: 1.25rem; +} + +.h-6 { + height: 1.5rem; +} + +.w-11 { + width: 2.75rem; +} + +.w-5 { + width: 1.25rem; +} + +.flex-shrink-0 { + flex-shrink: 0; +} + +.translate-x-0 { + --tw-translate-x: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) + rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) + scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.translate-x-5 { + --tw-translate-x: 1.25rem; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) + rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) + scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) + rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) + scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.cursor-pointer { + cursor: pointer; +} + +.items-center { + align-items: center; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.border-2 { + border-width: 2px; +} + +.border-transparent { + border-color: transparent; +} + +.bg-green-100 { + --tw-bg-opacity: 1; + background-color: rgb(220 252 231 / var(--tw-bg-opacity)); +} + +.bg-indigo-600 { + --tw-bg-opacity: 1; + background-color: rgb(79 70 229 / var(--tw-bg-opacity)); +} + +.bg-slate-200 { + --tw-bg-opacity: 1; + background-color: rgb(226 232 240 / var(--tw-bg-opacity)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.p-4 { + padding: 1rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.font-medium { + font-weight: 500; +} + +.text-green-950 { + --tw-text-opacity: 1; + color: rgb(5 46 22 / var(--tw-text-opacity)); +} + +.text-slate-900 { + --tw-text-opacity: 1; + color: rgb(15 23 42 / var(--tw-text-opacity)); +} + +.shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), + 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), + var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.ring-0 { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 + var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 + calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), + var(--tw-shadow, 0 0 #0000); +} + +.transition { + transition-property: color, background-color, border-color, fill, stroke, + opacity, box-shadow, transform, filter, -webkit-text-decoration-color, + -webkit-backdrop-filter; + transition-property: color, background-color, border-color, + text-decoration-color, fill, stroke, opacity, box-shadow, transform, + filter, backdrop-filter; + transition-property: color, background-color, border-color, + text-decoration-color, fill, stroke, opacity, box-shadow, transform, + filter, backdrop-filter, -webkit-text-decoration-color, + -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-colors { + transition-property: color, background-color, border-color, fill, stroke, + -webkit-text-decoration-color; + transition-property: color, background-color, border-color, + text-decoration-color, fill, stroke; + transition-property: color, background-color, border-color, + text-decoration-color, fill, stroke, -webkit-text-decoration-color; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-200 { + transition-duration: 200ms; +} + +.ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 + var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 + calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), + var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-indigo-600:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(79 70 229 / var(--tw-ring-opacity)); +} + +.focus\:ring-offset-2:focus { + --tw-ring-offset-width: 2px; +} +.pointer-events-none { + pointer-events: none; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.-bottom-\[3px\] { + bottom: -3px; +} + +.-left-1 { + left: -0.25rem; +} + +.-left-1\.5 { + left: -0.375rem; +} + +.-right-1 { + right: -0.25rem; +} + +.-right-1\.5 { + right: -0.375rem; +} + +.-top-0 { + top: -0px; +} + +.-top-0\.5 { + top: -0.125rem; +} + +.ml-\[0\.75rem\] { + margin-left: 0.75rem; +} + +.ml-\[14px\] { + margin-left: 14px; +} + +.ml-\[1rem\] { + margin-left: 1rem; +} + +.mr-1 { + margin-right: 0.25rem; +} + +.mr-1\.5 { + margin-right: 0.375rem; +} + +.inline-block { + display: inline-block; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.h-5 { + height: 1.25rem; +} + +.h-6 { + height: 1.5rem; +} + +.w-11 { + width: 2.75rem; +} + +.w-5 { + width: 1.25rem; +} + +.flex-shrink-0 { + flex-shrink: 0; +} + +.translate-x-0 { + --tw-translate-x: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) + rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) + scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.translate-x-5 { + --tw-translate-x: 1.25rem; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) + rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) + scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) + rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) + scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.cursor-pointer { + cursor: pointer; +} + +.items-center { + align-items: center; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.border-2 { + border-width: 2px; +} + +.border-transparent { + border-color: transparent; +} + +.bg-green-100 { + --tw-bg-opacity: 1; + background-color: rgb(220 252 231 / var(--tw-bg-opacity)); +} + +.bg-green-600 { + --tw-bg-opacity: 1; + background-color: rgb(22 163 74 / var(--tw-bg-opacity)); +} + +.bg-slate-200 { + --tw-bg-opacity: 1; + background-color: rgb(226 232 240 / var(--tw-bg-opacity)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.p-4 { + padding: 1rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.font-medium { + font-weight: 500; +} + +.text-green-950 { + --tw-text-opacity: 1; + color: rgb(5 46 22 / var(--tw-text-opacity)); +} + +.text-slate-900 { + --tw-text-opacity: 1; + color: rgb(15 23 42 / var(--tw-text-opacity)); +} + +.shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), + 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), + var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.ring-0 { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 + var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 + calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), + var(--tw-shadow, 0 0 #0000); +} + +.transition { + transition-property: color, background-color, border-color, fill, stroke, + opacity, box-shadow, transform, filter, -webkit-text-decoration-color, + -webkit-backdrop-filter; + transition-property: color, background-color, border-color, + text-decoration-color, fill, stroke, opacity, box-shadow, transform, + filter, backdrop-filter; + transition-property: color, background-color, border-color, + text-decoration-color, fill, stroke, opacity, box-shadow, transform, + filter, backdrop-filter, -webkit-text-decoration-color, + -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-colors { + transition-property: color, background-color, border-color, fill, stroke, + -webkit-text-decoration-color; + transition-property: color, background-color, border-color, + text-decoration-color, fill, stroke; + transition-property: color, background-color, border-color, + text-decoration-color, fill, stroke, -webkit-text-decoration-color; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-200 { + transition-duration: 200ms; +} + +.ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 + var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 + calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), + var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-green-600:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(22 163 74 / var(--tw-ring-opacity)); +} + +.focus\:ring-offset-2:focus { + --tw-ring-offset-width: 2px; +}