← back to Cypress Awards
fix: remove backup source-leak files from served dir, harden .gitignore, add rel=noopener to blank-target links
851d492020b95ca24eed449df1105a5501a5cb2c · 2026-05-30 23:52:03 -0700 · SteveStudio2
- Deleted frontend/src/index.html.backup and app.js.backup which were publicly served by express.static
- Added *.bak, *.backup, *.bak-*, *.pre-*, *.orig, *.save, *.old, *~, copy-of-* to .gitignore
- Added rel="noopener noreferrer" to all target="_blank" links in news.html, cases.html, outreach.html, simple.html
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Files touched
M .gitignoreD frontend/src/app.js.backupM frontend/src/cases.htmlD frontend/src/index.html.backupM frontend/src/news.htmlM frontend/src/outreach.htmlM frontend/src/simple.html
Diff
commit 851d492020b95ca24eed449df1105a5501a5cb2c
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Sat May 30 23:52:03 2026 -0700
fix: remove backup source-leak files from served dir, harden .gitignore, add rel=noopener to blank-target links
- Deleted frontend/src/index.html.backup and app.js.backup which were publicly served by express.static
- Added *.bak, *.backup, *.bak-*, *.pre-*, *.orig, *.save, *.old, *~, copy-of-* to .gitignore
- Added rel="noopener noreferrer" to all target="_blank" links in news.html, cases.html, outreach.html, simple.html
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
.gitignore | 9 +
frontend/src/app.js.backup | 392 -----------------------------------------
frontend/src/cases.html | 10 +-
frontend/src/index.html.backup | 129 --------------
frontend/src/news.html | 12 +-
frontend/src/outreach.html | 2 +-
frontend/src/simple.html | 4 +-
7 files changed, 23 insertions(+), 535 deletions(-)
diff --git a/.gitignore b/.gitignore
index 1d7a585..0c3327a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,13 @@ coverage/
.DS_Store
*.log
tmp/
+*.bak
+*.bak-*
+*.backup
+*.pre-*
+*.orig
+*.save
+*.old
+*~
+copy-of-*
diff --git a/frontend/src/app.js.backup b/frontend/src/app.js.backup
deleted file mode 100644
index d7352f6..0000000
--- a/frontend/src/app.js.backup
+++ /dev/null
@@ -1,392 +0,0 @@
-// CyPresAwards Frontend Application
-
-const API_BASE_URL = 'http://45.61.58.125:3010/api';
-
-// Application state
-const state = {
- organizations: [],
- categories: [],
- legalTopics: [],
- currentPage: 1,
- pageSize: 20,
- totalResults: 0,
- filters: {
- search: '',
- legalTopics: [],
- categories: [],
- state: ''
- }
-};
-
-// DOM Elements
-const elements = {
- searchInput: document.getElementById('searchInput'),
- searchBtn: document.getElementById('searchBtn'),
- legalTopicFilter: document.getElementById('legalTopicFilter'),
- categoryFilter: document.getElementById('categoryFilter'),
- stateFilter: document.getElementById('stateFilter'),
- applyFiltersBtn: document.getElementById('applyFilters'),
- clearFiltersBtn: document.getElementById('clearFilters'),
- organizationsList: document.getElementById('organizationsList'),
- loadingSpinner: document.getElementById('loadingSpinner'),
- errorMessage: document.getElementById('errorMessage'),
- resultsCount: document.getElementById('resultsCount'),
- totalOrgs: document.getElementById('totalOrgs'),
- totalCategories: document.getElementById('totalCategories'),
- totalTopics: document.getElementById('totalTopics'),
- prevPageBtn: document.getElementById('prevPage'),
- nextPageBtn: document.getElementById('nextPage'),
- pageInfo: document.getElementById('pageInfo')
-};
-
-// Initialize application
-async function init() {
- console.log('Application initializing...');
- console.log('API_BASE_URL:', API_BASE_URL);
-
- try {
- console.log('Loading initial data...');
- await Promise.all([
- loadCategories(),
- loadLegalTopics(),
- loadStats()
- ]);
-
- console.log('Loading organizations...');
- await loadOrganizations();
- setupEventListeners();
- console.log('Application initialized successfully');
- } catch (error) {
- console.error('Initialization error:', error);
- showError('Failed to initialize application. Please refresh the page.');
- }
-}
-
-// Setup event listeners
-function setupEventListeners() {
- elements.searchBtn.addEventListener('click', handleSearch);
- elements.searchInput.addEventListener('keypress', (e) => {
- if (e.key === 'Enter') handleSearch();
- });
-
- elements.applyFiltersBtn.addEventListener('click', handleApplyFilters);
- elements.clearFiltersBtn.addEventListener('click', handleClearFilters);
-
- elements.prevPageBtn.addEventListener('click', () => changePage(-1));
- elements.nextPageBtn.addEventListener('click', () => changePage(1));
-}
-
-// API Functions
-async function fetchAPI(endpoint, params = {}) {
- const queryString = new URLSearchParams(params).toString();
- const url = `${API_BASE_URL}${endpoint}${queryString ? '?' + queryString : ''}`;
-
- console.log('Fetching:', url);
-
- try {
- const response = await fetch(url, {
- method: 'GET',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json'
- },
- mode: 'cors',
- credentials: 'omit'
- });
-
- console.log('Response status:', response.status);
- console.log('Response ok:', response.ok);
-
- if (!response.ok) {
- const errorText = await response.text();
- console.error('Response error text:', errorText);
- throw new Error(`API request failed: ${response.status} ${response.statusText}`);
- }
-
- const data = await response.json();
- console.log('Response data:', data);
- return data;
- } catch (error) {
- console.error('Fetch error:', error);
- console.error('Error type:', error.constructor.name);
- throw error;
- }
-}
-
-async function loadCategories() {
- try {
- const response = await fetchAPI('/categories');
- state.categories = response.data;
- populateFilterSelect(elements.categoryFilter, state.categories);
- } catch (error) {
- console.error('Error loading categories:', error);
- }
-}
-
-async function loadLegalTopics() {
- try {
- const response = await fetchAPI('/legal-topics');
- state.legalTopics = response.data;
- populateFilterSelect(elements.legalTopicFilter, state.legalTopics);
- } catch (error) {
- console.error('Error loading legal topics:', error);
- }
-}
-
-async function loadStats() {
- try {
- const response = await fetchAPI('/stats');
- elements.totalOrgs.textContent = response.data.totalOrganizations.toLocaleString();
- elements.totalCategories.textContent = response.data.totalCategories;
- elements.totalTopics.textContent = response.data.totalLegalTopics;
- } catch (error) {
- console.error('Error loading stats:', error);
- }
-}
-
-async function loadOrganizations() {
- console.log('loadOrganizations() called');
- showLoading(true);
- hideError();
-
- try {
- const params = {
- limit: state.pageSize,
- offset: (state.currentPage - 1) * state.pageSize
- };
-
- if (state.filters.search) {
- params.search = state.filters.search;
- }
-
- if (state.filters.legalTopics.length > 0) {
- params.legalTopics = state.filters.legalTopics.join(',');
- }
-
- if (state.filters.categories.length > 0) {
- params.categories = state.filters.categories.join(',');
- }
-
- if (state.filters.state) {
- params.state = state.filters.state;
- }
-
- console.log('Params:', params);
- const response = await fetchAPI('/organizations', params);
- console.log('Organizations loaded:', response.data.length);
-
- state.organizations = response.data;
- state.totalResults = response.meta.total;
-
- renderOrganizations();
- updatePagination();
- updateResultsCount();
-
- } catch (error) {
- console.error('Error loading organizations:', error);
- console.error('Error stack:', error.stack);
- console.error('Error name:', error.name);
- console.error('Error message:', error.message);
- showError(`Failed to load organizations: ${error.message}. Please check console and try again.`);
- } finally {
- showLoading(false);
- }
-}
-
-// UI Rendering Functions
-function populateFilterSelect(selectElement, items) {
- // Keep the first option (e.g., "All Topics")
- const firstOption = selectElement.options[0];
- selectElement.innerHTML = '';
- selectElement.appendChild(firstOption);
-
- items.forEach(item => {
- const option = document.createElement('option');
- option.value = item.name;
- option.textContent = item.name;
- selectElement.appendChild(option);
- });
-}
-
-function renderOrganizations() {
- elements.organizationsList.innerHTML = '';
-
- if (state.organizations.length === 0) {
- elements.organizationsList.innerHTML = `
- <div style="text-align: center; padding: 3rem; color: var(--text-secondary);">
- <h3>No organizations found</h3>
- <p>Try adjusting your search filters</p>
- </div>
- `;
- return;
- }
-
- state.organizations.forEach(org => {
- const card = createOrganizationCard(org);
- elements.organizationsList.appendChild(card);
- });
-}
-
-function createOrganizationCard(org) {
- const card = document.createElement('div');
- card.className = 'org-card';
-
- // Parse arrays from PostgreSQL
- const categories = org.categories ? parseArray(org.categories) : [];
- const legalTopics = org.legal_topics ? parseArray(org.legal_topics) : [];
-
- // Create logo or placeholder
- const logoHTML = org.logo_url
- ? `<img src="${org.logo_url}" alt="${org.name} logo" class="org-logo" onerror="this.style.display='none'">`
- : `<div class="org-logo-placeholder">${org.name.charAt(0).toUpperCase()}</div>`;
-
- // Create tags
- const categoryTags = categories.filter(c => c).map(cat =>
- `<span class="tag category">${cat}</span>`
- ).join('');
-
- const topicTags = legalTopics.filter(t => t).map(topic =>
- `<span class="tag legal-topic">${topic}</span>`
- ).join('');
-
- // Location
- const location = [org.location_city, org.location_state, org.location_country]
- .filter(Boolean)
- .join(', ') || 'Location not specified';
-
- card.innerHTML = `
- ${logoHTML}
- <div class="org-content">
- <div class="org-header">
- <h3 class="org-name">${org.name}</h3>
- <p class="org-location">📍 ${location}</p>
- </div>
-
- ${org.mission_statement ? `<p class="org-mission">${truncateText(org.mission_statement, 350)}</p>` : ''}
-
- <div class="org-tags">
- ${topicTags}
- ${categoryTags}
- </div>
-
- <div class="org-links">
- <a href="${org.website_url}" target="_blank" rel="noopener" class="org-link">
- Visit Website →
- </a>
- ${org.cypres_page_url ? `<a href="${org.cypres_page_url}" target="_blank" rel="noopener" class="org-link">
- Cy Pres Page →
- </a>` : ''}
- </div>
- </div>
- `;
-
- return card;
-}
-
-// Event Handlers
-function handleSearch() {
- state.filters.search = elements.searchInput.value.trim();
- state.currentPage = 1;
- loadOrganizations();
-}
-
-function handleApplyFilters() {
- state.filters.legalTopics = Array.from(elements.legalTopicFilter.selectedOptions)
- .map(opt => opt.value)
- .filter(val => val !== '');
-
- state.filters.categories = Array.from(elements.categoryFilter.selectedOptions)
- .map(opt => opt.value)
- .filter(val => val !== '');
-
- state.filters.state = elements.stateFilter.value;
-
- state.currentPage = 1;
- loadOrganizations();
-}
-
-function handleClearFilters() {
- elements.searchInput.value = '';
- elements.legalTopicFilter.selectedIndex = 0;
- elements.categoryFilter.selectedIndex = 0;
- elements.stateFilter.selectedIndex = 0;
-
- state.filters = {
- search: '',
- legalTopics: [],
- categories: [],
- state: ''
- };
-
- state.currentPage = 1;
- loadOrganizations();
-}
-
-function changePage(delta) {
- const newPage = state.currentPage + delta;
- const maxPage = Math.ceil(state.totalResults / state.pageSize);
-
- if (newPage >= 1 && newPage <= maxPage) {
- state.currentPage = newPage;
- loadOrganizations();
- window.scrollTo({ top: 0, behavior: 'smooth' });
- }
-}
-
-function updatePagination() {
- const maxPage = Math.ceil(state.totalResults / state.pageSize);
-
- elements.prevPageBtn.disabled = state.currentPage === 1;
- elements.nextPageBtn.disabled = state.currentPage >= maxPage || maxPage === 0;
- elements.pageInfo.textContent = `Page ${state.currentPage} of ${maxPage || 1}`;
-}
-
-function updateResultsCount() {
- const start = (state.currentPage - 1) * state.pageSize + 1;
- const end = Math.min(state.currentPage * state.pageSize, state.totalResults);
-
- if (state.totalResults === 0) {
- elements.resultsCount.textContent = 'No results';
- } else {
- elements.resultsCount.textContent = `Showing ${start}-${end} of ${state.totalResults.toLocaleString()} results`;
- }
-}
-
-// Utility Functions
-function showLoading(show) {
- elements.loadingSpinner.classList.toggle('hidden', !show);
- elements.organizationsList.classList.toggle('hidden', show);
-}
-
-function showError(message) {
- elements.errorMessage.textContent = message;
- elements.errorMessage.classList.remove('hidden');
-}
-
-function hideError() {
- elements.errorMessage.classList.add('hidden');
-}
-
-function truncateText(text, maxLength) {
- if (!text) return '';
- if (text.length <= maxLength) return text;
- return text.substring(0, maxLength) + '...';
-}
-
-function parseArray(pgArray) {
- if (!pgArray) return [];
- if (Array.isArray(pgArray)) return pgArray;
-
- // PostgreSQL returns arrays as strings like "{item1,item2,item3}"
- if (typeof pgArray === 'string') {
- return pgArray
- .replace(/^\{|\}$/g, '')
- .split(',')
- .filter(Boolean);
- }
-
- return [];
-}
-
-// Start the application
-document.addEventListener('DOMContentLoaded', init);
diff --git a/frontend/src/cases.html b/frontend/src/cases.html
index 517b787..94b2ec4 100644
--- a/frontend/src/cases.html
+++ b/frontend/src/cases.html
@@ -254,7 +254,7 @@
<span class="recipient-tag">Public Interest Research Group (PIRG) - Rejected</span>
</div>
<div class="case-links">
- <a href="https://www.insideclassactions.com/2024/01/04/federal-court-rejects-class-action-settlement-over-cy-pres-provision/" target="_blank" class="case-link">Read Opinion →</a>
+ <a href="https://www.insideclassactions.com/2024/01/04/federal-court-rejects-class-action-settlement-over-cy-pres-provision/" target="_blank" rel="noopener noreferrer" class="case-link">Read Opinion →</a>
</div>
</div>
@@ -285,8 +285,8 @@
<span class="recipient-tag">Nonprofit organization providing student loan borrower counseling (to be established)</span>
</div>
<div class="case-links">
- <a href="https://www.courtlistener.com/docket/7974080/hyland-v-navient-corporation/" target="_blank" class="case-link">View Docket →</a>
- <a href="https://law.justia.com/cases/federal/appellate-courts/ca2/20-3765/20-3765-2022-09-07.html" target="_blank" class="case-link">Read Opinion →</a>
+ <a href="https://www.courtlistener.com/docket/7974080/hyland-v-navient-corporation/" target="_blank" rel="noopener noreferrer" class="case-link">View Docket →</a>
+ <a href="https://law.justia.com/cases/federal/appellate-courts/ca2/20-3765/20-3765-2022-09-07.html" target="_blank" rel="noopener noreferrer" class="case-link">Read Opinion →</a>
</div>
</div>
@@ -321,7 +321,7 @@
<span class="recipient-tag">National Center for Youth Law</span>
</div>
<div class="case-links">
- <a href="https://www.phoenixclassaction.com/washington-esa/" target="_blank" class="case-link">Read Opinion →</a>
+ <a href="https://www.phoenixclassaction.com/washington-esa/" target="_blank" rel="noopener noreferrer" class="case-link">Read Opinion →</a>
</div>
</div>
@@ -353,7 +353,7 @@
<span class="recipient-tag">Public Counsel</span>
</div>
<div class="case-links">
- <a href="http://wifibluetoothgrayoutsettlement.com/" target="_blank" class="case-link">View Docket →</a>
+ <a href="http://wifibluetoothgrayoutsettlement.com/" target="_blank" rel="noopener noreferrer" class="case-link">View Docket →</a>
</div>
</div>
diff --git a/frontend/src/index.html.backup b/frontend/src/index.html.backup
deleted file mode 100644
index 05682ab..0000000
--- a/frontend/src/index.html.backup
+++ /dev/null
@@ -1,129 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>CyPresAwards - Non-Profit Cy Pres Directory</title>
- <meta name="description" content="Comprehensive database of US non-profit organizations with cy pres statements for legal settlement distribution.">
- <link rel="stylesheet" href="styles.css">
-</head>
-<body>
- <header>
- <div class="container">
- <div class="header-content">
- <div class="header-text">
- <h1>CyPresAwards</h1>
- <p class="tagline">Directory of Non-Profits with Cy Pres Statements</p>
- </div>
- <nav class="header-nav">
- <a href="index.html" class="nav-link active">Search Organizations</a>
- <a href="submit.html" class="nav-link">Add My Org</a>
- <a href="cases.html" class="nav-link">Cy Pres Cases</a>
- <a href="settlement-firms.html" class="nav-link">Settlement Firms</a>
- </nav>
- </div>
- </div>
- </header>
-
- <main class="container">
- <!-- Search and Filter Section -->
- <section class="search-section">
- <div class="search-bar">
- <input
- type="text"
- id="searchInput"
- placeholder="Search by organization name or mission..."
- aria-label="Search organizations">
- <button id="searchBtn" class="btn btn-primary">Search</button>
- </div>
-
- <div class="filters">
- <div class="filter-group">
- <label for="legalTopicFilter">Legal Topic:</label>
- <select id="legalTopicFilter" multiple size="5">
- <option value="">All Topics</option>
- </select>
- </div>
-
- <div class="filter-group">
- <label for="categoryFilter">Category:</label>
- <select id="categoryFilter" multiple size="5">
- <option value="">All Categories</option>
- </select>
- </div>
-
- <div class="filter-group">
- <label for="stateFilter">State:</label>
- <select id="stateFilter">
- <option value="">All States</option>
- <option value="CA">California</option>
- <option value="NY">New York</option>
- <option value="TX">Texas</option>
- <option value="FL">Florida</option>
- <option value="IL">Illinois</option>
- <!-- Add more states as needed -->
- </select>
- </div>
- </div>
-
- <div class="filter-actions">
- <button id="applyFilters" class="btn btn-primary">Apply Filters</button>
- <button id="clearFilters" class="btn btn-secondary">Clear Filters</button>
- </div>
- </section>
-
- <!-- Statistics Section -->
- <section class="stats-section">
- <div class="stat-card">
- <h3 id="totalOrgs">0</h3>
- <p>Total Organizations</p>
- </div>
- <div class="stat-card">
- <h3 id="totalCategories">0</h3>
- <p>Categories</p>
- </div>
- <div class="stat-card">
- <h3 id="totalTopics">0</h3>
- <p>Legal Topics</p>
- </div>
- </section>
-
- <!-- Results Section -->
- <section class="results-section">
- <div class="results-header">
- <div>
- <h2>🏛️ Organizations</h2>
- <p class="results-subtitle">Nonprofits accepting cy pres awards</p>
- </div>
- <span id="resultsCount" class="results-count">Showing 0 results</span>
- </div>
-
- <div id="loadingSpinner" class="loading hidden">
- <div class="spinner"></div>
- <p>Loading organizations...</p>
- </div>
-
- <div id="errorMessage" class="error-message hidden"></div>
-
- <div id="organizationsList" class="organizations-list">
- <!-- Organizations will be dynamically inserted here -->
- </div>
-
- <div class="pagination">
- <button id="prevPage" class="btn btn-secondary" disabled>Previous</button>
- <span id="pageInfo">Page 1</span>
- <button id="nextPage" class="btn btn-secondary">Next</button>
- </div>
- </section>
- </main>
-
- <footer>
- <div class="container">
- <p>© 2024 CyPresAwards. A comprehensive database for legal professionals to identify suitable non-profits for cy pres awards.</p>
- <p class="disclaimer">This database is provided for informational purposes. Please verify all information directly with organizations.</p>
- </div>
- </footer>
-
- <script src="app.js"></script>
-</body>
-</html>
diff --git a/frontend/src/news.html b/frontend/src/news.html
index 0367181..094e34f 100644
--- a/frontend/src/news.html
+++ b/frontend/src/news.html
@@ -181,7 +181,7 @@
<div class="news-card">
<div class="news-header">
<h3 class="news-title">
- <a href="https://www.insideclassactions.com/2024/01/04/federal-court-rejects-class-action-settlement-over-cy-pres-provision/" target="_blank">
+ <a href="https://www.insideclassactions.com/2024/01/04/federal-court-rejects-class-action-settlement-over-cy-pres-provision/" target="_blank" rel="noopener noreferrer">
Court Rejects Cy Pres Settlement in Hawes v. Macy's
</a>
</h3>
@@ -193,7 +193,7 @@
<p class="news-description">
U.S. District Court (S.D. Ohio) rejected a $10.5M settlement because proposed recipient PIRG was not sufficiently related to false advertising claims in bed sheets case. This ruling underscores the importance of selecting cy pres recipients that have a clear nexus to the underlying claims.
</p>
- <a href="https://www.insideclassactions.com/2024/01/04/federal-court-rejects-class-action-settlement-over-cy-pres-provision/" target="_blank" class="news-link">
+ <a href="https://www.insideclassactions.com/2024/01/04/federal-court-rejects-class-action-settlement-over-cy-pres-provision/" target="_blank" rel="noopener noreferrer" class="news-link">
Read Full Article →
</a>
</div>
@@ -202,7 +202,7 @@
<div class="news-card">
<div class="news-header">
<h3 class="news-title">
- <a href="https://www.scotusblog.com/case-files/cases/hyland-v-navient-corporation/" target="_blank">
+ <a href="https://www.scotusblog.com/case-files/cases/hyland-v-navient-corporation/" target="_blank" rel="noopener noreferrer">
Supreme Court Denies Review of Cy Pres-Only Settlement in Hyland v. Navient
</a>
</h3>
@@ -214,7 +214,7 @@
<p class="news-description">
SCOTUS declined to hear challenge to Second Circuit's approval of $2.25M cy pres-only settlement with no distribution to class members. The decision allows the Second Circuit's approval to stand, representing a significant development in cy pres jurisprudence.
</p>
- <a href="https://www.scotusblog.com/case-files/cases/hyland-v-navient-corporation/" target="_blank" class="news-link">
+ <a href="https://www.scotusblog.com/case-files/cases/hyland-v-navient-corporation/" target="_blank" rel="noopener noreferrer" class="news-link">
Read Full Article →
</a>
</div>
@@ -223,7 +223,7 @@
<div class="news-card">
<div class="news-header">
<h3 class="news-title">
- <a href="https://law.justia.com/cases/federal/appellate-courts/ca2/20-3765/20-3765-2022-09-07.html" target="_blank">
+ <a href="https://law.justia.com/cases/federal/appellate-courts/ca2/20-3765/20-3765-2022-09-07.html" target="_blank" rel="noopener noreferrer">
Second Circuit Approves Cy Pres-Only Settlement in Student Loan Case
</a>
</h3>
@@ -235,7 +235,7 @@
<p class="news-description">
Court approved settlement establishing nonprofit for student loan counseling despite no direct payments to class members in Hyland v. Navient. This precedent-setting decision addresses the validity of cy pres-only settlements where direct distribution is impractical.
</p>
- <a href="https://law.justia.com/cases/federal/appellate-courts/ca2/20-3765/20-3765-2022-09-07.html" target="_blank" class="news-link">
+ <a href="https://law.justia.com/cases/federal/appellate-courts/ca2/20-3765/20-3765-2022-09-07.html" target="_blank" rel="noopener noreferrer" class="news-link">
Read Full Article →
</a>
</div>
diff --git a/frontend/src/outreach.html b/frontend/src/outreach.html
index 3fb18d5..7c68fc1 100644
--- a/frontend/src/outreach.html
+++ b/frontend/src/outreach.html
@@ -204,7 +204,7 @@
orgList.innerHTML = orgs.map(org => `
<div class="org-item">
- <a href="${org.url}" target="_blank" class="org-url">${org.url}</a>
+ <a href="${org.url}" target="_blank" rel="noopener noreferrer" class="org-url">${org.url}</a>
<span class="org-date">${new Date(org.scraped_at).toLocaleDateString()}</span>
</div>
`).join('');
diff --git a/frontend/src/simple.html b/frontend/src/simple.html
index c2f83c7..5b11f0e 100644
--- a/frontend/src/simple.html
+++ b/frontend/src/simple.html
@@ -52,8 +52,8 @@
<div class="org-card">
<h3 class="org-name">${org.name}</h3>
<p><strong>Location:</strong> ${org.location_city || ''} ${org.location_state || ''} ${org.location_country || ''}</p>
- <p><strong>Website:</strong> <a href="${org.website_url}" target="_blank">${org.website_url}</a></p>
- ${org.cypres_page_url ? `<p><strong>Cy Pres:</strong> <a href="${org.cypres_page_url}" target="_blank">${org.cypres_page_url}</a></p>` : ''}
+ <p><strong>Website:</strong> <a href="${org.website_url}" target="_blank" rel="noopener noreferrer">${org.website_url}</a></p>
+ ${org.cypres_page_url ? `<p><strong>Cy Pres:</strong> <a href="${org.cypres_page_url}" target="_blank" rel="noopener noreferrer">${org.cypres_page_url}</a></p>` : ''}
${org.mission_statement ? `<p>${org.mission_statement.substring(0, 200)}...</p>` : ''}
</div>
`).join('');
← 7a2e112 initial snapshot — gitify all builds (CLAUDE.md rule 2026-05
·
back to Cypress Awards
·
(newest)