From a609d89adc16afd70dfd3d62f38f8234c35b2cd4 Mon Sep 17 00:00:00 2001
From: Yeghro <130201060+Yeghro@users.noreply.github.com>
Date: Fri, 24 Jan 2025 18:29:12 -0800
Subject: [PATCH 01/17] so far got the basics down, categories and lists are
being populated for the most part, just need to go through and check them all
and improve the visual to make it more pleasing
---
index.html | 54 +++++++++
script.js | 320 +++++++++++++++++++++++++++++++++++++++++++++++++++++
styles.css | 213 +++++++++++++++++++++++++++++++++++
3 files changed, 587 insertions(+)
create mode 100644 index.html
create mode 100644 script.js
create mode 100644 styles.css
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..20c7e51
--- /dev/null
+++ b/index.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+ Awesome Nostr Resources
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..dd64bea
--- /dev/null
+++ b/script.js
@@ -0,0 +1,320 @@
+// Dark mode toggle
+const darkModeToggle = document.getElementById('darkModeToggle');
+const body = document.body;
+
+darkModeToggle.addEventListener('click', () => {
+ body.dataset.theme = body.dataset.theme === 'dark' ? 'light' : 'dark';
+ darkModeToggle.innerHTML = body.dataset.theme === 'dark'
+ ? ' '
+ : ' ';
+});
+
+// Mobile menu toggle
+const menuToggle = document.getElementById('menuToggle');
+const sidebar = document.querySelector('.sidebar');
+
+menuToggle.addEventListener('click', () => {
+ sidebar.classList.toggle('active');
+});
+
+// Search functionality
+const searchInput = document.getElementById('search');
+const resourceCards = document.querySelectorAll('.resource-card');
+
+searchInput.addEventListener('input', (e) => {
+ const searchTerm = e.target.value.toLowerCase();
+ resourceCards.forEach(card => {
+ const text = card.textContent.toLowerCase();
+ card.style.display = text.includes(searchTerm) ? 'block' : 'none';
+ });
+});
+
+// Sort functionality
+const sortSelect = document.getElementById('sortSelect');
+
+sortSelect.addEventListener('change', (e) => {
+ const sortBy = e.target.value;
+ const container = document.getElementById('resources-container');
+ const cards = Array.from(container.getElementsByClassName('resource-card'));
+
+ cards.sort((a, b) => {
+ if (sortBy === 'stars') {
+ return parseInt(b.dataset.stars) - parseInt(a.dataset.stars);
+ } else if (sortBy === 'name') {
+ return a.querySelector('h3').textContent.localeCompare(b.querySelector('h3').textContent);
+ }
+ return 0;
+ });
+
+ container.innerHTML = '';
+ cards.forEach(card => container.appendChild(card));
+});
+
+// Function to parse resources from README content
+function parseResources(content) {
+ const resources = {
+ 'most-popular': [],
+ 'protocol': [],
+ 'relays': [],
+ 'clients': [],
+ 'libraries': [],
+ 'bridges-and-gateways': [],
+ 'cache-services': [],
+ 'tools': [],
+ 'nip-05-identity-services': [],
+ 'offline-signers': [],
+ 'vanity-pubkey-mining': [],
+ 'peer-to-peer-markets': [],
+ 'nip-07-browser-extensions': [],
+ 'nip-47-nostr-wallet-connect-nwc-implementations': [],
+ 'nip-57-zaps-compatible-wallets-and-solutions': [],
+ 'nip-90-data-vending-machines': [],
+ 'nip-96-file-storage-servers': [],
+ 'nostr-web-services-nws': [],
+ 'adjacent-protocols': [],
+ 'games-on-nostr': [],
+ 'communities': [],
+ 'tutorials': [],
+ 'recommended-reading-watching': [],
+ 'podcasts': [],
+ 'other-links': [],
+ 'deprecated': [],
+ 'related-resources': [],
+ 'contributing': [],
+ 'contributors': []
+ };
+
+ // Split content by lines and process each line
+ const lines = content.split('\n');
+ let currentMainSection = '';
+ let currentSubSection = '';
+
+ lines.forEach(line => {
+ // Detect main section headers (##)
+ if (line.startsWith('## ')) {
+ currentMainSection = line.slice(3).trim();
+ currentSubSection = '';
+ }
+ // Detect subsection headers (###)
+ else if (line.startsWith('### ')) {
+ currentSubSection = line.slice(4).trim();
+ }
+ // Special handling for contributors section with HTML content
+ else if (currentMainSection.toLowerCase() === 'contributors' && line.includes('${resource.name}
+ ${resource.link ? `
+
+ ` : ''}
+ ${resource.description ? `
+
+ ${resource.description}
+
+ ` : ''}
+ ${resource.stars ? `
+
+
+ ${resource.stars}
+
+ ` : ''}
+ `;
+
+ card.innerHTML = formattedContent;
+ return card;
+}
+
+// Function to populate resources container
+function populateResources(categoryId, resources) {
+ const container = document.getElementById('resources-container');
+ container.innerHTML = ''; // Clear existing content
+
+ resources[categoryId]?.forEach(resource => {
+ const card = createResourceCard(resource);
+ container.appendChild(card);
+ });
+}
+
+// Function to get icon for category
+function getCategoryIcon(category) {
+ const iconMap = {
+ 'most-popular': 'fa-star',
+ 'protocol': 'fa-book',
+ 'relays': 'fa-server',
+ 'clients': 'fa-mobile-alt',
+ 'libraries': 'fa-code',
+ 'bridges-and-gateways': 'fa-bridge',
+ 'cache-services': 'fa-database',
+ 'tools': 'fa-wrench',
+ 'nip-05-identity-services': 'fa-id-card',
+ 'offline-signers': 'fa-signature',
+ 'vanity-pubkey-mining': 'fa-hammer',
+ 'peer-to-peer-markets': 'fa-store',
+ 'nip-07-browser-extensions': 'fa-puzzle-piece',
+ 'nip-47-nostr-wallet-connect-nwc-implementations': 'fa-wallet',
+ 'nip-57-zaps-compatible-wallets-and-solutions': 'fa-bolt',
+ 'nip-90-data-vending-machines': 'fa-store-alt',
+ 'nip-96-file-storage-servers': 'fa-folder',
+ 'nostr-web-services-nws': 'fa-globe',
+ 'adjacent-protocols': 'fa-link',
+ 'games-on-nostr': 'fa-gamepad',
+ 'communities': 'fa-users',
+ 'tutorials': 'fa-graduation-cap',
+ 'recommended-reading-watching': 'fa-book-reader',
+ 'podcasts': 'fa-podcast',
+ 'other-links': 'fa-external-link-alt',
+ 'deprecated': 'fa-archive',
+ 'related-resources': 'fa-project-diagram',
+ 'contributing': 'fa-hands-helping',
+ 'contributors': 'fa-users-cog',
+ // Default icon if category not found
+ 'default': 'fa-circle'
+ };
+
+ const key = category.toLowerCase().replace(/[^a-z0-9]+/g, '-');
+ return iconMap[key] || iconMap.default;
+}
+
+// Function to generate navigation from content
+function generateNavigation(content) {
+ const navList = document.querySelector('.nav-links');
+ const categories = [];
+ let currentCategory = '';
+
+ // Split content by lines and find all ## headers
+ const lines = content.split('\n');
+ lines.forEach(line => {
+ if (line.startsWith('## ')) {
+ currentCategory = line.slice(3).trim();
+ const categoryId = currentCategory.toLowerCase().replace(/[^a-z0-9]+/g, '-');
+ const icon = getCategoryIcon(currentCategory.toLowerCase());
+
+ categories.push({
+ id: categoryId,
+ name: currentCategory,
+ icon: icon
+ });
+ }
+ });
+
+ // Generate navigation HTML
+ navList.innerHTML = categories.map(category => `
+
+
+
+ ${category.name}
+
+
+ `).join('');
+
+ // Add click event listeners to the new navigation items
+ document.querySelectorAll('.nav-links a').forEach(link => {
+ link.addEventListener('click', (e) => {
+ e.preventDefault();
+ const categoryId = e.target.getAttribute('href').replace('#', '');
+ populateResources(categoryId, window.parsedResources);
+ });
+ });
+
+ return categories;
+}
+
+// Update the fetch call to generate navigation
+fetch('./README.md')
+ .then(response => {
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return response.text();
+ })
+ .then(content => {
+ console.log('README content loaded:', content.substring(0, 200) + '...');
+
+ // Generate navigation first
+ const categories = generateNavigation(content);
+
+ // Then parse and populate resources
+ window.parsedResources = parseResources(content);
+ console.log('Parsed resources:', window.parsedResources);
+
+ // Load initial category (first one in the list)
+ if (categories.length > 0) {
+ populateResources(categories[0].id, window.parsedResources);
+ }
+ })
+ .catch(error => {
+ console.error('Error loading README:', error);
+ document.getElementById('resources-container').innerHTML =
+ `Error loading resources: ${error.message}
`;
+ });
\ No newline at end of file
diff --git a/styles.css b/styles.css
new file mode 100644
index 0000000..8e196d0
--- /dev/null
+++ b/styles.css
@@ -0,0 +1,213 @@
+:root {
+ --primary-color: #6e5494;
+ --background-color: #ffffff;
+ --text-color: #333333;
+ --card-background: #f5f5f5;
+ --sidebar-background: #f8f9fa;
+ --hover-color: #563d7c;
+}
+
+/* Dark theme variables */
+[data-theme="dark"] {
+ --background-color: #1a1a1a;
+ --text-color: #ffffff;
+ --card-background: #2d2d2d;
+ --sidebar-background: #252525;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+ background-color: var(--background-color);
+ color: var(--text-color);
+ line-height: 1.6;
+}
+
+.container {
+ display: flex;
+ min-height: 100vh;
+}
+
+/* Sidebar Styles */
+.sidebar {
+ width: 280px;
+ background-color: var(--sidebar-background);
+ padding: 1rem;
+ position: fixed;
+ height: 100vh;
+ overflow-y: auto;
+ transition: transform 0.3s ease;
+}
+
+.sidebar-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 2rem;
+}
+
+.search-box {
+ position: relative;
+ margin-bottom: 1.5rem;
+}
+
+.search-box input {
+ width: 100%;
+ padding: 0.5rem 2rem 0.5rem 1rem;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ background-color: var(--background-color);
+ color: var(--text-color);
+}
+
+.search-box i {
+ position: absolute;
+ right: 10px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: #666;
+}
+
+.nav-links {
+ list-style: none;
+}
+
+.nav-links li {
+ margin-bottom: 0.5rem;
+}
+
+.nav-links a {
+ display: flex;
+ align-items: center;
+ padding: 0.5rem;
+ color: var(--text-color);
+ text-decoration: none;
+ border-radius: 4px;
+ transition: background-color 0.2s;
+}
+
+.nav-links a:hover {
+ background-color: var(--primary-color);
+ color: white;
+}
+
+.nav-links i {
+ margin-right: 0.5rem;
+ width: 20px;
+ text-align: center;
+}
+
+/* Main Content Styles */
+.main-content {
+ margin-left: 280px;
+ padding: 2rem;
+ flex: 1;
+}
+
+.content-header {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 2rem;
+}
+
+.view-controls {
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+}
+
+/* Resource Cards */
+.resource-card {
+ background: var(--card-bg);
+ border-radius: 8px;
+ padding: 16px;
+ margin-bottom: 16px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.resource-title {
+ font-size: 1.2em;
+ margin-bottom: 8px;
+ color: var(--text-primary);
+}
+
+.resource-link {
+ margin-bottom: 12px;
+}
+
+.resource-link a {
+ color: var(--link-color);
+ text-decoration: none;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 0.9em;
+}
+
+.resource-link a:hover {
+ text-decoration: underline;
+}
+
+.resource-description {
+ color: var(--text-secondary);
+ margin-bottom: 12px;
+ line-height: 1.5;
+ font-size: 0.95em;
+ padding-left: 8px;
+ border-left: 3px solid var(--border-color);
+}
+
+.resource-stars {
+ color: var(--star-color);
+ font-size: 0.9em;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.fa-star {
+ color: #f1c40f;
+}
+
+.fa-external-link-alt {
+ font-size: 0.9em;
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+ .sidebar {
+ transform: translateX(-100%);
+ z-index: 1000;
+ }
+
+ .sidebar.active {
+ transform: translateX(0);
+ }
+
+ .main-content {
+ margin-left: 0;
+ }
+
+ .menu-toggle {
+ display: block;
+ }
+}
+
+.markdown-content {
+ padding: 10px;
+ line-height: 1.5;
+}
+
+.markdown-content a {
+ color: #0366d6;
+ text-decoration: none;
+}
+
+.markdown-content a:hover {
+ text-decoration: underline;
+}
\ No newline at end of file
From e390d63ba462a0f656e44f12b4bd36a443e50a19 Mon Sep 17 00:00:00 2001
From: Yeghro <130201060+Yeghro@users.noreply.github.com>
Date: Fri, 24 Jan 2025 21:28:52 -0800
Subject: [PATCH 02/17] fixed contributers/contributing displays and got the
search working
---
script.js | 220 +++++++++++++++++++++++++++++++++++++++++++++++------
styles.css | 104 ++++++++++++++++++++++++-
2 files changed, 300 insertions(+), 24 deletions(-)
diff --git a/script.js b/script.js
index dd64bea..34fa263 100644
--- a/script.js
+++ b/script.js
@@ -19,13 +19,82 @@ menuToggle.addEventListener('click', () => {
// Search functionality
const searchInput = document.getElementById('search');
-const resourceCards = document.querySelectorAll('.resource-card');
searchInput.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase();
- resourceCards.forEach(card => {
- const text = card.textContent.toLowerCase();
- card.style.display = text.includes(searchTerm) ? 'block' : 'none';
+ const container = document.getElementById('resources-container');
+
+ if (!searchTerm) {
+ // If search is empty, restore current category view
+ const currentCategory = document.querySelector('.nav-links a.active')?.getAttribute('href')?.replace('#', '');
+ if (currentCategory) {
+ populateResources(currentCategory, window.parsedResources);
+ }
+ return;
+ }
+
+ // Clear current container
+ container.innerHTML = '';
+
+ // Search through all categories
+ Object.entries(window.parsedResources).forEach(([category, resources]) => {
+ resources.forEach(resource => {
+ let shouldShow = false;
+
+ if (resource.type) {
+ // Handle special sections (contributors, contributing)
+ if (resource.type === 'markdown') {
+ shouldShow = resource.content.toLowerCase().includes(searchTerm);
+ } else if (resource.type === 'github-contributors') {
+ // Skip contributors section in search
+ return;
+ }
+ } else {
+ // Regular resources
+ const searchableText = [
+ resource.name,
+ resource.description,
+ resource.link
+ ].filter(Boolean).join(' ').toLowerCase();
+
+ shouldShow = searchableText.includes(searchTerm);
+ }
+
+ if (shouldShow) {
+ let card;
+ if (resource.type) {
+ card = createSpecialSectionCard(resource);
+ } else {
+ card = createResourceCard(resource);
+ }
+
+ // Add category label to card
+ const categoryLabel = document.createElement('div');
+ categoryLabel.className = 'category-label';
+ categoryLabel.textContent = category.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
+ card.insertBefore(categoryLabel, card.firstChild);
+
+ container.appendChild(card);
+ }
+ });
+ });
+
+ // Show "no results" message if nothing found
+ if (!container.children.length) {
+ container.innerHTML = `
+
+ No resources found matching "${searchTerm}"
+
+ `;
+ }
+});
+
+// Add active class handling for navigation
+document.querySelectorAll('.nav-links a').forEach(link => {
+ link.addEventListener('click', () => {
+ document.querySelectorAll('.nav-links a').forEach(l => l.classList.remove('active'));
+ link.classList.add('active');
+ searchInput.value = ''; // Clear search when changing categories
});
});
@@ -50,7 +119,42 @@ sortSelect.addEventListener('change', (e) => {
cards.forEach(card => container.appendChild(card));
});
-// Function to parse resources from README content
+// Add this function to fetch contributors from GitHub API
+async function fetchContributors(owner, repo) {
+ try {
+ let page = 1;
+ let allContributors = [];
+
+ while (true) {
+ const response = await fetch(
+ `https://api.github.com/repos/${owner}/${repo}/contributors?per_page=100&page=${page}`
+ );
+
+ if (!response.ok) throw new Error('Failed to fetch contributors');
+
+ const contributors = await response.json();
+ if (contributors.length === 0) break; // No more contributors
+
+ allContributors = [...allContributors, ...contributors];
+ page++;
+
+ // Check if we've reached the last page
+ const linkHeader = response.headers.get('Link');
+ if (!linkHeader || !linkHeader.includes('rel="next"')) {
+ break;
+ }
+ }
+
+ console.log(`Total contributors fetched: ${allContributors.length}`);
+ return allContributors;
+
+ } catch (error) {
+ console.error('Error fetching contributors:', error);
+ return [];
+ }
+}
+
+// Modify the parseResources function to handle contributors differently
function parseResources(content) {
const resources = {
'most-popular': [],
@@ -84,31 +188,36 @@ function parseResources(content) {
'contributors': []
};
- // Split content by lines and process each line
const lines = content.split('\n');
let currentMainSection = '';
let currentSubSection = '';
+ let contributingContent = '';
+
+ // Extract repo info from contributors section
+ let repoInfo = null;
+ const repoRegex = /github\.com\/([\w-]+)\/([\w-]+)\/graphs\/contributors/;
lines.forEach(line => {
- // Detect main section headers (##)
if (line.startsWith('## ')) {
currentMainSection = line.slice(3).trim();
currentSubSection = '';
+ } else if (currentMainSection.toLowerCase() === 'contributors') {
+ const match = line.match(repoRegex);
+ if (match) {
+ repoInfo = {
+ owner: match[1],
+ repo: match[2]
+ };
+ }
+ } else if (currentMainSection.toLowerCase() === 'contributing') {
+ if (line.trim() && !line.startsWith('##')) {
+ contributingContent += line + '\n';
+ }
}
// Detect subsection headers (###)
else if (line.startsWith('### ')) {
currentSubSection = line.slice(4).trim();
}
- // Special handling for contributors section with HTML content
- else if (currentMainSection.toLowerCase() === 'contributors' && line.includes('Contributors
+
+
Loading contributors...
+
+ `;
+
+ if (resource.repoInfo) {
+ console.log('Fetching contributors for:', resource.repoInfo); // Debug log
+ const contributors = await fetchContributors(resource.repoInfo.owner, resource.repoInfo.repo);
+ console.log('Number of contributors:', contributors.length); // Debug log
+ const contributorsHtml = contributors.map(contributor => `
+
+
+ ${contributor.login}
+
+ `).join('');
+
+ const grid = card.querySelector('.contributors-grid');
+ grid.innerHTML = contributorsHtml || 'No contributors found';
+ }
+ } else if (resource.type === 'markdown') {
+ // For contributing section
+ const formattedContent = resource.content
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1 ');
+
+ card.innerHTML = `
+ How to Contribute
+
+ ${formattedContent}
+
+ `;
+ }
+
+ return card;
+}
+
+// Update populateResources to handle both special and regular resources
+async function populateResources(categoryId, resources) {
const container = document.getElementById('resources-container');
- container.innerHTML = ''; // Clear existing content
+ container.innerHTML = '';
+
+ const categoryResources = resources[categoryId];
+ if (!categoryResources) return;
- resources[categoryId]?.forEach(resource => {
- const card = createResourceCard(resource);
+ for (const resource of categoryResources) {
+ let card;
+ if (resource.type) {
+ // Handle special sections (contributors and contributing)
+ card = await createSpecialSectionCard(resource);
+ } else {
+ // Handle regular resource cards
+ card = createResourceCard(resource);
+ }
container.appendChild(card);
- });
+ }
}
// Function to get icon for category
diff --git a/styles.css b/styles.css
index 8e196d0..c8b547c 100644
--- a/styles.css
+++ b/styles.css
@@ -107,6 +107,8 @@ body {
margin-left: 280px;
padding: 2rem;
flex: 1;
+ min-height: 100vh;
+ overflow: auto;
}
.content-header {
@@ -123,11 +125,14 @@ body {
/* Resource Cards */
.resource-card {
- background: var(--card-bg);
+ background: var(--card-background);
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+ width: 100%;
+ height: auto;
+ overflow: visible;
}
.resource-title {
@@ -210,4 +215,101 @@ body {
.markdown-content a:hover {
text-decoration: underline;
+}
+
+/* Update contributors grid styles */
+.contributors-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
+ gap: 16px;
+ padding: 20px;
+ width: 100%;
+ height: auto;
+ min-height: 100px;
+ justify-items: center;
+ overflow: visible;
+}
+
+.contributors-grid a {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-decoration: none;
+ color: var(--text-color);
+ font-size: 0.8em;
+ text-align: center;
+}
+
+.contributors-grid img {
+ border-radius: 50%;
+ width: 64px;
+ height: 64px;
+ transition: transform 0.2s;
+ margin-bottom: 8px;
+}
+
+.contributors-grid a:hover {
+ text-decoration: none;
+}
+
+.contributors-grid a:hover img {
+ transform: scale(1.1);
+}
+
+.contributors-grid .loading {
+ grid-column: 1 / -1;
+ text-align: center;
+ padding: 20px;
+ color: var(--text-color);
+ font-style: italic;
+}
+
+/* Update the resource card for contributors */
+.resource-card.contributors-card {
+ max-width: none;
+ width: 100%;
+ height: auto;
+ min-height: 200px;
+}
+
+.resource-description a {
+ color: var(--primary-color);
+ text-decoration: none;
+}
+
+.resource-description a:hover {
+ text-decoration: underline;
+}
+
+#resources-container {
+ width: 100%;
+ height: auto;
+ min-height: 200px;
+ overflow: visible;
+}
+
+/* Category label styles */
+.category-label {
+ font-size: 0.8em;
+ color: var(--primary-color);
+ text-transform: capitalize;
+ margin-bottom: 8px;
+ padding: 2px 8px;
+ background-color: rgba(110, 84, 148, 0.1);
+ border-radius: 4px;
+ display: inline-block;
+}
+
+/* No results styles */
+.no-results {
+ text-align: center;
+ padding: 2rem;
+ color: var(--text-color);
+ font-style: italic;
+}
+
+/* Active nav link style */
+.nav-links a.active {
+ background-color: var(--primary-color);
+ color: white;
}
\ No newline at end of file
From 4a5bfedd1f2781baaa6f82695ab5826b783bd335 Mon Sep 17 00:00:00 2001
From: Yeghro <130201060+Yeghro@users.noreply.github.com>
Date: Fri, 24 Jan 2025 21:48:08 -0800
Subject: [PATCH 03/17] added mark.js and now it looks and works even better
---
index.html | 2 +
script.js | 258 +++++++++++++++++++++++++++++++++++++++--------------
styles.css | 21 +++--
3 files changed, 211 insertions(+), 70 deletions(-)
diff --git a/index.html b/index.html
index 20c7e51..224b883 100644
--- a/index.html
+++ b/index.html
@@ -49,6 +49,8 @@ Nostr Resources
+
+