feat: Add MSI, SCI, Disco Biscuits importers + refactor About page to be band-agnostic
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s

This commit is contained in:
fullsizemalt 2025-12-28 22:36:52 -08:00
parent 1a9c89e1f1
commit 762d2b81ff
13 changed files with 249 additions and 56 deletions

View file

@ -182,16 +182,25 @@ class ImporterBase(ABC):
else: else:
# Include vertical slug in song slug for cross-band uniqueness # Include vertical slug in song slug for cross-band uniqueness
song_slug = f"{vertical.slug}-{generate_slug(title)}" song_slug = f"{vertical.slug}-{generate_slug(title)}"
song = Song(
title=title, # Check if slug exists (handle simple case variations)
slug=song_slug, existing_slug = self.session.exec(
original_artist=original_artist, select(Song).where(Song.slug == song_slug)
vertical_id=vertical.id ).first()
)
self.session.add(song) if existing_slug:
self.session.commit() song_id = existing_slug.id
self.session.refresh(song) else:
song_id = song.id song = Song(
title=title,
slug=song_slug,
original_artist=original_artist,
vertical_id=vertical.id
)
self.session.add(song)
self.session.commit()
self.session.refresh(song)
song_id = song.id
if external_id: if external_id:
self.song_map[external_id] = song_id self.song_map[external_id] = song_id

View file

@ -246,6 +246,72 @@ class BillyStringsImporter(SetlistFmImporter):
ARTIST_MBID = "640db492-34c4-47df-be14-96e2cd4b9fe4" ARTIST_MBID = "640db492-34c4-47df-be14-96e2cd4b9fe4"
class JoeRussosAlmostDeadImporter(SetlistFmImporter):
"""Import Joe Russo's Almost Dead data from Setlist.fm"""
VERTICAL_NAME = "Joe Russo's Almost Dead"
VERTICAL_SLUG = "jrad"
VERTICAL_DESCRIPTION = "Joe Russo's Almost Dead is an American rock band formed in 2013 that interprets the music of the Grateful Dead."
# JRAD MusicBrainz ID
ARTIST_MBID = "84a69823-3d4f-4ede-b43f-17f85513181a"
class EggyImporter(SetlistFmImporter):
"""Import Eggy data from Setlist.fm"""
VERTICAL_NAME = "Eggy"
VERTICAL_SLUG = "eggy"
VERTICAL_DESCRIPTION = "Connecticut jam band formed in 2014. Known for improvisational rock and explosive live shows."
# Eggy MusicBrainz ID
ARTIST_MBID = "ba0b9dc6-bd61-42c7-a28f-5179b1c04391"
class DogsInAPileImporter(SetlistFmImporter):
"""Import Dogs in a Pile data from Setlist.fm"""
VERTICAL_NAME = "Dogs in a Pile"
VERTICAL_SLUG = "dogs-in-a-pile"
VERTICAL_DESCRIPTION = "New Jersey jam band. Young and energetic."
# Dogs in a Pile MusicBrainz ID
ARTIST_MBID = "a05236ee-3fac-45d7-96f5-b2cd6d03fda9"
class TheDiscoBiscuitsImporter(SetlistFmImporter):
"""Import The Disco Biscuits data from Setlist.fm"""
VERTICAL_NAME = "The Disco Biscuits"
VERTICAL_SLUG = "disco-biscuits"
VERTICAL_DESCRIPTION = "Philadelphia trance-fusion jam band. Pioneers of livetronica."
# The Disco Biscuits MusicBrainz ID
ARTIST_MBID = "4e43632a-afef-4b54-a822-26311110d5c5"
class TheStringCheeseIncidentImporter(SetlistFmImporter):
"""Import The String Cheese Incident data from Setlist.fm"""
VERTICAL_NAME = "The String Cheese Incident"
VERTICAL_SLUG = "sci"
VERTICAL_DESCRIPTION = "Colorado jam band formed in 1993. Known for bluegrass-infused improvisational rock."
# SCI MusicBrainz ID
ARTIST_MBID = "cff95140-6d57-498a-8834-10eb72865b29"
class MindlessSelfIndulgenceImporter(SetlistFmImporter):
"""Import Mindless Self Indulgence data from Setlist.fm"""
VERTICAL_NAME = "Mindless Self Indulgence"
VERTICAL_SLUG = "msi"
VERTICAL_DESCRIPTION = "New York City electronic rock band. Known for their high-energy, chaotic style."
# MSI MusicBrainz ID
ARTIST_MBID = "44f42386-a733-4b51-8298-fe5c807d03aa"
def main_dead_and_company(): def main_dead_and_company():
"""Run Dead & Company import""" """Run Dead & Company import"""
from database import engine from database import engine
@ -264,6 +330,60 @@ def main_billy_strings():
importer.import_all() importer.import_all()
def main_jrad():
"""Run JRAD import"""
from database import engine
with Session(engine) as session:
importer = JoeRussosAlmostDeadImporter(session)
importer.import_all()
def main_eggy():
"""Run Eggy import"""
from database import engine
with Session(engine) as session:
importer = EggyImporter(session)
importer.import_all()
def main_dogs():
"""Run Dogs in a Pile import"""
from database import engine
with Session(engine) as session:
importer = DogsInAPileImporter(session)
importer.import_all()
def main_biscuits():
"""Run The Disco Biscuits import"""
from database import engine
with Session(engine) as session:
importer = TheDiscoBiscuitsImporter(session)
importer.import_all()
def main_sci():
"""Run SCI import"""
from database import engine
with Session(engine) as session:
importer = TheStringCheeseIncidentImporter(session)
importer.import_all()
def main_msi():
"""Run Mindless Self Indulgence import"""
from database import engine
with Session(engine) as session:
importer = MindlessSelfIndulgenceImporter(session)
importer.import_all()
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys
if len(sys.argv) > 1: if len(sys.argv) > 1:
@ -271,5 +391,17 @@ if __name__ == "__main__":
main_dead_and_company() main_dead_and_company()
elif sys.argv[1] == "bmfs": elif sys.argv[1] == "bmfs":
main_billy_strings() main_billy_strings()
elif sys.argv[1] == "jrad":
main_jrad()
elif sys.argv[1] == "eggy":
main_eggy()
elif sys.argv[1] == "dogs":
main_dogs()
elif sys.argv[1] == "biscuits":
main_biscuits()
elif sys.argv[1] == "sci":
main_sci()
elif sys.argv[1] == "msi":
main_msi()
else: else:
print("Usage: python -m importers.setlistfm [deadco|bmfs]") print("Usage: python -m importers.setlistfm [deadco|bmfs|jrad|eggy|dogs|biscuits|sci|msi]")

View file

@ -0,0 +1,48 @@
import os
import sys
import requests
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv("SETLISTFM_API_KEY")
BASE_URL = "https://api.setlist.fm/rest/1.0"
def search_artist(name):
print(f"Searching for '{name}'...")
headers = {
"Accept": "application/json",
"x-api-key": API_KEY
}
url = f"{BASE_URL}/search/artists"
params = {"artistName": name, "sort": "relevance"}
try:
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
data = response.json()
artists = data.get("artist", [])
if not artists:
print("No artists found.")
return
print(f"Found {len(artists)} results:")
for artist in artists[:3]:
print(f" - Name: {artist.get('name')}")
print(f" MBID: {artist.get('mbid')}")
print(f" URL: {artist.get('url')}")
print(f" Disambiguation: {artist.get('disambiguation', 'N/A')}")
print("-" * 20)
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python find_artist.py <artist_name>")
sys.exit(1)
search_artist(sys.argv[1])

View file

@ -6,9 +6,9 @@ export default function AboutPage() {
<div className="flex flex-col gap-12 max-w-4xl mx-auto py-8"> <div className="flex flex-col gap-12 max-w-4xl mx-auto py-8">
{/* Header */} {/* Header */}
<section className="text-center space-y-4"> <section className="text-center space-y-4">
<h1 className="text-4xl font-extrabold tracking-tight sm:text-5xl">About Elmeg</h1> <h1 className="text-4xl font-extrabold tracking-tight sm:text-5xl">About Fediversion</h1>
<p className="text-xl text-muted-foreground max-w-2xl mx-auto"> <p className="text-xl text-muted-foreground max-w-2xl mx-auto">
A comprehensive community-driven archive dedicated to preserving the history and evolution of the band <span className="text-foreground font-semibold">Goose</span>. The unified, community-driven platform for the entire <span className="text-foreground font-semibold">Jam Scene</span>.
</p> </p>
</section> </section>
@ -22,9 +22,10 @@ export default function AboutPage() {
<h2 className="text-2xl font-bold">Our Mission</h2> <h2 className="text-2xl font-bold">Our Mission</h2>
</div> </div>
<p className="text-lg leading-relaxed text-muted-foreground"> <p className="text-lg leading-relaxed text-muted-foreground">
Elmeg is a collaborative effort, growing organically through the contributions of the flock. Fediversion is a collaborative effort to bring the entire scene together under one roof.
We believe that every performance is a shared experience. Our goal is to build a mycelium-like network We believe that the magic of live music transcends any single band. Our goal is to create a seamless,
of information, where every setlist, note, and rating helps others discover the magic of the music. interconnected archive where fans can track their stats, rate performances, and discover new music
across the entire spectrum of the jam universe.
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
@ -37,33 +38,33 @@ export default function AboutPage() {
<h2 className="text-xl font-bold">Heady Inspiration</h2> <h2 className="text-xl font-bold">Heady Inspiration</h2>
</div> </div>
<p className="text-sm text-muted-foreground leading-relaxed"> <p className="text-sm text-muted-foreground leading-relaxed">
The soul of Elmeg's performance rating system is directly inspired by <strong>Heady Version</strong>. The soul of our rating system is directly inspired by <strong>Heady Version</strong>.
We've adopted the concept of "Heady Versions" to help fans identify and celebrate the definitive We've adopted the concept of "Heady Versions" to help fans identify and celebrate the definitive
takes on their favorite Goose songs. takes on songs, now expanded across every band in the scene.
</p> </p>
</section> </section>
<section className="space-y-4"> <section className="space-y-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Music className="h-6 w-6 text-blue-500" /> <Music className="h-6 w-6 text-blue-500" />
<h2 className="text-xl font-bold">The Dead Heritage</h2> <h2 className="text-xl font-bold">The Taper Heritage</h2>
</div> </div>
<p className="text-sm text-muted-foreground leading-relaxed"> <p className="text-sm text-muted-foreground leading-relaxed">
We stand on the shoulders of giants. The culture of meticulous documentation pioneered by the We stand on the shoulders of giants. The culture of meticulous documentation pioneered by the
legendary fans of the <strong>Grateful Dead</strong>. Elmeg continues this tradition, legendary fans of the <strong>Grateful Dead</strong>. Fediversion continues this tradition,
bringing the spirit of the Tapestry into the modern era. bringing the spirit of the Tapestry into the modern era for a new generation of fans.
</p> </p>
</section> </section>
<section className="space-y-4"> <section className="space-y-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Music className="h-6 w-6 text-green-500" /> <Music className="h-6 w-6 text-green-500" />
<h2 className="text-xl font-bold">Data & API</h2> <h2 className="text-xl font-bold">Community Data</h2>
</div> </div>
<p className="text-sm text-muted-foreground leading-relaxed"> <p className="text-sm text-muted-foreground leading-relaxed">
We are incredibly grateful to <strong>elgoose.net</strong> for providing their comprehensive We are incredibly grateful to the open data communities that make this possible, including
API. Their dedication to documenting the band&apos;s history makes the archival work of Elmeg possible <strong> Setlist.fm</strong>, <strong>Phish.net</strong>, <strong>Elgoose.net</strong>, and others.
for all fans to enjoy. Their dedication to documenting history allows us to build this unified home for all fans.
</p> </p>
</section> </section>
</div> </div>
@ -75,9 +76,9 @@ export default function AboutPage() {
<div className="space-y-2"> <div className="space-y-2">
<h3 className="font-bold text-yellow-600 dark:text-yellow-400 uppercase tracking-wider text-sm">Official Disclaimer</h3> <h3 className="font-bold text-yellow-600 dark:text-yellow-400 uppercase tracking-wider text-sm">Official Disclaimer</h3>
<p className="text-sm text-yellow-800 dark:text-yellow-100/80 leading-relaxed"> <p className="text-sm text-yellow-800 dark:text-yellow-100/80 leading-relaxed">
Elmeg is an independent, non-commercial community archive. Fediversion is an independent, non-commercial community archive.
This site is <strong>NOT</strong> endorsed by, affiliated with, or sponsored by <strong>Goose</strong> or This site is <strong>NOT</strong> endorsed by, affiliated with, or sponsored by any of the bands featured herein.
their management. All trade names, trademarks, and band imagery are the property of their respective owners. All trade names, trademarks, and band imagery are the property of their respective owners.
We are simply fans celebrating the music. We are simply fans celebrating the music.
</p> </p>
</div> </div>
@ -88,7 +89,7 @@ export default function AboutPage() {
{/* Footer Note */} {/* Footer Note */}
<section className="text-center pt-8 border-t"> <section className="text-center pt-8 border-t">
<p className="text-sm text-muted-foreground italic"> <p className="text-sm text-muted-foreground italic">
"Built by the flock, for the flock." "Built by fans, for the scene."
</p> </p>
</section> </section>
</div > </div >

View file

@ -30,8 +30,8 @@ export async function generateMetadata({ params }: ArtistPageProps): Promise<Met
if (!data) return { title: "Artist Not Found" } if (!data) return { title: "Artist Not Found" }
return { return {
title: `${data.artist.name} | Elmeg`, title: `${data.artist.name} | Fediversion`,
description: data.artist.bio || `Artist profile for ${data.artist.name} on Elmeg.`, description: data.artist.bio || `Artist profile for ${data.artist.name} on Fediversion.`,
} }
} }

View file

@ -21,7 +21,10 @@ const jetbrainsMono = JetBrains_Mono({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Fediversion", title: {
default: "Fediversion",
template: "%s | Fediversion",
},
description: "The ultimate HeadyVersion platform for all jam bands", description: "The ultimate HeadyVersion platform for all jam bands",
}; };
@ -37,11 +40,11 @@ export default function RootLayout({
jetbrainsMono.variable, jetbrainsMono.variable,
"min-h-screen bg-background font-sans antialiased flex flex-col" "min-h-screen bg-background font-sans antialiased flex flex-col"
)}> )}>
<Script {/* <Script
defer defer
src="https://stats.elmeg.xyz/stats" src="https://stats.elmeg.xyz/stats"
data-website-id="4338bbf0-fe74-4256-8973-8cdc0cffe08c" data-website-id="4338bbf0-fe74-4256-8973-8cdc0cffe08c"
/> /> */}
<ThemeProvider <ThemeProvider
attribute="class" attribute="class"
defaultTheme="dark" defaultTheme="dark"

View file

@ -1,21 +1,21 @@
import { Metadata } from "next" import { Metadata } from "next"
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Privacy Policy - Elmeg", title: "Privacy Policy - Fediversion",
description: "Privacy Policy for Elmeg, a community archive platform for live music fans.", description: "Privacy Policy for Fediversion, a community archive platform for live music fans.",
} }
export default function PrivacyPage() { export default function PrivacyPage() {
return ( return (
<div className="max-w-3xl mx-auto py-8"> <div className="max-w-3xl mx-auto py-8">
<h1 className="text-4xl font-bold mb-2">Privacy Policy</h1> <h1 className="text-4xl font-bold mb-2">Privacy Policy</h1>
<p className="text-muted-foreground mb-8">Last updated: December 21, 2024</p> <p className="text-muted-foreground mb-8">Last updated: December 28, 2025</p>
<div className="prose prose-neutral dark:prose-invert max-w-none space-y-8"> <div className="prose prose-neutral dark:prose-invert max-w-none space-y-8">
<section> <section>
<h2 className="text-2xl font-semibold mb-4">1. Introduction</h2> <h2 className="text-2xl font-semibold mb-4">1. Introduction</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
Elmeg ("we," "our," or "us") respects your privacy and is committed to protecting your Fediversion ("we," "our," or "us") respects your privacy and is committed to protecting your
personal data. This Privacy Policy explains how we collect, use, disclose, and safeguard personal data. This Privacy Policy explains how we collect, use, disclose, and safeguard
your information when you use our Service. your information when you use our Service.
</p> </p>
@ -119,8 +119,8 @@ export default function PrivacyPage() {
</ul> </ul>
<p className="mt-3"> <p className="mt-3">
To exercise these rights, contact us at{" "} To exercise these rights, contact us at{" "}
<a href="mailto:privacy@elmeg.xyz" className="text-primary hover:underline"> <a href="mailto:privacy@fediversion.runfoo.run" className="text-primary hover:underline">
privacy@elmeg.xyz privacy@fediversion.runfoo.run
</a>. </a>.
</p> </p>
</div> </div>
@ -174,13 +174,13 @@ export default function PrivacyPage() {
<p>If you have questions about this Privacy Policy or our data practices, contact us at:</p> <p>If you have questions about this Privacy Policy or our data practices, contact us at:</p>
<div className="mt-4 p-4 bg-muted/50 rounded-lg"> <div className="mt-4 p-4 bg-muted/50 rounded-lg">
<p><strong className="text-foreground">Email:</strong>{" "} <p><strong className="text-foreground">Email:</strong>{" "}
<a href="mailto:privacy@elmeg.xyz" className="text-primary hover:underline"> <a href="mailto:privacy@fediversion.runfoo.run" className="text-primary hover:underline">
privacy@elmeg.xyz privacy@fediversion.runfoo.run
</a> </a>
</p> </p>
<p className="mt-2"><strong className="text-foreground">General Support:</strong>{" "} <p className="mt-2"><strong className="text-foreground">General Support:</strong>{" "}
<a href="mailto:support@elmeg.xyz" className="text-primary hover:underline"> <a href="mailto:support@fediversion.runfoo.run" className="text-primary hover:underline">
support@elmeg.xyz support@fediversion.runfoo.run
</a> </a>
</p> </p>
</div> </div>

View file

@ -7,6 +7,6 @@ export default function robots(): MetadataRoute.Robots {
allow: '/', allow: '/',
disallow: ['/admin/', '/api/'], disallow: ['/admin/', '/api/'],
}, },
sitemap: 'https://elmeg.xyz/sitemap.xml', sitemap: 'https://fediversion.runfoo.run/sitemap.xml',
} }
} }

View file

@ -678,7 +678,7 @@ function PrivacySection({ settings, onChange }: {
const url = URL.createObjectURL(blob) const url = URL.createObjectURL(blob)
const a = document.createElement('a') const a = document.createElement('a')
a.href = url a.href = url
a.download = 'my-elmeg-data.json' a.download = 'my-fediversion-data.json'
a.click() a.click()
URL.revokeObjectURL(url) URL.revokeObjectURL(url)
} }

View file

@ -1,7 +1,7 @@
import { MetadataRoute } from 'next' import { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap { export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://elmeg.xyz' const baseUrl = 'https://fediversion.runfoo.run'
const routes = [ const routes = [
'', '',

View file

@ -1,21 +1,21 @@
import { Metadata } from "next" import { Metadata } from "next"
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Terms of Service - Elmeg", title: "Terms of Service - Fediversion",
description: "Terms of Service for Elmeg, a community archive platform for live music fans.", description: "Terms of Service for Fediversion, a community archive platform for live music fans.",
} }
export default function TermsPage() { export default function TermsPage() {
return ( return (
<div className="max-w-3xl mx-auto py-8"> <div className="max-w-3xl mx-auto py-8">
<h1 className="text-4xl font-bold mb-2">Terms of Service</h1> <h1 className="text-4xl font-bold mb-2">Terms of Service</h1>
<p className="text-muted-foreground mb-8">Last updated: December 21, 2024</p> <p className="text-muted-foreground mb-8">Last updated: December 28, 2025</p>
<div className="prose prose-neutral dark:prose-invert max-w-none space-y-8"> <div className="prose prose-neutral dark:prose-invert max-w-none space-y-8">
<section> <section>
<h2 className="text-2xl font-semibold mb-4">1. Acceptance of Terms</h2> <h2 className="text-2xl font-semibold mb-4">1. Acceptance of Terms</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
By accessing or using Elmeg ("the Service"), you agree to be bound by these Terms of Service. By accessing or using Fediversion ("the Service"), you agree to be bound by these Terms of Service.
If you do not agree to these terms, please do not use the Service. We reserve the right to If you do not agree to these terms, please do not use the Service. We reserve the right to
update these terms at any time, and your continued use of the Service constitutes acceptance update these terms at any time, and your continued use of the Service constitutes acceptance
of any changes. of any changes.
@ -25,7 +25,7 @@ export default function TermsPage() {
<section> <section>
<h2 className="text-2xl font-semibold mb-4">2. Description of Service</h2> <h2 className="text-2xl font-semibold mb-4">2. Description of Service</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
Elmeg is a community-driven archive platform for live music enthusiasts. The Service allows Fediversion is a community-driven archive platform for live music enthusiasts. The Service allows
users to browse setlists, rate performances, participate in discussions, and contribute to users to browse setlists, rate performances, participate in discussions, and contribute to
the archive. The Service is provided "as is" and we make no guarantees regarding availability, the archive. The Service is provided "as is" and we make no guarantees regarding availability,
accuracy, or completeness of content. accuracy, or completeness of content.
@ -140,8 +140,8 @@ export default function TermsPage() {
<h2 className="text-2xl font-semibold mb-4">11. Contact</h2> <h2 className="text-2xl font-semibold mb-4">11. Contact</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
If you have questions about these Terms of Service, please contact us at{" "} If you have questions about these Terms of Service, please contact us at{" "}
<a href="mailto:support@elmeg.xyz" className="text-primary hover:underline"> <a href="mailto:support@fediversion.runfoo.run" className="text-primary hover:underline">
support@elmeg.xyz support@fediversion.runfoo.run
</a>. </a>.
</p> </p>
</section> </section>

View file

@ -15,7 +15,7 @@ import { getApiUrl } from "@/lib/api-config"
const STEPS = [ const STEPS = [
{ {
id: "intro", id: "intro",
title: "Welcome to Elmeg", title: "Welcome to Fediversion",
description: "Let's get you set up to discover and track your favorite shows.", description: "Let's get you set up to discover and track your favorite shows.",
icon: PartyPopper icon: PartyPopper
}, },

View file

@ -81,7 +81,7 @@ export function SearchDialog() {
className="inline-flex items-center rounded-lg font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background/50 shadow-sm hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2 relative w-full justify-start text-sm text-muted-foreground sm:pr-12 md:w-40 lg:w-64" className="inline-flex items-center rounded-lg font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background/50 shadow-sm hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2 relative w-full justify-start text-sm text-muted-foreground sm:pr-12 md:w-40 lg:w-64"
> >
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" /> <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<span className="hidden lg:inline-flex">Search Elmeg...</span> <span className="hidden lg:inline-flex">Search Fediversion...</span>
<span className="inline-flex lg:hidden">Search...</span> <span className="inline-flex lg:hidden">Search...</span>
<kbd className="pointer-events-none absolute right-1.5 top-1.5 hidden h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex"> <kbd className="pointer-events-none absolute right-1.5 top-1.5 hidden h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex">
<span className="text-xs"></span>K <span className="text-xs"></span>K